package com.github.michaelbull.rs.hiscores; import com.github.michaelbull.rs.Client; import com.github.michaelbull.rs.HttpClient; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.commons.csv.CSVRecord; import java.io.IOException; import java.util.Collection; import java.util.Optional; import java.util.regex.Pattern; /** * Represents the RuneScape Hiscores API. * @see <a href="https://runescape.wiki/w/Application_programming_interface#Hiscores">Hiscores APIs</a> */ public final class Hiscores { /** * The format of the URL to fetch a {@link Player}. */ private static final String PLAYER_INFORMATION_URL_FORMAT = HttpClient.WEB_SERVICES_URL + "/m=%s/index_lite.ws?player=%s"; /** * The format of the URL to fetch a list of {@link ClanMate}s. */ private static final String CLAN_INFORMATION_URL_FORMAT = HttpClient.WEB_SERVICES_URL + "/m=clan-hiscores/members_lite.ws?clanName=%s"; /** * The RuneScape skill names. */ public static final ImmutableList<String> SKILL_NAMES = ImmutableList.of( "Overall", "Attack", "Defence", "Strength", "Constitution", "Ranged", "Prayer", "Magic", "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining", "Herblore", "Agility", "Thieving", "Slayer", "Farming", "Runecrafting", "Hunter", "Construction", "Summoning", "Dungeoneering", "Divination", "Invention" ); /** * The oldschool RuneScape skill names. */ public static final ImmutableList<String> OLDSCHOOL_SKILL_NAMES = ImmutableList.of( "Overall", "Attack", "Defence", "Strength", "Constitution", "Ranged", "Prayer", "Magic", "Cooking", "Woodcutting", "Fletching", "Fishing", "Firemaking", "Crafting", "Smithing", "Mining", "Herblore", "Agility", "Thieving", "Slayer", "Farming", "Runecrafting", "Hunter", "Construction" ); /** * The RuneScape activity names. */ public static final ImmutableList<String> ACTIVITY_NAMES = ImmutableList.of( "Bounty Hunter", "B.H. Rogues", "Dominion Tower", "The Crucible", "Castle Wars games", "B.A. Attackers", "B.A. Defenders", "B.A. Collectors", "B.A. Healers", "Duel Tournament", "Mobilising Armies", "Conquest", "Fist of Guthix", "GG: Athletics", "GG: Resource Race", "WE2: Armadyl Lifetime Contribution", "WE2: Bandos Lifetime Contribution", "WE2: Armadyl PvP kills", "WE2: Bandos PvP kills", "Heist Guard Level", "Heist Robber Level", "CFP: 5 game average", "AF15: Cow Tipping", "AF15: Rats killed after the miniquest", "Clue Scrolls (easy)", "Clue Scrolls (medium)", "Clue Scrolls (hard)", "Clue Scrolls (elite)", "Clue Scrolls (master)" ); /** * The oldschool RuneScape activity names. */ public static final ImmutableList<String> OLDSCHOOL_ACTIVITY_NAMES = ImmutableList.of( "Clues", "Bounty Hunter", "B.H. Rogues" ); /** * The {@link Pattern} that is replaced with '+' symbols when parsing player/clan names. */ private static final Pattern NAME_SPACER = Pattern.compile(" "); /** * Reads the {@link HiscoreActivity}s from an {@link ImmutableList} of {@link CSVRecord}s. * @param records The {@link CSVRecord}s. * @param skills The skill names. * @param activities The activity names. * @return An {@link ImmutableMap} of {@link HiscoreActivity} names to {@link HiscoreActivity}s. */ private static ImmutableMap<String, HiscoreActivity> readActivities(ImmutableList<CSVRecord> records, Collection<String> skills, ImmutableList<String> activities) { ImmutableMap.Builder<String, HiscoreActivity> builder = ImmutableMap.builder(); if (records.size() >= (skills.size() + activities.size())) { for (int i = 0; i < activities.size(); i++) { CSVRecord record = records.get(skills.size() + i); String activityName = activities.get(i); HiscoreActivity.fromCsv(record).ifPresent(activity -> builder.put(activityName, activity)); } } return builder.build(); } /** * Reads the {@link Skill}s from an {@link ImmutableList} of {@link CSVRecord}s. * @param records The {@link CSVRecord}s. * @param skills The skill names. * @return An {@link ImmutableMap} of {@link Skill} names to {@link Skill}s. */ private static ImmutableMap<String, Skill> readSkills(ImmutableList<CSVRecord> records, ImmutableList<String> skills) { ImmutableMap.Builder<String, Skill> builder = ImmutableMap.builder(); if (records.size() >= skills.size()) { for (int i = 0; i < skills.size(); i++) { CSVRecord record = records.get(i); String skillName = skills.get(i); Skill.fromCsv(record).ifPresent(skill -> builder.put(skillName, skill)); } } return builder.build(); } /** * The web-services {@link Client}. */ private final Client client; /** * Creates a new {@link Hiscores}. * @param client The web-services {@link Client}. */ public Hiscores(Client client) { this.client = Preconditions.checkNotNull(client); } /** * Gets a {@link Player} based on their display name. * @param displayName The player's display name. * @param table The table of {@link Hiscores}. * @return An {@link Optional} containing the {@link Player}, or {@link Optional#empty()} if no {@link Player} was found with that name. * @throws IOException If an I/O error occurs. * @see <a href="https://runescape.wiki/w/Application_programming_interface#Hiscores_Lite">Hiscores Lite</a> * @see <a href="https://runescape.wiki/w/Application_programming_interface#Ironman_Lite">Ironman Hiscores Lite</a> * @see <a href="https://runescape.wiki/w/Application_programming_interface#Hardcore_Ironman_Lite">Hardcore Ironman Hiscores Lite</a> */ public Optional<Player> playerInformation(String displayName, HiscoreTable table) throws IOException { Preconditions.checkNotNull(displayName); Preconditions.checkNotNull(table); String escapedName = NAME_SPACER.matcher(displayName).replaceAll("+"); String url = String.format(PLAYER_INFORMATION_URL_FORMAT, table.getName(), escapedName); ImmutableList<CSVRecord> records = client.fromCSV(url); ImmutableList<String> skillNames = table.getSkillNames(); ImmutableList<String> activityNames = table.getActivityNames(); if (records.size() >= (skillNames.size() + activityNames.size())) { ImmutableMap<String, Skill> skills = readSkills(records, skillNames); ImmutableMap<String, HiscoreActivity> activities = readActivities(records, skillNames, activityNames); return Optional.of(new Player(skills, activities)); } else { return Optional.empty(); } } /** * Gets an {@link ImmutableList} of {@link ClanMate}s within a clan, based on the clan's name. * @param clanName The clan's name. * @return An {@link ImmutableList} of {@link ClanMate}s in the clan. * @throws IOException If an I/O error occurs. * @see <a href="https://runescape.wiki/w/Application_programming_interface#Clan_Members_Lite">Clan Members Lite</a> */ public ImmutableList<ClanMate> clanInformation(String clanName) throws IOException { Preconditions.checkNotNull(clanName); String escapedName = NAME_SPACER.matcher(clanName).replaceAll("+"); String url = String.format(CLAN_INFORMATION_URL_FORMAT, escapedName); ImmutableList<CSVRecord> records = client.fromCSV(url); ImmutableList.Builder<ClanMate> builder = ImmutableList.builder(); for (int i = 1; i < records.size(); i++) { CSVRecord record = records.get(i); ClanMate.fromCsv(record).ifPresent(builder::add); } return builder.build(); } }