def from_content(cls, content): """Creates an instance of the class from the html content of the tournament's leaderboards page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`TournamentLeaderboard` The tournament contained in the page, or None if the tournament leaderboard doesn't exist. Raises ------ InvalidContent If content is not the HTML of a tournament's leaderboard page. """ try: parsed_content = parse_tibiacom_content(content) tables = parsed_content.find_all('div', attrs={'class': 'TableContainer'}) if not tables: raise InvalidContent("content does not belong to the Tibia.com's tournament leaderboards section") selector_table = tables[0] leaderboard = cls() result = leaderboard._parse_leaderboard_selectors(selector_table) if not result: return None ranking_table = tables[1] leaderboard._parse_leaderboard_entries(ranking_table) return leaderboard except AttributeError as e: raise InvalidContent("content does not belong to the Tibia.com's tournament leaderboards section", e)
def list_from_tibiadata(cls, content): """Parses the content of a house list from TibiaData.com into a list of houses Parameters ---------- content: :class:`str` The raw JSON response from TibiaData Returns ------- :class:`list` of :class:`ListedHouse` Raises ------ InvalidContent` Content is not the house list from TibiaData.com """ json_data = parse_json(content) try: house_data = json_data["houses"] houses = [] house_type = HouseType.HOUSE if house_data["type"] == "houses" else HouseType.GUILDHALL for house_json in house_data["houses"]: house = ListedHouse(house_json["name"], house_data["world"], house_json["houseid"], size=house_json["size"], rent=house_json["rent"], town=house_data["town"], type=house_type) house._parse_status(house_json["status"]) houses.append(house) return houses except KeyError: raise InvalidContent("content is not a house list json response from TibiaData.com")
def boosted_creature_from_header(cls, content): """Get the boosted creature from any Tibia.com page. Parameters ---------- content: :class:`str` The HTML content of a Tibia.com page. Returns ------- :class:`CreatureEntry` The boosted creature of the day. Raises ------ InvalidContent If content is not the HTML of a Tibia.com's page. """ try: parsed_content = bs4.BeautifulSoup(content.replace('ISO-8859-1', 'utf-8'), "lxml", parse_only=bs4.SoupStrainer("div", attrs={"id": "RightArtwork"})) img = parsed_content.find("img", attrs={"id": "Monster"}) name = img["title"].replace(BOOSTED_ALT, "").strip() image_url = img["src"] identifier = image_url.split("/")[-1].replace(".gif", "") return CreatureEntry(name, identifier) except TypeError as e: raise InvalidContent("content is not from Tibia.com", e)
def from_content(cls, content): """ Gets the boosted creature from any Tibia.com page. Parameters ---------- content: :class:`str` The HTML content of a Tibia.com page. Returns ------- :class:`BoostedCreature` The boosted creature of the day. Raises ------ InvalidContent If content is not the HTML of a Tibia.com's page. """ try: parsed_content = bs4.BeautifulSoup( content.replace('ISO-8859-1', 'utf-8'), "lxml", parse_only=bs4.SoupStrainer("div", attrs={"id": "RightArtwork"})) img = parsed_content.find("img", attrs={"id": "Monster"}) name = img["title"].replace(BOOSTED_ALT, "").strip() image_url = img["src"] return cls(name, image_url) except TypeError: raise InvalidContent("content is not from Tibia.com")
def parse_json(content): """Tries to parse a string into a json object. This also performs a trim of all values, recursively removing leading and trailing whitespace. Parameters ---------- content: :class:`str` A JSON format string. Returns ------- obj The object represented by the json string. Raises ------ InvalidContent If the content is not a valid json string. """ try: json_content = json.loads(content) return _recursive_strip(json_content) except json.JSONDecodeError: raise InvalidContent("content is not a json string.")
def from_content(cls, content): """Parse the content of the World Overview section from Tibia.com into an object of this class. Parameters ---------- content: :class:`str` The HTML content of the World Overview page in Tibia.com Returns ------- :class:`WorldOverview` An instance of this class containing all the information. Raises ------ InvalidContent If the provided content is not the HTML content of the worlds section in Tibia.com """ parsed_content = parse_tibiacom_content(content) world_overview = WorldOverview() try: record_table, *tables \ = parsed_content.find_all("table", {"class": "TableContent"}) m = record_regexp.search(record_table.text) world_overview.record_count = parse_integer(m.group("count")) world_overview.record_date = parse_tibia_datetime(m.group("date")) world_overview._parse_worlds_tables(tables) return world_overview except (AttributeError, KeyError, ValueError) as e: raise InvalidContent("content does not belong to the World Overview section in Tibia.com", e)
def from_content(cls, content): """Creates an instance of the class from the HTML content of the guild's page. Parameters ----------- content: :class:`str` The HTML content of the page. Returns ---------- :class:`Guild` The guild contained in the page or None if it doesn't exist. Raises ------ InvalidContent If content is not the HTML of a guild's page. """ if "An internal error has occurred" in content: return None parsed_content = parse_tibiacom_content(content) try: name_header = parsed_content.find('h1') guild = Guild(name_header.text.strip()) except AttributeError: raise InvalidContent( "content does not belong to a Tibia.com guild page.") if not guild._parse_logo(parsed_content): raise InvalidContent( "content does not belong to a Tibia.com guild page.") info_container = parsed_content.find("div", id="GuildInformationContainer") guild._parse_guild_info(info_container) guild._parse_application_info(info_container) guild._parse_guild_homepage(info_container) guild._parse_guild_guildhall(info_container) guild._parse_guild_disband_info(info_container) guild._parse_guild_members(parsed_content) if guild.guildhall and guild.members: guild.guildhall.owner = guild.members[0].name return guild
def from_content(cls, content): """Creates an instance of the class from the html content of a highscores page. Notes ----- Tibia.com only shows up to 50 entries per page, so in order to obtain the full highscores, all pages must be obtained individually and merged into one. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`Highscores` The highscores results contained in the page. Raises ------ InvalidContent If content is not the HTML of a highscore's page.""" parsed_content = parse_tibiacom_content(content) tables = cls._parse_tables(parsed_content) filters = tables.get("Highscores Filter") if filters is None: raise InvalidContent("content does is not from the highscores section of Tibia.com") world_filter, vocation_filter, category_filter = filters world = world_filter.find("option", {"selected": True})["value"] if world == "ALL": world = None category = int(category_filter.find("option", {"selected": True})["value"]) vocation_selected = vocation_filter.find("option", {"selected": True}) vocation = int(vocation_selected["value"]) if vocation_selected else 0 highscores = cls(world, category, vocation=vocation) entries = tables.get("Highscores") last_update_container = parsed_content.find("span", attrs={"class": "RightArea"}) if last_update_container: m = numeric_pattern.search(last_update_container.text) highscores.last_updated = datetime.timedelta(minutes=int(m.group(1))) if m else datetime.timedelta() if entries is None: return None _, header, *rows = entries info_row = rows.pop() pages_div, results_div = info_row.find_all("div") page_links = pages_div.find_all("a") listed_pages = [int(p.text) for p in page_links] if listed_pages: highscores.page = next((x for x in range(1, listed_pages[-1] + 1) if x not in listed_pages), 0) highscores.total_pages = max(int(page_links[-1].text), highscores.page) highscores.results_count = int(results_pattern.search(results_div.text).group(1)) for row in rows: cols_raw = row.find_all('td') if "There is currently no data" in cols_raw[0].text: break highscores._parse_entry(cols_raw) return highscores
def list_from_content(cls, content): """ Gets a list of news from the HTML content of the news search page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`list` of :class:`ListedNews` List of news in the search results. Raises ------ InvalidContent If content is not the HTML of a news search's page. """ try: parsed_content = parse_tibiacom_content(content) tables = parsed_content.find_all("table", attrs={"width": "100%"}) news = [] news_table = tables[0] title_row = news_table.find("td", attrs={ "class": "white", "colspan": "3" }) if title_row.text != "Search Results": raise InvalidContent( "content is not from the news archive section in Tibia.com" ) rows = news_table.find_all("tr", attrs={"class": ["Odd", "Even"]}) for row in rows: cols_raw = row.find_all('td') if len(cols_raw) != 3: continue entry = cls._parse_entry(cols_raw) news.append(entry) return news except (AttributeError, IndexError): raise InvalidContent( "content is not from the news archive section in Tibia.com")
def from_content(cls, content): """Creates an instance of the class from the HTML content of the kill statistics' page. Parameters ----------- content: :class:`str` The HTML content of the page. Returns ---------- :class:`KillStatistics` The kill statistics contained in the page or None if it doesn't exist. Raises ------ InvalidContent If content is not the HTML of a kill statistics' page. """ try: parsed_content = parse_tibiacom_content(content) selection_table = parsed_content.find( 'div', attrs={'class': 'TableContainer'}) world = selection_table.find("option", {"selected": True})["value"] entries_table = parsed_content.find('table', attrs={ 'border': '0', 'cellpadding': '3' }) # If the entries table doesn't exist, it means that this belongs to an nonexistent or unselected world. if entries_table is None: return None header, subheader, *rows = entries_table.find_all('tr') entries = {} total = None for i, row in enumerate(rows): columns_raw = row.find_all('td') columns = [ c.text.replace('\xa0', ' ').strip() for c in columns_raw ] entry = RaceEntry( last_day_players_killed=int(columns[1]), last_day_killed=int(columns[2]), last_week_players_killed=int(columns[3]), last_week_killed=int(columns[4]), ) if i == len(rows) - 1: total = entry else: entries[columns[0]] = entry return cls(world, entries, total) except AttributeError: raise InvalidContent( "content does not belong to a Tibia.com kill statistics page.")
def from_content(cls, content): """Parse a Tibia.com response into a House object. Parameters ---------- content: :class:`str` HTML content of the page. Returns ------- :class:`House` The house contained in the page, or None if the house doesn't exist. Raises ------ InvalidContent If the content is not the house section on Tibia.com """ parsed_content = parse_tibiacom_content(content) image_column, desc_column, *_ = parsed_content.find_all('td') if "Error" in image_column.text: return None image = image_column.find('img') for br in desc_column.find_all("br"): br.replace_with("\n") description = desc_column.text.replace("\u00a0", " ").replace("\n\n", "\n") lines = description.splitlines() try: name, beds, info, state, *_ = lines except ValueError: raise InvalidContent( "content does is not from the house section of Tibia.com") house = cls(name.strip()) house.image_url = image["src"] house.id = int(id_regex.search(house.image_url).group(1)) m = bed_regex.search(beds) if m: if m.group("type").lower() in ["guildhall", "clanhall"]: house.type = HouseType.GUILDHALL else: house.type = HouseType.HOUSE house.beds = int(m.group("beds")) m = info_regex.search(info) if m: house.world = m.group("world") house.rent = parse_tibia_money(m.group("rent")) house.size = int(m.group("size")) house._parse_status(state) return house
def from_content(cls, content): """Get a list of news from the HTML content of the news search page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`NewsArchive` The news archive with the news found. Raises ------ InvalidContent If content is not the HTML of a news search's page. """ try: parsed_content = parse_tibiacom_content(content) tables = parse_tibiacom_tables(parsed_content) if "News Archive Search" not in tables: raise InvalidContent( "content is not from the news archive section in Tibia.com" ) form = parsed_content.find("form") news_archive = cls._parse_filtering(form) if "Search Results" in tables: rows = tables["Search Results"].find_all( "tr", attrs={"class": ["Odd", "Even"]}) for row in rows: cols_raw = row.find_all('td') if len(cols_raw) != 3: continue entry = cls._parse_entry(cols_raw) news_archive.entries.append(entry) return news_archive except (AttributeError, IndexError, ValueError, KeyError) as e: raise InvalidContent( "content is not from the news archive section in Tibia.com", e)
def from_content(cls, content): """Gets a guild's war information from Tibia.com's content Parameters ---------- content: :class:`str` The HTML content of a guild's war section in Tibia.com Returns ------- :class:`GuildWars` The guild's war information. """ try: parsed_content = parse_tibiacom_content(content) table_current, table_history = parsed_content.find_all( "div", attrs={"class": "TableContainer"}) current_table_content = table_current.find( "table", attrs={"class": "TableContent"}) current_war = None guild_name = None if current_table_content is not None: for br in current_table_content.find_all("br"): br.replace_with("\n") current_war = cls._parse_current_war_information( current_table_content.text) else: current_war_text = table_current.text current_war_match = war_current_empty.search(current_war_text) guild_name = current_war_match.group(1) history_entries = [] history_contents = table_history.find_all( "table", attrs={"class": "TableContent"}) for history_content in history_contents: for br in history_content.find_all("br"): br.replace_with("\n") entry = cls._parse_war_history_entry(history_content.text) history_entries.append(entry) if current_war: guild_name = current_war.guild_name elif history_entries: guild_name = history_entries[0].guild_name return cls(guild_name, current=current_war, history=history_entries) except ValueError as e: raise InvalidContent( "content does not belong to the guild wars section", e)
def list_from_content(cls, content): """ Gets a list of guilds from the HTML content of the world guilds' page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`list` of :class:`ListedGuild` List of guilds in the current world. ``None`` if it's the list of a world that doesn't exist. Raises ------ InvalidContent If content is not the HTML of a guild's page. """ parsed_content = parse_tibiacom_content(content) selected_world = parsed_content.find('option', selected=True) try: if "choose world" in selected_world.text: # It belongs to a world that doesn't exist return None world = selected_world.text except AttributeError: raise InvalidContent( "Content does not belong to world guild list.") # First TableContainer contains world selector. _, *containers = parsed_content.find_all('div', class_="TableContainer") guilds = [] for container in containers: header = container.find('div', class_="Text") active = "Active" in header.text header, *rows = container.find_all( "tr", {'bgcolor': ["#D4C0A1", "#F1E0C6"]}) for row in rows: columns = row.find_all('td') logo_img = columns[0].find('img')["src"] description_lines = columns[1].get_text("\n").split("\n", 1) name = description_lines[0] description = None if len(description_lines) > 1: description = description_lines[1].replace("\r", "").replace( "\n", " ") guild = cls(name, world, logo_img, description, active) guilds.append(guild) return guilds
def from_content(cls, content): """Parse the content of a house list from Tibia.com into a list of houses. Parameters ---------- content: :class:`str` The raw HTML response from the house list. Returns ------- :class:`HouseSection` The houses found in the page. Raises ------ InvalidContent` Content is not the house list from Tibia.com """ try: parsed_content = parse_tibiacom_content(content) tables = parse_tibiacom_tables(parsed_content) house_results = cls() house_results._parse_filters(tables["House Search"]) if len(tables) < 2: return house_results houses_table = tables[list(tables.keys())[0]] _, *rows = houses_table.find_all("tr") for row in rows[1:]: cols = row.find_all("td") if len(cols) != 5: continue name = cols[0].text.replace('\u00a0', ' ') house = HouseEntry(name, house_results.world, 0, town=house_results.town, type=house_results.house_type) size = cols[1].text.replace('sqm', '') house.size = int(size) rent = cols[2].text.replace('gold', '') house.rent = parse_tibia_money(rent) status = cols[3].text.replace('\xa0', ' ') house._parse_status(status) id_input = cols[4].find("input", {'name': 'houseid'}) house.id = int(id_input["value"]) house_results.entries.append(house) return house_results except (ValueError, AttributeError, KeyError) as e: raise InvalidContent( "content does not belong to a Tibia.com house list", e)
def from_content(cls, content): """Creates an instance of the class from the html content of the tournament's page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`Tournament` The tournament contained in the page, or None if the tournament doesn't exist. Raises ------ InvalidContent If content is not the HTML of a tournament's page. """ try: if "An internal error has occurred" in content: return None if "Currently there is no Tournament running." in content: return None parsed_content = parse_tibiacom_content(content, builder='html5lib') box_content = parsed_content.find("div", attrs={"class": "BoxContent"}) tables = box_content.find_all('table', attrs={"class": "Table5"}) archive_table = box_content.find('table', attrs={"class": "Table4"}) tournament_details_table = tables[-1] info_tables = tournament_details_table.find_all( 'table', attrs={'class': 'TableContent'}) main_info = info_tables[0] rule_set = info_tables[1] score_set = info_tables[2] reward_set = info_tables[3] tournament = cls() tournament._parse_tournament_info(main_info) tournament._parse_tournament_rules(rule_set) tournament._parse_tournament_scores(score_set) tournament._parse_tournament_rewards(reward_set) if archive_table: tournament._parse_archive_list(archive_table) return tournament except IndexError as e: raise InvalidContent( "content does not belong to the Tibia.com's tournament section", e)
def list_from_content(cls, content): """Parses the content of a house list from Tibia.com into a list of houses Parameters ---------- content: :class:`str` The raw HTML response from the house list. Returns ------- :class:`list` of :class:`ListedHouse` Raises ------ InvalidContent` Content is not the house list from Tibia.com """ try: parsed_content = parse_tibiacom_content(content) table = parsed_content.find("table") header, *rows = table.find_all("tr") except (ValueError, AttributeError): raise InvalidContent("content does not belong to a Tibia.com house list") m = list_header_regex.match(header.text.strip()) if not m: return None town = m.group("town") world = m.group("world") house_type = HouseType.GUILDHALL if m.group("type") == "Guildhalls" else HouseType.HOUSE houses = [] for row in rows[1:]: cols = row.find_all("td") if len(cols) != 6: continue name = cols[0].text.replace('\u00a0', ' ') house = ListedHouse(name, world, 0, town=town, type=house_type) size = cols[1].text.replace('sqm', '') house.size = int(size) rent = cols[2].text.replace('gold', '') house.rent = parse_tibia_money(rent) status = cols[3].text.replace('\xa0', ' ') house._parse_status(status) id_input = cols[5].find("input", {'name': 'houseid'}) house.id = int(id_input["value"]) houses.append(house) return houses
def from_content(cls, content): """Get a list of guilds from the HTML content of the world guilds' page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`GuildsSection` List of guilds in the current world. :obj:`None` if it's the list of a world that doesn't exist. Raises ------ InvalidContent If content is not the HTML of a guild's page. """ try: parsed_content = parse_tibiacom_content(content) form = parsed_content.find("form") data = parse_form_data(form, include_options=True) selected_world = data["world"] if data["world"] else None available_worlds = [w for w in data["__options__"]["world"].values() if w] guilds = cls(selected_world, available_worlds=available_worlds) except AttributeError as e: raise InvalidContent("Content does not belong to world guild list.", e) # First TableContainer contains world selector. _, *containers = parsed_content.find_all('div', class_="TableContainer") for container in containers: header = container.find('div', class_="Text") active = "Active" in header.text header, *rows = container.find_all("tr", {'bgcolor': ["#D4C0A1", "#F1E0C6"]}) for row in rows: columns = row.find_all('td') logo_img = columns[0].find('img')["src"] description_lines = columns[1].get_text("\n").split("\n", 1) name = description_lines[0] description = None if len(description_lines) > 1: description = description_lines[1].replace("\r", "").replace("\n", " ") guild = GuildEntry(name, guilds.world, logo_img, description, active) guilds.entries.append(guild) return guilds
def from_content(cls, content): """Parses a Tibia.com response into a :class:`World`. Parameters ---------- content: :class:`str` The raw HTML from the server's information page. Returns ------- :class:`World` The World described in the page, or ``None``. Raises ------ InvalidContent If the provided content is not the html content of the world section in Tibia.com """ parsed_content = parse_tibiacom_content(content) tables = cls._parse_tables(parsed_content) try: error = tables.get("Error") if error and error[0].text == "World with this name doesn't exist!": return None selected_world = parsed_content.find('option', selected=True) world = cls(selected_world.text) world._parse_world_info(tables.get("World Information", [])) online_table = tables.get("Players Online", []) world.online_players = [] for row in online_table[1:]: cols_raw = row.find_all('td') name, level, vocation = (c.text.replace('\xa0', ' ').strip() for c in cols_raw) world.online_players.append( OnlineCharacter(name, world.name, int(level), vocation)) except AttributeError: raise InvalidContent( "content is not from the world section in Tibia.com") return world
def from_content(cls, content): """Create an instance of the class from the html content of the creature library's page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`Character` The character contained in the page. Raises ------ InvalidContent If content is not the HTML of a creature library's page. """ try: parsed_content = parse_tibiacom_content(content) boosted_creature_table = parsed_content.find("div", {"class": "TableContainer"}) boosted_creature_text = boosted_creature_table.find("div", {"class": "Text"}) if not boosted_creature_text or "Boosted" not in boosted_creature_text.text: return None boosted_creature_link = boosted_creature_table.find("a") url = urllib.parse.urlparse(boosted_creature_link["href"]) query = urllib.parse.parse_qs(url.query) boosted_creature = CreatureEntry(boosted_creature_link.text, query["race"][0]) list_table = parsed_content.find("div", style=lambda v: v and 'display: table' in v) entries_container = list_table.find_all("div", style=lambda v: v and 'float: left' in v) entries = [] for entry_container in entries_container: name = entry_container.text.strip() link = entry_container.find("a") url = urllib.parse.urlparse(link["href"]) query = urllib.parse.parse_qs(url.query) entries.append(CreatureEntry(name, query["race"][0])) return cls(boosted_creature, entries) except (AttributeError, ValueError) as e: raise InvalidContent("content is not the creature's library", e)
def list_from_tibiadata(cls, content): """Builds a character object from a TibiaData character response. Parameters ---------- content: :class:`str` A string containing the JSON response from TibiaData. Returns ------- :class:`list` of :class:`ListedGuild` The list of guilds contained. Raises ------ InvalidContent If content is not a JSON response of TibiaData's guild list. """ json_content = parse_json(content) try: guilds_obj = json_content["guilds"] guilds = [] for guild in guilds_obj["active"]: guilds.append( cls(guild["name"], guilds_obj["world"], logo_url=guild["guildlogo"], description=guild["desc"], active=True)) for guild in guilds_obj["formation"]: guilds.append( cls(guild["name"], guilds_obj["world"], logo_url=guild["guildlogo"], description=guild["desc"], active=False)) except KeyError: raise InvalidContent( "content doest not belong to a guilds response.") return guilds
def from_content(cls, content): """Create an instance of the class from the html content of a highscores page. Notes ----- Tibia.com only shows up to 50 entries per page, so in order to obtain the full highscores, all pages must be obtained individually and merged into one. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`Highscores` The highscores results contained in the page. Raises ------ InvalidContent If content is not the HTML of a highscore's page. """ parsed_content = parse_tibiacom_content(content) form = parsed_content.find("form") tables = cls._parse_tables(parsed_content) if form is None: if "Error" in tables and "The world doesn't exist!" in tables["Error"].text: return None raise InvalidContent("content does is not from the highscores section of Tibia.com") highscores = cls(None) highscores._parse_filters_table(form) last_update_container = parsed_content.find("span", attrs={"class": "RightArea"}) if last_update_container: m = numeric_pattern.search(last_update_container.text) highscores.last_updated = datetime.timedelta(minutes=int(m.group(1))) if m else datetime.timedelta() entries_table = tables.get("Highscores") highscores._parse_entries_table(entries_table) return highscores
def from_tibiadata(cls, content): """ Parses a TibiaData response into a House object. Parameters ---------- content: :class:`str` The JSON content of the TibiaData response. Returns ------- :class:`House` The house contained in the response, if found. Raises ------ InvalidContent If the content is not a house JSON response from TibiaData """ json_content = parse_json(content) try: house_json = json_content["house"] if not house_json["name"]: return None house = cls(house_json["name"], house_json["world"]) house.type = try_enum(HouseType, house_json["type"]) house.id = house_json["houseid"] house.beds = house_json["beds"] house.size = house_json["size"] house.size = house_json["size"] house.rent = house_json["rent"] house.image_url = house_json["img"] # Parsing the original status string is easier than dealing with TibiaData fields house._parse_status(house_json["status"]["original"]) except KeyError: raise InvalidContent("content is not a TibiaData house response.") return house
def from_content(cls, content): """Creates an instance of the class from the html content of the character's page. Parameters ---------- content: :class:`str` The HTML content of the page. Returns ------- :class:`Character` The character contained in the page, or None if the character doesn't exist Raises ------ InvalidContent If content is not the HTML of a character's page. """ parsed_content = parse_tibiacom_content(content) tables = cls._parse_tables(parsed_content) char = Character() if "Could not find character" in tables.keys(): return None if "Character Information" in tables.keys(): char._parse_character_information(tables["Character Information"]) else: raise InvalidContent( "content does not contain a tibia.com character information page." ) char._parse_achievements(tables.get("Account Achievements", [])) if "Account Badges" in tables: char._parse_badges(tables["Account Badges"]) char._parse_deaths(tables.get("Character Deaths", [])) char._parse_account_information(tables.get("Account Information", [])) char._parse_other_characters(tables.get("Characters", [])) return char
def from_content(cls, content): """Create an instance of the class from the HTML content of the kill statistics' page. Parameters ----------- content: :class:`str` The HTML content of the page. Returns ---------- :class:`KillStatistics` The kill statistics contained in the page or None if it doesn't exist. Raises ------ InvalidContent If content is not the HTML of a kill statistics' page. """ try: parsed_content = parse_tibiacom_content(content) entries_table = parsed_content.find('table', attrs={ 'border': '0', 'cellpadding': '3' }) form = parsed_content.find("form") data = parse_form_data(form, include_options=True) world = data["world"] available_worlds = list(data["__options__"]["world"].values()) if not entries_table: entries_table = parsed_content.find("table", {"class": "Table3"}) # If the entries table doesn't exist, it means that this belongs to an nonexistent or unselected world. if entries_table is None: return None header, subheader, *rows = entries_table.find_all('tr') entries = {} total = None for i, row in enumerate(rows): columns_raw = row.find_all('td') columns = [ c.text.replace('\xa0', ' ').strip() for c in columns_raw ] if not columns[2].isnumeric(): continue entry = RaceEntry(last_day_players_killed=int(columns[1]), last_day_killed=int(columns[2]), last_week_players_killed=int(columns[3]), last_week_killed=int(columns[4])) if i == len(rows) - 1: total = entry else: entries[columns[0]] = entry return cls(world, entries, total, available_worlds=available_worlds) except AttributeError as e: raise InvalidContent( "content does not belong to a Tibia.com kill statistics page.", e)
def from_tibiadata(cls, content): """Builds a guild object from a TibiaData character response. Parameters ---------- content: :class:`str` The json string from the TibiaData response. Returns ------- :class:`Guild` The guild contained in the description or ``None``. Raises ------ InvalidContent If content is not a JSON response of a guild's page. """ json_content = parse_json(content) guild = cls() try: guild_obj = json_content["guild"] if "error" in guild_obj: return None guild_data = guild_obj["data"] guild.name = guild_data["name"] guild.world = guild_data["world"] guild.logo_url = guild_data["guildlogo"] guild.description = guild_data["description"] guild.founded = parse_tibiadata_date(guild_data["founded"]) guild.open_applications = guild_data["application"] except KeyError: raise InvalidContent( "content does not match a guild json from TibiaData.") guild.homepage = guild_data.get("homepage") guild.active = not guild_data.get("formation", False) if isinstance(guild_data["disbanded"], dict): guild.disband_date = parse_tibiadata_date( guild_data["disbanded"]["date"]) guild.disband_condition = disband_tibadata_regex.search( guild_data["disbanded"]["notification"]).group(1) for rank in guild_obj["members"]: rank_name = rank["rank_title"] for member in rank["characters"]: guild.members.append( GuildMember(member["name"], rank_name, member["nick"] or None, member["level"], member["vocation"], joined=parse_tibiadata_date(member["joined"]), online=member["status"] == "online")) for invited in guild_obj["invited"]: guild.invites.append( GuildInvite(invited["name"], parse_tibiadata_date(invited["invited"]))) if isinstance(guild_data["guildhall"], dict): gh = guild_data["guildhall"] guild.guildhall = GuildHouse(gh["name"], gh["world"], guild.members[0].name, parse_tibiadata_date(gh["paid"])) return guild
def from_content(cls, content, news_id=0): """ Gets a news entry by its HTML content from Tibia.com Notes ----- Since there's no way to obtain the entry's Id from the page contents, it will always be 0. A news_id can be passed to set the news_id of the resulting object. Parameters ---------- content: :class:`str` The HTML content of the page. news_id: :class:`int`, optional The news_id belonging to the content being parsed. Returns ------- :class:`News` The news article found in the page. Raises ------ InvalidContent If content is not the HTML of a news' page. """ if "(no news with id " in content: return None try: parsed_content = parse_tibiacom_content(content) # Read Information from the headline headline = parsed_content.find("div", attrs={"class": "NewsHeadline"}) img = headline.find('img') img_url = img["src"] category_name = ICON_PATTERN.search(img_url) category = try_enum(NewsCategory, category_name.group(1)) title_div = headline.find("div", attrs={"class": "NewsHeadlineText"}) title = title_div.text.replace('\xa0', ' ') date_div = headline.find("div", attrs={"class": "NewsHeadlineDate"}) date_str = date_div.text.replace('\xa0', ' ').replace('-', '').strip() date = parse_tibia_date(date_str) # Read the page's content. content_table = parsed_content.find("table") content_row = content_table.find("td") content = content_row.encode_contents().decode() thread_id = None thread_div = content_table.find("div") if thread_div: news_link = thread_div.find('a') url = urllib.parse.urlparse(news_link["href"]) query = urllib.parse.parse_qs(url.query) thread_id = int(query["threadid"][0]) return cls(news_id, title, content, date, category, thread_id=thread_id, category_icon=img_url) except AttributeError: raise InvalidContent( "content is not from the news archive section in Tibia.com")
def from_tibiadata(cls, content): """Builds a character object from a TibiaData character response. Parameters ---------- content: :class:`str` The JSON content of the response. Returns ------- :class:`Character` The character contained in the page, or None if the character doesn't exist Raises ------ InvalidContent If content is not a JSON string of the Character response.""" json_content = parse_json(content) char = cls() try: character = json_content["characters"] if "error" in character: return None character_data = character["data"] char.name = character_data["name"] char.world = character_data["world"] char.level = character_data["level"] char.achievement_points = character_data["achievement_points"] char.sex = try_enum(Sex, character_data["sex"]) char.vocation = try_enum(Vocation, character_data["vocation"]) char.residence = character_data["residence"] char.account_status = try_enum(AccountStatus, character_data["account_status"]) except KeyError: raise InvalidContent( "content does not match a character json from TibiaData.") char.former_names = character_data.get("former_names", []) if "deleted" in character_data: char.deletion_date = parse_tibiadata_datetime( character_data["deleted"]) char.married_to = character_data.get("married_to") char.former_world = character_data.get("former_world") char.position = character_data.get("Position:") if "guild" in character_data: char.guild_membership = GuildMembership( character_data["guild"]["name"], character_data["guild"]["rank"]) if "house" in character_data: house = character_data["house"] paid_until_date = parse_tibiadata_date(house["paid"]) char.houses.append( CharacterHouse(house["houseid"], house["name"], char.world, house["town"], char.name, paid_until_date)) char.comment = character_data.get("comment") if len(character_data["last_login"]) > 0: char.last_login = parse_tibiadata_datetime( character_data["last_login"][0]) for achievement in character["achievements"]: char.achievements.append( Achievement(achievement["name"], achievement["stars"])) char._parse_deaths_tibiadata(character.get("deaths", [])) for other_char in character["other_characters"]: char.other_characters.append( OtherCharacter(other_char["name"], other_char["world"], other_char["status"] == "online", other_char["status"] == "deleted")) if character["account_information"]: acc_info = character["account_information"] created = parse_tibiadata_datetime(acc_info.get("created")) loyalty_title = None if acc_info[ "loyalty_title"] == "(no title)" else acc_info["loyalty_title"] position = acc_info.get("position") char.account_information = AccountInformation( created, loyalty_title, position) return char