Skip to content

Commit

Permalink
Merge pull request #15 from Galarzaa90/dev
Browse files Browse the repository at this point in the history
v2.1.0
  • Loading branch information
Galarzaa90 authored Jun 18, 2019
2 parents 9d16deb + be5099f commit 7ebadf4
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 27 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 19 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -73,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
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion tests/tests_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)



20 changes: 20 additions & 0 deletions tests/tests_creature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import unittest

from tests.tests_tibiapy import TestCommons
from tibiapy import BoostedCreature, InvalidContent


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)

def testBoostedCreatureNotTibiaCom(self):
with self.assertRaises(InvalidContent):
BoostedCreature.from_content("<html><div><p>Nothing</p></div></html>")

# endregion
3 changes: 3 additions & 0 deletions tests/tests_highscores.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 17 additions & 0 deletions tests/tests_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -145,6 +146,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)
Expand All @@ -160,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))
3 changes: 3 additions & 0 deletions tests/tests_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion tibiapy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
from tibiapy.kill_statistics import *
from tibiapy.news import *
from tibiapy.world import *
from tibiapy.creature import *
from tibiapy.client import *

__version__ = '2.0.1'
__version__ = '2.1.0'

from logging import NullHandler

Expand Down
14 changes: 10 additions & 4 deletions tibiapy/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand All @@ -333,14 +334,19 @@ 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
-------
:class:`str`
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):
Expand Down
43 changes: 37 additions & 6 deletions tibiapy/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, BoostedCreature

__all__ = (
"Client",
Expand Down Expand Up @@ -37,11 +38,14 @@ 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):
"""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:
Expand Down Expand Up @@ -99,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
Expand Down Expand Up @@ -258,7 +284,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
Expand All @@ -269,6 +296,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
-------
Expand All @@ -283,15 +314,15 @@ 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

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.
Expand Down
67 changes: 67 additions & 0 deletions tibiapy/creature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import bs4

from tibiapy import abc, InvalidContent

__all__ = (
"BoostedCreature",
)

BOOSTED_ALT = "Today's boosted creature: "


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

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):
"""
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")

Loading

0 comments on commit 7ebadf4

Please sign in to comment.