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:`News` The boosted article shown. 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 from_content(cls, content): """Parses 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, html_class="TableContentAndRightShadow") world_overview = WorldOverview() try: record_row, *rows = parsed_content.find_all("tr") m = record_regexp.search(record_row.text) world_overview.record_count = parse_integer(m.group("count")) world_overview.record_date = parse_tibia_datetime(m.group("date")) world_rows = rows world_overview._parse_worlds(world_rows) return world_overview except (AttributeError, KeyError, ValueError): raise InvalidContent( "content does not belong to the World Overview section in Tibia.com" )
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_tibiadata(cls, content, vocation=None): """Builds a highscores object from a TibiaData highscores response. Notes ----- Since TibiaData.com's response doesn't contain any indication of the vocation filter applied, :py:attr:`vocation` can't be determined from the response, so the attribute must be assigned manually. If the attribute is known, it can be passed for it to be assigned in this method. Parameters ---------- content: :class:`str` The JSON content of the response. vocation: :class:`VocationFilter`, optional The vocation filter to assign to the results. Note that this won't affect the parsing. Returns ------- :class:`Highscores` The highscores contained in the page, or None if the content is for the highscores of a nonexistent world. Raises ------ InvalidContent If content is not a JSON string of the highscores response.""" json_content = parse_json(content) try: highscores_json = json_content["highscores"] if "error" in highscores_json["data"]: return None world = highscores_json["world"] category = highscores_json["type"] highscores = cls(world, category) for entry in highscores_json["data"]: value_key = "level" if highscores.category in [Category.ACHIEVEMENTS, Category.LOYALTY_POINTS, Category.EXPERIENCE]: value_key = "points" if highscores.category == Category.EXPERIENCE: highscores.entries.append(ExpHighscoresEntry(entry["name"], entry["rank"], entry["voc"], entry[value_key], entry["level"])) elif highscores.category == Category.LOYALTY_POINTS: highscores.entries.append(LoyaltyHighscoresEntry(entry["name"], entry["rank"], entry["voc"], entry[value_key], entry["title"])) else: highscores.entries.append(HighscoresEntry(entry["name"], entry["rank"], entry["voc"], entry[value_key])) highscores.results_count = len(highscores.entries) except KeyError: raise InvalidContent("content is not a TibiaData highscores response.") highscores.vocation = vocation or VocationFilter.ALL return highscores
def from_tibiadata(cls, content): """Parses a TibiaData.com response into a :class:`World` Parameters ---------- content: :class:`str` The raw JSON content from TibiaData Returns ------- :class:`World` The World described in the page, or ``None``. Raises ------ InvalidContent If the provided content is not a TibiaData world response. """ json_data = parse_json(content) try: world_data = json_data["world"] world_info = world_data["world_information"] world = cls(world_info["name"]) if "location" not in world_info: return None world.online_count = world_info["players_online"] world.status = "Online" if world.online_count > 0 else "Offline" world.record_count = world_info["online_record"]["players"] world.record_date = parse_tibiadata_datetime( world_info["online_record"]["date"]) world.creation_date = world_info["creation_date"] world.location = try_enum(WorldLocation, world_info["location"]) world.pvp_type = try_enum(PvpType, world_info["pvp_type"]) world.transfer_type = try_enum(TransferType, world_info.get("transfer_type"), TransferType.REGULAR) world.premium_only = "premium_type" in world_info world.world_quest_titles = world_info.get("world_quest_titles", []) world._parse_battleye_status(world_info.get("battleye_status", "")) world.experimental = world_info.get("Game World Type:", "Regular") != "Regular" for player in world_data.get("players_online", []): world.online_players.append( OnlineCharacter(player["name"], world.name, player["level"], player["vocation"])) return world except KeyError: raise InvalidContent( "content is not a world json response from TibiaData")
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 25 entries per page, so in order to obtain the full highscores, all 12 pages must be parsed 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 == "": return None category = 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") if entries is None: return None _, header, *rows = entries info_row = rows.pop() highscores.results_count = int(results_pattern.search(info_row.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 from_tibiadata(cls, content): """Parses the content of the World Overview section from TibiaData.com into an object of this class. Notes ----- Due to TibiaData limitations, :py:attr:`record_count` and :py:attr:`record_date` are unavailable object. Additionally, the listed worlds in :py:attr:`worlds` lack some information when obtained from TibiaData. The following attributes are unavailable: - :py:attr:`ListedWorld.status` is always ``Online``. - :py:attr:`ListedWorld.battleye_protected` is always ``False`` - :py:attr:`ListedWorld.battleye_date` is always ``None``. Parameters ---------- content: :class:`str` The JSON response of the worlds section in TibiaData.com Returns ------- :class:`WorldOverview` An instance of this class containing only the available worlds. Raises ------ InvalidContent If the provided content is the json content of the world section in TibiaData.com """ json_data = parse_json(content) try: worlds_json = json_data["worlds"]["allworlds"] world_overview = cls() for world_json in worlds_json: world = ListedWorld(world_json["name"], world_json["location"], world_json["worldtype"]) world._parse_additional_info(world_json["additional"]) world.online_count = world_json["online"] world_overview.worlds.append(world) return world_overview except KeyError: raise InvalidContent( "content is not a worlds json response from TibiaData.com.")
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, 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")