def _parse_world_info(self, world_info_table): """ Parses the World Information table from Tibia.com and adds the found values to the object. Parameters ---------- world_info_table: :class:`list`[:class:`bs4.Tag`] """ world_info = {} for row in world_info_table: cols_raw = row.find_all('td') cols = [ele.text.strip() for ele in cols_raw] field, value = cols field = field.replace("\xa0", "_").replace(" ", "_").replace(":", "").lower() value = value.replace("\xa0", " ") world_info[field] = value try: self.online_count = parse_integer(world_info.pop("players_online")) except KeyError: self.online_count = 0 self.location = try_enum(WorldLocation, world_info.pop("location")) self.pvp_type = try_enum(PvpType, world_info.pop("pvp_type")) self.transfer_type = try_enum(TransferType, world_info.pop("transfer_type", None), TransferType.REGULAR) m = record_regexp.match(world_info.pop("online_record")) if m: self.record_count = parse_integer(m.group("count")) self.record_date = parse_tibia_datetime(m.group("date")) if "world_quest_titles" in world_info: self.world_quest_titles = [ q.strip() for q in world_info.pop("world_quest_titles").split(",") ] if self.world_quest_titles and "currently has no title" in self.world_quest_titles[ 0]: self.world_quest_titles = [] self.experimental = world_info.pop("game_world_type", None) == "Experimental" self.tournament_world_type = try_enum( TournamentWorldType, world_info.pop("tournament_world_type", None)) self._parse_battleye_status(world_info.pop("battleye_status")) self.premium_only = "premium_type" in world_info month, year = world_info.pop("creation_date").split("/") month = int(month) year = int(year) if year > 90: year += 1900 else: year += 2000 self.creation_date = "%d-%02d" % (year, month) for k, v in world_info.items(): try: setattr(self, k, v) except AttributeError: pass
def _parse_rewards_column(cls, column, entry): """Parses a column from the tournament's reward section. Parameters ---------- column: :class:`bs4.BeautifulSoup` The parsed content of the column. entry: :class:`RewardEntry` The reward entry where the data will be stored to. """ col_str = str(column) img = column.find('img') if img and "tibiacoin" in img["src"]: entry.tibia_coins = parse_integer(column.text) if img and "tournamentcoin" in img["src"]: entry.tournament_coins = parse_integer(column.text) if img and "tournamentvoucher" in img["src"]: entry.tournament_ticker_voucher = parse_integer(column.text) if img and "trophy" in img["src"]: m = CUP_PATTERN.search(col_str) if m: entry.cup = m.group(1) m = DEED_PATTERN.search(col_str) if m: entry.deed = m.group(1) if img and "reward" in img["src"]: span = column.find('span', attrs={"class": "HelperDivIndicator"}) mouse_over = span["onmouseover"] title, popup = parse_popup(mouse_over) label = popup.find('div', attrs={'class': 'ItemOverLabel'}) entry.other_rewards = label.text.strip()
def _parse_worlds(self, world_rows, tournament=False): """Parse the world columns and adds the results to :py:attr:`worlds`. Parameters ---------- world_rows: :class:`list` of :class:`bs4.Tag` A list containing the rows of each world. tournament: :class:`bool` Whether these are tournament worlds or not. """ for world_row in world_rows: cols = world_row.find_all("td") name = cols[0].text.strip() status = "Online" online = parse_integer(cols[1].text.strip(), None) if online is None: online = 0 status = "Offline" location = cols[2].text.replace("\u00a0", " ").strip() pvp = cols[3].text.strip() world = WorldEntry(name, location, pvp, online_count=online, status=status) # Check Battleye icon to get information battleye_icon = cols[4].find("span", attrs={"class": "HelperDivIndicator"}) if battleye_icon is not None: m = battleye_regexp.search(battleye_icon["onmouseover"]) if m: world.battleye_date = parse_tibia_full_date(m.group(1)) world.battleye_type = BattlEyeType.PROTECTED if world.battleye_date else BattlEyeType.INITIALLY_PROTECTED additional_info = cols[5].text.strip() world._parse_additional_info(additional_info, tournament) self.worlds.append(world)
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 _parse_leaderboard_entries(self, ranking_table): """Parses the leaderboards' entries. Parameters ---------- ranking_table: :class:`bs4.BeautifulSoup` The table containing the rankings. """ ranking_table_content = ranking_table.find("table", attrs={"class": "TableContent"}) header, *rows = ranking_table_content.find_all('tr') entries = [] for row in rows: raw_columns = row.find_all("td") if len(raw_columns) != 4: break cols = [c.text.strip() for c in raw_columns] rank_and_change, character, vocation, score = cols m = RANK_PATTERN.search(rank_and_change) rank = int(m.group(1)) change = int(m.group(2)) voc = try_enum(Vocation, vocation) score = parse_integer(score, 0) entries.append(LeaderboardEntry(rank=rank, change=change, name=character, vocation=voc, score=score)) # Results footer small = ranking_table.find("small") if small: pagination_text = small.text results_str = RESULTS_PATTERN.search(pagination_text) self.results_count = int(results_str.group(1)) self.entries = entries
def _parse_tournament_scores(self, table): """Parses the tournament scores table. Parameters ---------- table: :class:`bs4.BeautifulSoup` The parsed table containing the tournament score set. """ creatures = {} rows = table.find_all('tr') rules = {} for row in rows[1:]: cols_raw = row.find_all('td') cols = [ele.text.strip() for ele in cols_raw] field, value, *_ = cols icon = cols_raw[2].find("span") field = field.replace("\xa0", "_").replace(" ", "_").replace(":", "").replace("/", "_").lower() value = re.sub(r'[^-0-9]', '', value.replace("+/-", "")) if not icon: creatures[field.replace("_", " ")] = int(value) else: rules[field] = parse_integer(value) if "creature_kills" in rules: rules["creature_kills"] = creatures self.score_set = ScoreSet(**rules)
def _parse_entries_table(self, table): """Parse the table containing the highscore entries. Parameters ---------- table: :class:`bs4.Tag` The table containing the entries. """ entries = table.find_all("tr") if entries is None: return _, 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: self.page = next((x for x in range(1, listed_pages[-1] + 1) if x not in listed_pages), listed_pages[-1] + 1) self.total_pages = max(int(page_links[-1].text), self.page) self.results_count = parse_integer(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 if len(cols_raw) <= 2: break self._parse_entry(cols_raw)
def _parse_filters_table(self, form): """ Parse the filters table found in a highscores page. Parameters ---------- form: :class:`bs4.Tag` The table containing the filters. """ data = parse_form_data(form, include_options=True) self.world = data["world"] if data.get("world") else None self.battleye_filter = try_enum(BattlEyeHighscoresFilter, parse_integer(data.get("beprotection"), None)) self.category = try_enum(Category, parse_integer(data.get("category"), None)) self.vocation = try_enum(VocationFilter, parse_integer(data.get("profession"), None), VocationFilter.ALL) checkboxes = form.find_all("input", {"type": "checkbox", "checked": "checked"}) values = [int(c["value"]) for c in checkboxes] self.pvp_types_filter = [try_enum(PvpTypeFilter, v) for v in values] self.available_words = [v for v in data["__options__"]["world"].values() if v]
def _parse_spells_table(cls, identifier, spell_table): """Parse the table containing spell information. Parameters ---------- identifier: :class:`str` The identifier of the spell. spell_table: :class:`bs4.Tag` The table containing the spell information. Returns ------- :class:`Spell` The spell described in the table. """ attrs = cls._parse_table_attributes(spell_table) spell = cls(identifier, attrs["name"], attrs["formula"], premium="yes" in attrs["premium"], exp_level=int(attrs["exp_lvl"])) spell.vocations = [s.strip() for s in attrs["vocation"].split(",")] spell.cities = [s.strip() for s in attrs["city"].split(",")] m = group_pattern.match(attrs["group"]) groups = m.groupdict() spell.group = try_enum(SpellGroup, groups.get("group")) spell.group_secondary = groups.get("secondary") m = cooldown_pattern.match(attrs["cooldown"]) cooldowns = m.groupdict() spell.cooldown = int(cooldowns["cooldown"]) spell.cooldown_group = int(cooldowns["group_cooldown"]) spell.cooldown_group_secondary = parse_integer( cooldowns.get("secondary_group_cooldown"), None) spell.spell_type = try_enum(SpellType, attrs["type"]) spell.soul_points = parse_integer(attrs.get("soul_points"), None) spell.mana = parse_integer(attrs.get("mana"), None) spell.amount = parse_integer(attrs.get("amount"), None) spell.price = parse_integer(attrs.get("price"), 0) spell.magic_type = attrs.get("magic_type") return spell
def _parse_rune_table(cls, table): """Parse the rune information table. Parameters ---------- table: :class:`bs4.Tag` The table containing the rune information. Returns ------- :class:`Rune` The rune described in the table. """ attrs = cls._parse_table_attributes(table) rune = Rune(name=attrs["name"], group=try_enum(SpellGroup, attrs["group"])) rune.vocations = [v.strip() for v in attrs["vocation"].split(",")] rune.magic_type = attrs.get("magic_type") rune.magic_level = parse_integer(attrs.get("mag_lvl"), 0) rune.exp_level = parse_integer(attrs.get("exp_lvl"), 0) rune.mana = parse_integer(attrs.get("mana"), None) return rune
def _parse_worlds(self, world_rows): """Parses the world columns and adds the results to :py:attr:`worlds`. Parameters ---------- world_rows: :class:`list` of :class:`bs4.Tag` A list containing the rows of each world. """ tournament = False for world_row in world_rows: cols = world_row.find_all("td") name = cols[0].text.strip() status = "Online" if len(cols) == 1 and name == "Tournament Worlds": tournament = True continue elif len(cols) == 1 and name == "Regular Worlds": tournament = False continue elif name == "World": continue try: online = parse_integer(cols[1].text.strip()) except ValueError: online = 0 status = "Offline" location = cols[2].text.replace("\u00a0", " ").strip() pvp = cols[3].text.strip() world = ListedWorld(name, location, pvp, online_count=online, status=status) # Check Battleye icon to get information battleye_icon = cols[4].find("span", attrs={"class": "HelperDivIndicator"}) if battleye_icon is not None: world.battleye_protected = True m = battleye_regexp.search(battleye_icon["onmouseover"]) if m: world.battleye_date = parse_tibia_full_date(m.group(1)) additional_info = cols[5].text.strip() world._parse_additional_info(additional_info, tournament) self.worlds.append(world)
def test_parse_integer(self): self.assertEqual(1450, parse_integer("1.450")) self.assertEqual(1110, parse_integer("1,110")) self.assertEqual(15, parse_integer("15")) self.assertEqual(0, parse_integer("abc")) self.assertEqual(-1, parse_integer("abc", -1))
def from_content(cls, content): """Parse the content of the spells section. Parameters ----------- content: :class:`str` The HTML content of the page. Returns ---------- :class:`SpellsSection` The spells contained and the filtering information. Raises ------ InvalidContent If content is not the HTML of the spells section. """ try: parsed_content = parse_tibiacom_content(content) table_content_container = parsed_content.find( "div", attrs={"class": "InnerTableContainer"}) spells_table = table_content_container.find( "table", class_=lambda t: t != "TableContent") spell_rows = spells_table.find_all( "tr", {'bgcolor': ["#D4C0A1", "#F1E0C6"]}) spells_section = cls() for row in spell_rows: columns = row.find_all("td") if len(columns) != 7: continue spell_link = columns[0].find("a") url = urllib.parse.urlparse(spell_link["href"]) query = urllib.parse.parse_qs(url.query) cols_text = [c.text for c in columns] identifier = query["spell"][0] match = spell_name.findall(cols_text[0]) name, words = match[0] group = try_enum(SpellGroup, cols_text[1]) spell_type = try_enum(SpellType, cols_text[2]) level = int(cols_text[3]) mana = parse_integer(cols_text[4], None) price = parse_integer(cols_text[5], 0) premium = "yes" in cols_text[6] spell = SpellEntry(name=name.strip(), words=words.strip(), spell_type=spell_type, level=level, group=group, mana=mana, premium=premium, price=price, identifier=identifier) spells_section.entries.append(spell) form = parsed_content.find("form") data = parse_form_data(form) spells_section.vocation = try_enum(VocationSpellFilter, data["vocation"]) spells_section.group = try_enum(SpellGroup, data["group"]) spells_section.premium = try_enum(SpellGroup, data["group"]) spells_section.spell_type = try_enum(SpellType, data["type"]) spells_section.sort_by = try_enum(SpellSorting, data["sort"]) spells_section.premium = "yes" in data["premium"] if data[ "premium"] else None return spells_section except (AttributeError, TypeError) as e: raise errors.InvalidContent( "content does not belong to the Spells section", e)