From 1132dbc8287387c7d7b205618437a332b9ceefeb Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 10 Jun 2019 15:30:35 -0700 Subject: [PATCH 1/8] Added parse_integer function --- tibiapy/utils.py | 23 +++++++++++++++++++++++ tibiapy/world.py | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tibiapy/utils.py b/tibiapy/utils.py index d6092539..4198f93a 100644 --- a/tibiapy/utils.py +++ b/tibiapy/utils.py @@ -10,6 +10,29 @@ TIBIA_CASH_PATTERN = re.compile(r'(\d*\.?\d*)k*$') +def parse_integer(number: str, default=0): + """Parses a string representing an integer, ignoring commas or periods. + + Parameters + ---------- + number: :class:`str` + A string representing a number. + default: :class:`int` + The default value to use if the string is not numeric. + By default, 0 is used. + + Returns + ------- + :class:`int` + The represented integer, or the default value if invalid. + """ + try: + number = re.sub(r'[,.]', '', number.strip()) + return int(number) + except ValueError: + return default + + def parse_tibia_datetime(datetime_str) -> Optional[datetime.datetime]: """Parses date and time from the format used in Tibia.com diff --git a/tibiapy/world.py b/tibiapy/world.py index 145c1b18..a56f8453 100644 --- a/tibiapy/world.py +++ b/tibiapy/world.py @@ -8,7 +8,7 @@ from tibiapy.character import OnlineCharacter from tibiapy.enums import PvpType, TransferType, WorldLocation from tibiapy.utils import parse_json, parse_tibia_datetime, parse_tibia_full_date, parse_tibiacom_content, \ - parse_tibiadata_datetime, try_date, try_datetime, try_enum + parse_tibiadata_datetime, try_date, try_datetime, try_enum, parse_integer __all__ = ( "ListedWorld", @@ -349,7 +349,7 @@ def _parse_world_info(self, world_info_table): 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 = int(m.group("count").replace(".", "")) + 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(",")] @@ -494,7 +494,7 @@ def from_content(cls, content): m = record_regexp.search(record_row.text) if not m: raise InvalidContent("content does not belong to the World Overview section in Tibia.com") - world_overview.record_count = int(m.group("count").replace(".", "")) + 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) From 78485e1dcb252157360cef17a762268cb2212719 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 10 Jun 2019 16:36:48 -0700 Subject: [PATCH 2/8] Added regular_worlds and tournament_worlds properties - Added user agent to client --- tibiapy/client.py | 5 ++++- tibiapy/world.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tibiapy/client.py b/tibiapy/client.py index 3b526935..3ad14ca3 100644 --- a/tibiapy/client.py +++ b/tibiapy/client.py @@ -37,7 +37,10 @@ def __init__(self, loop=None, session=None): self.loop.create_task(self._initialize_session()) async def _initialize_session(self): - self.session = aiohttp.ClientSession(loop=self.loop) # type: aiohttp.ClientSession + headers = { + 'User-Agent ': "Tibia.py/%s (+https://github.com/Galarzaa90/tibia.py" % tibiapy.__version__ + } + self.session = aiohttp.ClientSession(loop=self.loop, headers=headers) # type: aiohttp.ClientSession @classmethod def _handle_status(cls, status_code): diff --git a/tibiapy/world.py b/tibiapy/world.py index a56f8453..3e5867b8 100644 --- a/tibiapy/world.py +++ b/tibiapy/world.py @@ -444,6 +444,18 @@ def total_online(self): """:class:`int`: Total players online across all worlds.""" return sum(w.online_count for w in self.worlds) + @property + def tournament_worlds(self): + """:class:`list` of :class:`GuildMember`: List of tournament worlds. + + Note that tournament worlds are not listed when there are no active or upcoming tournaments.""" + return [w for w in self.worlds if w.tournament_world_type is not None] + + @property + def regular_worlds(self): + """:class:`list` of :class:`ListedWorld`: List of worlds that are not tournament worlds.""" + return [w for w in self.worlds if w.tournament_world_type is None] + @classmethod def get_url(cls): """ From d327771b5327aff7a61285f828517f3fab1a1516 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Tue, 11 Jun 2019 14:01:21 -0700 Subject: [PATCH 3/8] Various house and world improvements - Parse possible comma thousand separator in world online record - Added HouseOrder enum for the possible sorting types of houses. - Added order and status parameters to House.get_list_url and Client.fetch_world_houses to filter and order results --- docs/api.rst | 16 +++++++++++++--- tests/tests_utils.py | 1 + tibiapy/abc.py | 14 ++++++++++---- tibiapy/client.py | 16 +++++++++++----- tibiapy/enums.py | 10 ++++++++++ tibiapy/house.py | 10 +++++----- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 71e1ddbe..2e451dcd 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -18,9 +18,6 @@ Enumerations ============ Enumerations are provided for various values in order to avoid depending on strings. -.. autoclass:: HouseType - :members: - :undoc-members: .. autoclass:: AccountStatus :members: @@ -30,10 +27,18 @@ Enumerations are provided for various values in order to avoid depending on stri :members: :undoc-members: +.. autoclass:: HouseOrder + :members: + :undoc-members: + .. autoclass:: HouseStatus :members: :undoc-members: +.. autoclass:: HouseType + :members: + :undoc-members: + .. autoclass:: NewsCategory :members: :undoc-members: @@ -50,6 +55,11 @@ Enumerations are provided for various values in order to avoid depending on stri :members: :undoc-members: +.. autoclass:: TournamentWorldType + :members: + :undoc-members: + + .. autoclass:: TransferType :members: :undoc-members: diff --git a/tests/tests_utils.py b/tests/tests_utils.py index 540d7a0e..1160db65 100644 --- a/tests/tests_utils.py +++ b/tests/tests_utils.py @@ -145,6 +145,7 @@ def testTryDateTime(self): def testParseNumberWords(self): self.assertEqual(utils.parse_number_words("one"), 1) + self.assertEqual(utils.parse_number_words("no"), 0) self.assertEqual(utils.parse_number_words("..."), 0) self.assertEqual(utils.parse_number_words("twenty-one"), 21) self.assertEqual(utils.parse_number_words("one hundred two"), 102) diff --git a/tibiapy/abc.py b/tibiapy/abc.py index 9eb308a6..ebc20978 100644 --- a/tibiapy/abc.py +++ b/tibiapy/abc.py @@ -5,13 +5,13 @@ from collections import OrderedDict from enum import Enum -from tibiapy.enums import HouseType +from tibiapy.enums import HouseType, HouseStatus, HouseOrder CHARACTER_URL = "https://www.tibia.com/community/?subtopic=characters&name=%s" CHARACTER_URL_TIBIADATA = "https://api.tibiadata.com/v2/characters/%s.json" HOUSE_URL = "https://www.tibia.com/community/?subtopic=houses&page=view&houseid=%d&world=%s" HOUSE_URL_TIBIADATA = "https://api.tibiadata.com/v2/house/%s/%d.json" -HOUSE_LIST_URL = "https://www.tibia.com/community/?subtopic=houses&world=%s&town=%s&type=%s" +HOUSE_LIST_URL = "https://www.tibia.com/community/?subtopic=houses&world=%s&town=%s&type=%s&status=%s&order=%s" HOUSE_LIST_URL_TIBIADATA = "https://api.tibiadata.com/v2/houses/%s/%s/%s.json" GUILD_URL = "https://www.tibia.com/community/?subtopic=guilds&page=view&GuildName=%s" GUILD_URL_TIBIADATA = "https://api.tibiadata.com/v2/guild/%s.json" @@ -321,7 +321,8 @@ def get_url_tibiadata(cls, house_id, world): return HOUSE_URL_TIBIADATA % (world, house_id) @classmethod - def get_list_url(cls, world, town, house_type: HouseType = HouseType.HOUSE): + def get_list_url(cls, world, town, house_type: HouseType = HouseType.HOUSE, status: HouseStatus = None, + order=HouseOrder.NAME): """ Gets the URL to the house list on Tibia.com with the specified parameters. @@ -333,6 +334,10 @@ def get_list_url(cls, world, town, house_type: HouseType = HouseType.HOUSE): The name of the town. house_type: :class:`HouseType` Whether to search for houses or guildhalls. + status: :class:`HouseStatus`, optional + The house status to filter results. By default no filters will be applied. + order: :class:`HouseOrder`, optional + The ordering to use for the results. By default they are sorted by name. Returns ------- @@ -340,7 +345,8 @@ def get_list_url(cls, world, town, house_type: HouseType = HouseType.HOUSE): The URL to the list matching the parameters. """ house_type = "%ss" % house_type.value - return HOUSE_LIST_URL % (urllib.parse.quote(world), urllib.parse.quote(town), house_type) + status = "" if status is None else status.value + return HOUSE_LIST_URL % (urllib.parse.quote(world), urllib.parse.quote(town), house_type, status, order.value) @classmethod def get_list_url_tibiadata(cls, world, town, house_type: HouseType = HouseType.HOUSE): diff --git a/tibiapy/client.py b/tibiapy/client.py index 3ad14ca3..eaa3fd14 100644 --- a/tibiapy/client.py +++ b/tibiapy/client.py @@ -5,7 +5,8 @@ import tibiapy from tibiapy import Character, Guild, World, House, KillStatistics, ListedGuild, Highscores, Category, VocationFilter, \ - ListedHouse, HouseType, WorldOverview, NewsCategory, NewsType, ListedNews, News, Forbidden, NetworkError + ListedHouse, HouseType, WorldOverview, NewsCategory, NewsType, ListedNews, News, Forbidden, NetworkError, \ + HouseStatus, HouseOrder __all__ = ( "Client", @@ -44,7 +45,7 @@ async def _initialize_session(self): @classmethod def _handle_status(cls, status_code): - """Handles error status codes, raising exceptions if neccesary.""" + """Handles error status codes, raising exceptions if necessary.""" if status_code < 400: return if status_code == 403: @@ -261,7 +262,8 @@ async def fetch_world(self, name): world = World.from_content(content) return world - async def fetch_world_houses(self, world, town, house_type=HouseType.HOUSE): + async def fetch_world_houses(self, world, town, house_type=HouseType.HOUSE, status: HouseStatus = None, + order=HouseOrder.NAME): """Fetches the house list of a world and type. Parameters @@ -272,6 +274,10 @@ async def fetch_world_houses(self, world, town, house_type=HouseType.HOUSE): The name of the town. house_type: :class:`HouseType` The type of building. House by default. + status: :class:`HouseStatus`, optional + The house status to filter results. By default no filters will be applied. + order: :class:`HouseOrder`, optional + The ordering to use for the results. By default they are sorted by name. Returns ------- @@ -286,7 +292,7 @@ async def fetch_world_houses(self, world, town, house_type=HouseType.HOUSE): NetworkError If there's any connection errors during the request. """ - content = await self._get(ListedHouse.get_list_url(world, town, house_type)) + content = await self._get(ListedHouse.get_list_url(world, town, house_type, status, order)) houses = ListedHouse.list_from_content(content) return houses @@ -294,7 +300,7 @@ async def fetch_world_guilds(self, world: str): """Fetches the list of guilds in a world from Tibia.com Parameters - ---------- + ---------cl- world: :class:`str` The name of the world. diff --git a/tibiapy/enums.py b/tibiapy/enums.py index f3fa86f5..8414d90d 100644 --- a/tibiapy/enums.py +++ b/tibiapy/enums.py @@ -3,6 +3,7 @@ __all__ = ( 'AccountStatus', 'Category', + 'HouseOrder', 'HouseStatus', 'HouseType', 'NewsCategory', @@ -50,6 +51,15 @@ class Category(BaseEnum): SWORD_FIGHTING = "sword" +class HouseOrder(BaseEnum): + """The possible ordering methods for house lists in Tibia.com""" + NAME = "name" + SIZE = "size" + RENT = "rent" + BID = "bid" + AUCTION_END = "end" + + class HouseStatus(BaseEnum): """Renting statuses of a house.""" RENTED = "rented" diff --git a/tibiapy/house.py b/tibiapy/house.py index 96419bda..ec3ca896 100644 --- a/tibiapy/house.py +++ b/tibiapy/house.py @@ -174,12 +174,12 @@ def from_content(cls, content): house.id = int(id_regex.search(house.image_url).group(1)) m = bed_regex.search(beds) if m: - house.type = HouseType.GUILDHALL if m.group("type") in ["guildhall", "clanhall"] else HouseType.HOUSE - beds_word = m.group("beds") - if beds_word == "no": - house.beds = 0 + if m.group("type").lower() in ["guildhall", "clanhall"]: + house.type = HouseType.GUILDHALL else: - house.beds = parse_number_words(beds_word) + house.type = HouseType.HOUSE + beds_word = m.group("beds") + house.beds = parse_number_words(beds_word) m = info_regex.search(info) if m: From 5604073135cc0f72e1840184131d8a19bc49ea4f Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Sun, 16 Jun 2019 19:37:26 -0700 Subject: [PATCH 4/8] Added boosted creature parsing --- tests/tests_creature.py | 16 ++++++++++++++++ tibiapy/__init__.py | 1 + tibiapy/creature.py | 23 +++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/tests_creature.py create mode 100644 tibiapy/creature.py diff --git a/tests/tests_creature.py b/tests/tests_creature.py new file mode 100644 index 00000000..8ba0445f --- /dev/null +++ b/tests/tests_creature.py @@ -0,0 +1,16 @@ +import unittest + +from tests.tests_tibiapy import TestCommons +from tibiapy import BoostedCreature + + +class TestCreature(TestCommons, unittest.TestCase): + # region Tibia.com Tests + def testBoostedCreature(self): + content = self._load_resource(self.FILE_UNRELATED_SECTION) + creature = BoostedCreature.from_content(content) + + self.assertIsInstance(creature, BoostedCreature) + self.assertEqual("Skeleton Warrior", creature.name) + + # endregion diff --git a/tibiapy/__init__.py b/tibiapy/__init__.py index a550effb..d7d0432b 100644 --- a/tibiapy/__init__.py +++ b/tibiapy/__init__.py @@ -11,6 +11,7 @@ from tibiapy.news import * from tibiapy.world import * from tibiapy.client import * +from tibiapy.creature import * __version__ = '2.0.1' diff --git a/tibiapy/creature.py b/tibiapy/creature.py new file mode 100644 index 00000000..2cfb9604 --- /dev/null +++ b/tibiapy/creature.py @@ -0,0 +1,23 @@ +import bs4 + +from tibiapy.utils import parse_tibiacom_content + +__all__ = ( + "BoostedCreature", +) + +BOOSTED_ALT = "Today's boosted creature: " + + +class BoostedCreature: + def __init__(self, name, image_url): + self.name = name + self.image_url = image_url + + @classmethod + def from_content(cls, content): + 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) From 23abfb3018fbc66051c746332d1cf0813ba706a5 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 17 Jun 2019 21:28:33 -0700 Subject: [PATCH 5/8] Added documentation and unit tests for boosted creatures --- docs/api.rst | 6 +++++ tests/tests_client.py | 8 +++++- tests/tests_creature.py | 6 ++++- tibiapy/__init__.py | 2 +- tibiapy/client.py | 24 +++++++++++++++++- tibiapy/creature.py | 55 +++++++++++++++++++++++++++++++++++------ 6 files changed, 90 insertions(+), 11 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 2e451dcd..257439dc 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -83,6 +83,12 @@ Main Models The following models all contain their respective ``from_content`` methods. They all have their respective section in Tibia.com +BoostedCreature +--------------- +.. autoclass:: BoostedCreature + :members: + :inherited-members: + Character --------- .. autoclass:: Character diff --git a/tests/tests_client.py b/tests/tests_client.py index 5d10a5f3..21b3091c 100644 --- a/tests/tests_client.py +++ b/tests/tests_client.py @@ -13,7 +13,7 @@ from tests.tests_news import FILE_NEWS_LIST, FILE_NEWS_ARTICLE from tests.tests_tibiapy import TestCommons from tibiapy import Client, Character, Guild, Highscores, VocationFilter, Category, House, ListedHouse, ListedGuild, \ - KillStatistics, ListedNews, News, World, WorldOverview, Forbidden, NetworkError + KillStatistics, ListedNews, News, World, WorldOverview, Forbidden, NetworkError, BoostedCreature class TestClient(asynctest.TestCase, TestCommons): @@ -168,7 +168,13 @@ async def testFetchWorldList(self, mock): self.assertIsInstance(worlds, WorldOverview) + @aioresponses() + async def testFetchBoostedCreature(self, mock): + content = self._load_resource(self.FILE_UNRELATED_SECTION) + mock.get(News.get_list_url(), status=200, body=content) + creature = await self.client.fetch_boosted_creature() + self.assertIsInstance(creature, BoostedCreature) diff --git a/tests/tests_creature.py b/tests/tests_creature.py index 8ba0445f..e9f439ca 100644 --- a/tests/tests_creature.py +++ b/tests/tests_creature.py @@ -1,7 +1,7 @@ import unittest from tests.tests_tibiapy import TestCommons -from tibiapy import BoostedCreature +from tibiapy import BoostedCreature, InvalidContent class TestCreature(TestCommons, unittest.TestCase): @@ -13,4 +13,8 @@ def testBoostedCreature(self): self.assertIsInstance(creature, BoostedCreature) self.assertEqual("Skeleton Warrior", creature.name) + def testBoostedCreatureNotTibiaCom(self): + with self.assertRaises(InvalidContent): + BoostedCreature.from_content("

Nothing

") + # endregion diff --git a/tibiapy/__init__.py b/tibiapy/__init__.py index d7d0432b..1131698b 100644 --- a/tibiapy/__init__.py +++ b/tibiapy/__init__.py @@ -10,8 +10,8 @@ from tibiapy.kill_statistics import * from tibiapy.news import * from tibiapy.world import * -from tibiapy.client import * from tibiapy.creature import * +from tibiapy.client import * __version__ = '2.0.1' diff --git a/tibiapy/client.py b/tibiapy/client.py index eaa3fd14..b3824a49 100644 --- a/tibiapy/client.py +++ b/tibiapy/client.py @@ -6,7 +6,7 @@ import tibiapy from tibiapy import Character, Guild, World, House, KillStatistics, ListedGuild, Highscores, Category, VocationFilter, \ ListedHouse, HouseType, WorldOverview, NewsCategory, NewsType, ListedNews, News, Forbidden, NetworkError, \ - HouseStatus, HouseOrder + HouseStatus, HouseOrder, BoostedCreature __all__ = ( "Client", @@ -103,6 +103,28 @@ async def _post(self, url, data): except aiohttp.ClientError as e: raise NetworkError("aiohttp.ClientError: %s" % e, e) + async def fetch_boosted_creature(self): + """Fetches today's boosted creature. + + .. versionadded:: 2.1.0 + + Returns + ------- + :class:`BoostedCreature` + The boosted creature of the day. + + Raises + ------ + Forbidden + If a 403 Forbidden error was returned. + This usually means that Tibia.com is rate-limiting the client because of too many requests. + NetworkError + If there's any connection errors during the request. + """ + content = await self._get(News.get_list_url()) + boosted_creature = BoostedCreature.from_content(content) + return boosted_creature + async def fetch_character(self, name): """Fetches a character by its name from Tibia.com diff --git a/tibiapy/creature.py b/tibiapy/creature.py index 2cfb9604..04a92fee 100644 --- a/tibiapy/creature.py +++ b/tibiapy/creature.py @@ -1,6 +1,6 @@ import bs4 -from tibiapy.utils import parse_tibiacom_content +from tibiapy import abc, InvalidContent __all__ = ( "BoostedCreature", @@ -9,15 +9,56 @@ BOOSTED_ALT = "Today's boosted creature: " -class BoostedCreature: +class BoostedCreature(abc.Serializable): + """Represents a boosted creature entry. + + This creature changes every server save and applies to all Game Worlds. + Boosted creatures yield twice the amount of experience points, carry more loot and respawn at a faster rate. + + Attributes + ---------- + name: :class:`str` + The name of the boosted creature. + image_url: :class:`str` + An URL containing the boosted creature's image. + """ + __slots__ = ( + "name", + "image_url", + ) + def __init__(self, name, image_url): self.name = name self.image_url = image_url @classmethod def from_content(cls, content): - 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) + """ + 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") + From 1907c36e2785aa63c0b3ccaa1e0aa67b38af45d1 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 17 Jun 2019 21:37:54 -0700 Subject: [PATCH 6/8] Add tournament information --- .../resources/tournaments/tournament_information_running.txt | 3 +++ .../resources/tournaments/tournament_leaderboards_running.txt | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 tests/resources/tournaments/tournament_information_running.txt create mode 100644 tests/resources/tournaments/tournament_leaderboards_running.txt diff --git a/tests/resources/tournaments/tournament_information_running.txt b/tests/resources/tournaments/tournament_information_running.txt new file mode 100644 index 00000000..5e667ea1 --- /dev/null +++ b/tests/resources/tournaments/tournament_information_running.txt @@ -0,0 +1,3 @@ +
+

Prove your Tibia skills!

Prove your skills by participating in Tournaments! Earn great rewards, such as Tournament Coins, Tibia Coins or Tournament Ticket Voucher by participating in a Tournament.

Fight hard and place yourself on top of the Tournament Leaderboard!

Your Tournament Information

Log in to see your personal Tournament information!


Tournament Details
Phase:running
Start Date:Jun 17 2019, 10:00:00 CEST
End Date:Jun 24 2019, 10:00:00 CEST
Worlds:Endebra, Endera, Endura, Velocera, Velocibra, Velocita
Rule Set:
PvP Type:Open PvP
Daily Tournament Playtime:02:00:00
Death Penalty Modifier:1.00x
XP Multiplier:7.00x
Skill Multiplier:10.00x
Spawn Rate Multiplier:1.00x
Loot Probability:1.00x
Rent Percentage:10%
House Auction Durations:0
Score Set:
Level Gain/Loss:+/- 100
Charm Point Multiplier (rare or lower creatures):Charm Points * 20
Reward Set:
Rank 1:3,0001,7501
Rank 2:2,0001,7501
Rank 3:1,0001,7501
Rank 4:7001,7501
Rank 5:5001,7501
Rank 6:4001,2501
Rank 7:3501,2501
Rank 8:3001,2501
Rank 9:2501,2501
Rank 10:2001,2501
Rank 11-15:1,0001
Rank 16-20:8001
Rank 21-25:7001
Rank 26-50:6001
Rank 51-75:500
Rank 76-100:450
Rank 101-125:350
Rank 126-150:300
Rank 151-175:250
Rank 176-200:150
Rank 201-99999:100

+ \ No newline at end of file diff --git a/tests/resources/tournaments/tournament_leaderboards_running.txt b/tests/resources/tournaments/tournament_leaderboards_running.txt new file mode 100644 index 00000000..37511b79 --- /dev/null +++ b/tests/resources/tournaments/tournament_leaderboards_running.txt @@ -0,0 +1,4 @@ +
+
Tournament Leaderboards
Here you can check the Tournament ranking of single characters on the single Tournament worlds as well as of the various Tournament cycles.

check this box to automatically refresh the leaderboard each minute
RankCharacterVocationScore
1. (+18) Himanentv TwitchSorcerer1,900
2. (+42) Mirosmar Barril DobradoSorcerer1,800
3. (+40) CaookDruid1,700
4. (+32) Doutor AurelioDruid1,700
5. (+29) Teus inSorcerer1,700
6. (+29) KywensKnight1,700
7. (+30) Rubinho OtitanPaladin1,700
8. (-4) Acid FujiDruid1,700
9. (+2) Rodriguez ChampionshipDruid1,600
10. (+30) MutsipSorcerer1,500
11. (+19) Lex Du GuesclinPaladin1,500
12. (+29) OpaladinPaladin1,400
13. (-12) Bacana TornezeiroSorcerer1,400
14. (+4) Falriah NathPaladin1,400
15. (+12) Pi KaSorcerer1,400
16. (-14) Esquilo Try HardDruid1,400
17. (+25) Algoz KeniKnight1,400
18. (+28) Nyderene IsunaKnight1,300
19. (+3) Dudric the GladiatorKnight1,300
20. (-8) Abitoxi EndebrandoSorcerer1,300
21. (-8) Druida Do VerdolaDruid1,300
22. (+29) IiePaladin1,200
23. (+33) Cohendo pro TrofeuPaladin1,200
24. (+30) MalestrixzsSorcerer1,200
25. (+28) Cidbom PainhoDruid1,200
26. (+5) Gaspar TournamentSorcerer1,200
27. (+28) Leo CluelessKnight1,200
28. (-8) Legendary XulodueKnight1,200
29. (+19) EsveinDruid1,100
30. (+19) AzaragothPaladin1,100
31. (-14) TranythzsSorcerer1,100
32. (-3) Solin SolinKnight1,100
33. (+14) AlestriderPaladin1,100
34. (+18) Leturk TiltDruid1,100
35. (-7) Andy el locoPaladin1,000
36. (+14) LanzadarPaladin1,000
37. (+2) Dive into GrindnessKnight1,000
38. (-15) Ramma AtenendyPaladin1,000
39. (-31) YadretSorcerer900
40. (-33) MarchelitoKnight900
41. (-25) MuufleszszKnight900
42. (-9) DyuzPaladin800
43. (-34) Menu MasSorcerer800
44. (-39) Zuke On FirePaladin800
45. (-35) Prion SainPaladin800
46. (-1) Wheels ChairKnight800
47. (-33) Galiazzi VanguardKnight800
48. (-24) Sonikast LavardKnight800
49. (-34) Paladino DasfrechaPaladin800
50. (-29) Portal GuyKnight800
51. (-26) Rock DirxKnight700
52. (-26) Ande PhindDruid600
53. (+6) ArrakiszPaladin600
54. (+6) Nervoso PoSorcerer600
55. (-17) Rata De TorneioPaladin600
56. (+2) ShionszzDruid600
57. (-25) Bernardo BurnquistKnight600
58. (-52) DaluzzzKnight500
59. (+3) DbrazzKnight300
60. (+3) Niix The BrutoKnight300
61. (-58) Lavawall cos PesKnight100
62. (-5) Crazy Of TournamentKnight0
63. (-2) Rubini TournamentSorcerer0
» Pages: 1
» Results: 63
+ + \ No newline at end of file From e1c1f82b49bcb1375c3ac775f0546e2249b0f518 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 17 Jun 2019 21:41:07 -0700 Subject: [PATCH 7/8] Updated changelog --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b95bd0e4..a2f4d71d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,14 @@ Changelog Due to this library relying on external content, older versions are not guaranteed to work. Try to always use the latest version. +.. _v2.1.0: + +2.1.0 (2019-06-17) +================== + +- Added ways to sort and filter House list results like in Tibia.com. +- Added support to get the Boosted Creature of the day. + .. _v2.0.1: 2.0.1 (2019-06-04) From be5099fa72aa86752aed152531880d6742231df3 Mon Sep 17 00:00:00 2001 From: Allan Galarza Date: Mon, 17 Jun 2019 21:56:24 -0700 Subject: [PATCH 8/8] Added missing covered code --- tests/tests_highscores.py | 3 +++ tests/tests_utils.py | 16 ++++++++++++++++ tests/tests_world.py | 3 +++ tibiapy/__init__.py | 2 +- tibiapy/creature.py | 3 +++ tibiapy/highscores.py | 3 +-- tibiapy/world.py | 2 -- 7 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/tests_highscores.py b/tests/tests_highscores.py index ecdf6d58..66ecab40 100644 --- a/tests/tests_highscores.py +++ b/tests/tests_highscores.py @@ -98,6 +98,9 @@ def testHighscoresTibiaData(self): self.assertEqual(highscores.category, Category.AXE_FIGHTING) self.assertEqual(highscores.results_count, 300) + self.assertEqual(highscores.url_tibiadata, + Highscores.get_url_tibiadata(highscores.world, highscores.category, highscores.vocation)) + for entry in highscores.entries: self.assertIsInstance(entry, HighscoresEntry) self.assertIsInstance(entry.name, str) diff --git a/tests/tests_utils.py b/tests/tests_utils.py index 1160db65..780ddf61 100644 --- a/tests/tests_utils.py +++ b/tests/tests_utils.py @@ -5,6 +5,7 @@ from tests.tests_tibiapy import TestCommons from tibiapy import enums, utils +from tibiapy.utils import parse_integer, parse_tibia_money TIBIA_DATETIME_CEST = "Jul 10 2018, 07:13:32 CEST" TIBIA_DATETIME_CET = "Jan 10 2018, 07:13:32 CET" @@ -161,3 +162,18 @@ def testEnumStr(self): self.assertEqual(enums.VocationFilter.from_name("royal paladin"), enums.VocationFilter.PALADINS) self.assertEqual(enums.VocationFilter.from_name("unknown"), enums.VocationFilter.ALL) self.assertIsNone(enums.VocationFilter.from_name("unknown", False)) + + def testParseTibiaMoney(self): + self.assertEqual(1000, parse_tibia_money("1k")) + self.assertEqual(5000000, parse_tibia_money("5kk")) + self.assertEqual(2500, parse_tibia_money("2.5k")) + self.assertEqual(50, parse_tibia_money("50")) + with self.assertRaises(ValueError): + parse_tibia_money("abc") + + def testParseInteger(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)) diff --git a/tests/tests_world.py b/tests/tests_world.py index c83cdfd6..d473f9d7 100644 --- a/tests/tests_world.py +++ b/tests/tests_world.py @@ -111,6 +111,8 @@ def testWorldOverview(self): self.assertGreater(world_overview.total_online, 0) self.assertIsNotNone(world_overview.record_date) self.assertIsNotNone(world_overview.record_count) + self.assertEqual(len(world_overview.regular_worlds), 65) + self.assertEqual(len(world_overview.tournament_worlds), 6) worlds = ListedWorld.list_from_content(content) self.assertEqual(len(world_overview.worlds), len(worlds)) @@ -206,6 +208,7 @@ def testWorldOverviewTibiaData(self): self.assertIsInstance(world_overview, WorldOverview) self.assertEqual(WorldOverview.get_url(), ListedWorld.get_list_url()) + self.assertEqual(WorldOverview.get_url_tibiadata(), ListedWorld.get_list_url_tibiadata()) self.assertGreater(sum(w.online_count for w in world_overview.worlds), 0) self.assertIsInstance(world_overview.worlds[0], ListedWorld) self.assertIsInstance(world_overview.worlds[0].pvp_type, PvpType) diff --git a/tibiapy/__init__.py b/tibiapy/__init__.py index 1131698b..8d337824 100644 --- a/tibiapy/__init__.py +++ b/tibiapy/__init__.py @@ -13,7 +13,7 @@ from tibiapy.creature import * from tibiapy.client import * -__version__ = '2.0.1' +__version__ = '2.1.0' from logging import NullHandler diff --git a/tibiapy/creature.py b/tibiapy/creature.py index 04a92fee..1d5d7e65 100644 --- a/tibiapy/creature.py +++ b/tibiapy/creature.py @@ -31,6 +31,9 @@ def __init__(self, name, image_url): self.name = name self.image_url = image_url + def __repr__(self): + return "<{0.__class__.__name__} name={0.name!r} image_url={0.image_url!r}>".format(self) + @classmethod def from_content(cls, content): """ diff --git a/tibiapy/highscores.py b/tibiapy/highscores.py index b2838657..502edbe2 100644 --- a/tibiapy/highscores.py +++ b/tibiapy/highscores.py @@ -187,8 +187,7 @@ def from_tibiadata(cls, content, vocation=None): highscores.results_count = len(highscores.entries) except KeyError: raise InvalidContent("content is not a TibiaData highscores response.") - if isinstance(vocation, VocationFilter): - highscores.vocation = vocation + highscores.vocation = vocation or VocationFilter.ALL return highscores @classmethod diff --git a/tibiapy/world.py b/tibiapy/world.py index 3e5867b8..17bd48c9 100644 --- a/tibiapy/world.py +++ b/tibiapy/world.py @@ -504,8 +504,6 @@ def from_content(cls, content): try: record_row, *rows = parsed_content.find_all("tr") m = record_regexp.search(record_row.text) - if not m: - raise InvalidContent("content does not belong to the World Overview section in Tibia.com") world_overview.record_count = parse_integer(m.group("count")) world_overview.record_date = parse_tibia_datetime(m.group("date")) world_rows = rows