Exemple #1
0
    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)
Exemple #2
0
    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")
Exemple #3
0
    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)
Exemple #4
0
    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")
Exemple #5
0
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.")
Exemple #6
0
    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)
Exemple #7
0
    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
Exemple #8
0
    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
Exemple #9
0
    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")
Exemple #10
0
    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.")
Exemple #11
0
    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
Exemple #12
0
    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)
Exemple #13
0
    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)
Exemple #14
0
    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
Exemple #15
0
    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)
Exemple #16
0
    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)
Exemple #17
0
    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
Exemple #18
0
    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
Exemple #19
0
    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
Exemple #20
0
    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)
Exemple #21
0
    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
Exemple #22
0
    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
Exemple #23
0
    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
Exemple #24
0
    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)
Exemple #26
0
    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
Exemple #27
0
    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")
Exemple #28
0
    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