"""Base classes shared by various models."""
from __future__ import annotations
import abc
import datetime
import enum
import json
from collections import OrderedDict
from typing import Callable, TYPE_CHECKING
from tibiapy.utils import get_tibia_url
if TYPE_CHECKING:
from tibiapy import PvpType, WorldLocation
[docs]class Serializable:
"""Contains methods to make a class convertible to JSON.
Only attributes defined in ``__slots__`` will be serialized.
.. note::
| There's no way to convert JSON strings back to their original object.
| Attempting to do so may result in data loss.
"""
_serializable_properties = ()
""":class:`tuple` of :class:`str`: Additional properties to serialize."""
@classmethod
def __slots_inherited__(cls):
slots = []
for base in cls.__bases__:
try:
# noinspection PyUnresolvedReferences
slots.extend(base.__slots_inherited__())
except AttributeError:
continue
slots.extend(getattr(cls, "__slots__", []))
slots.extend(getattr(cls, "_serializable_properties", []))
return tuple(OrderedDict.fromkeys(slots))
def keys(self):
return list(self.__slots_inherited__())
def __getitem__(self, item):
if item in self.keys():
try:
return getattr(self, item)
except AttributeError:
return None
else:
raise KeyError(item)
def __setitem__(self, key, value):
if key in self.keys():
setattr(self, key, value)
else:
raise KeyError(key)
@staticmethod
def _try_dict(obj):
try:
if isinstance(obj, datetime.datetime):
return obj.isoformat()
if isinstance(obj, datetime.timedelta):
return int(obj.total_seconds())
if isinstance(obj, enum.Flag):
return [str(i) for i in obj]
if isinstance(obj, enum.Enum):
return str(obj)
return {k: v for k, v in dict(obj).items() if v is not None}
except TypeError:
return str(obj)
[docs] def to_json(self, *, indent=None, sort_keys=False):
"""Get the object's JSON representation.
Parameters
----------
indent: :class:`int`, optional
Number of spaces used as indentation, :obj:`None` will return the shortest possible string.
sort_keys: :class:`bool`, optional
Whether keys should be sorted alphabetically or preserve the order defined by the object.
Returns
-------
:class:`str`
JSON representation of the object.
"""
return json.dumps({k: v for k, v in dict(self).items() if v is not None}, indent=indent, sort_keys=sort_keys,
default=self._try_dict)
[docs]class BaseAnnouncement(metaclass=abc.ABCMeta):
"""Base class for all announcement classes.
Implement common properties and methods for announcements.
The following implement this class:
- :class:`.ForumAnnouncement`
- :class:`.AnnouncementEntry`
Attributes
----------
announcement_id: :class:`int`
The ID of the announcement.
"""
announcement_id: int
__slots__ = (
"announcement_id",
)
def __eq__(self, other):
if isinstance(other, self.__class__):
return other.announcement_id == self.announcement_id
return False
@property
def url(self):
""":class:`str` Get the URL to this announcement."""
return self.get_url(self.announcement_id)
[docs] @classmethod
def get_url(cls, announcement_id):
"""Get the URL to an announcement with a given ID.
Parameters
----------
announcement_id: :class:`int`
The ID of the announcement
Returns
-------
:class:`str`
The URL of the announcement.
"""
return get_tibia_url("forum", None, action="announcement", announcementid=announcement_id)
[docs]class BaseBoard(metaclass=abc.ABCMeta):
"""Base class for all board classes.
Implements common properties and methods for boards.
The following implement this class:
- :class:`.ForumBoard`
- :class:`.BoardEntry`
Attributes
----------
board_id: :class:`int`
The ID of the board.
"""
__slots__ = (
"board_id",
)
def __eq__(self, o: object) -> bool:
"""Two boards are considered equal if their ids are equal."""
if isinstance(o, self.__class__):
return self.board_id == o.board_id
return False
def __repr__(self):
return f"<{self.__class__.__name__} board_id={self.board_id!r}>"
@property
def url(self):
""":class:`str`: The URL of this board."""
return self.get_url(self.board_id)
[docs] @classmethod
def get_url(cls, board_id, page=1, age=30):
"""Get the Tibia.com URL to a board with a given id.
Parameters
----------
board_id: :class:`int`
The ID of the board.
page: :class:`int`
The page to go to.
age: :class:`int`
The age in days of the threads to display.
Returns
-------
:class:`str`
The URL to the board.
"""
return get_tibia_url("forum", None, action="board", boardid=board_id, pagenumber=page, threadage=age)
[docs] @classmethod
def get_world_boards_url(cls):
"""Get the URL to the World Boards section in Tibia.com.
Returns
-------
:class:`str`:
The URL to the World Boards.
"""
return get_tibia_url("forum", "worldboards")
[docs] @classmethod
def get_trade_boards_url(cls):
"""Get the URL to the Trade Boards section in Tibia.com.
Returns
-------
:class:`str`:
The URL to the Trade Boards.
"""
return get_tibia_url("forum", "tradeboards")
[docs] @classmethod
def get_support_boards_url(cls):
"""Get the URL to the Support Boards section in Tibia.com.
Returns
-------
:class:`str`:
The URL to the Support Boards.
"""
return get_tibia_url("forum", "supportboards")
[docs]class BaseCharacter(metaclass=abc.ABCMeta):
"""Base class for all character classes.
Implements common properties methods for characters.
The following implement this class:
- :class:`.Character`
- :class:`.GuildInvite`
- :class:`.GuildMember`
- :class:`.HighscoresEntry`
- :class:`.TournamentLeaderboardEntry`
- :class:`.OnlineCharacter`
- :class:`.OtherCharacter`
- :class:`.AuctionEntry`
- :class:`.Auction`
Attributes
----------
name: :class:`str`
The name of the character.
"""
__slots__ = (
"name",
)
def __eq__(self, o: object) -> bool:
"""Two characters are considered equal if their names are equal."""
if isinstance(o, self.__class__):
return self.name.lower() == o.name.lower()
return False
def __repr__(self):
return f"<{self.__class__.__name__} name={self.name!r}>"
@property
def url(self):
""":class:`str`: The URL of the character's information page on Tibia.com."""
return self.get_url(self.name)
[docs] @classmethod
def get_url(cls, name):
"""Get the Tibia.com URL for a given character name.
Parameters
------------
name: :class:`str`
The name of the character.
Returns
--------
:class:`str`
The URL to the character's page.
"""
return get_tibia_url("community", "characters", name=name)
[docs]class BaseGuild(metaclass=abc.ABCMeta):
"""Base class for Guild classes.
The following implement this class:
- :class:`.Guild`
- :class:`.GuildMembership`
- :class:`.GuildEntry`
Attributes
----------
name: :class:`str`
The name of the guild.
"""
__slots__ = (
"name",
)
def __repr__(self):
return f"<{self.__class__.__name__} name={self.name!r}>"
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.name == other.name
return False
@property
def url(self):
""":class:`str`: The URL to the guild's information page on Tibia.com."""
return self.get_url(self.name)
@property
def url_wars(self):
""":class:`str` The URL to the guild's wars page on Tibia.com.
.. versionadded:: 3.0.0
"""
return self.get_url_wars(self.name)
[docs] @classmethod
def get_url(cls, name):
"""Get the Tibia.com URL for a given guild name.
Parameters
------------
name: :class:`str`
The name of the guild.
Returns
--------
:class:`str`
The URL to the guild's page.
"""
return get_tibia_url("community", "guilds", page="view", GuildName=name)
[docs] @classmethod
def get_url_wars(cls, name):
"""Get the Tibia.com URL for the guild wars of a guild with a given name.
.. versionadded:: 3.0.0
Parameters
------------
name: :class:`str`
The name of the guild.
Returns
--------
:class:`str`
The URL to the guild's wars page.
"""
return get_tibia_url("community", "guilds", page="guildwars", action="view", GuildName=name)
[docs]class BaseHouse(metaclass=abc.ABCMeta):
"""Base class for all house classes.
The following implement this class:
- :class:`.House`
- :class:`.GuildHouse`
- :class:`.CharacterHouse`
- :class:`.HouseEntry`
Attributes
----------
name: :class:`str`
The name of the house.
"""
__slots__ = (
"name",
)
def __repr__(self):
return f"<{self.__class__.__name__} name={self.name!r}>"
def __eq__(self, o: object) -> bool:
"""Two houses are considered equal if their names are equal."""
if isinstance(o, self.__class__):
return self.name.lower() == o.name.lower()
return False
[docs] @classmethod
def get_url(cls, house_id, world):
"""Get the Tibia.com URL for a house with the given id and world.
Parameters
----------
house_id: :class:`int`
The internal id of the house.
world: :class:`str`
The world of the house.
Returns
-------
The URL to the house in Tibia.com
"""
return get_tibia_url("community", "houses", page="view", houseid=house_id, world=world)
[docs]class BaseNews(metaclass=abc.ABCMeta):
"""Base class for all news classes.
Implements the :py:attr:`id` attribute and common properties.
The following implement this class:
- :class:`.News`
- :class:`.NewsEntry`
Attributes
----------
id: :class:`int`
The internal ID of the news entry.
"""
__slots__ = (
"id",
)
def __eq__(self, o: object) -> bool:
"""Two news articles are considered equal if their names or ids are equal."""
if isinstance(o, self.__class__):
return self.id == o.id
return False
@property
def url(self):
""":class:`str`: The URL to the Tibia.com page of the news entry."""
return self.get_url(self.id)
[docs] @classmethod
def get_url(cls, news_id):
"""Get the Tibia.com URL for a news entry by its id.
Parameters
------------
news_id: :class:`int`
The id of the news entry.
Returns
--------
:class:`str`
The URL to the news' page
"""
return get_tibia_url("news", "newsarchive", id=news_id)
[docs] @classmethod
def get_list_url(cls):
"""Get the URL to Tibia.com's news archive page.
Notes
-----
It is not possible to perform a search using query parameters.
News searches can only be performed using POST requests sending the parameters as form-data.
Returns
-------
:class:`str`
The URL to the news archive page on Tibia.com.
"""
return get_tibia_url("news", "newsarchive")
[docs]class BasePost(metaclass=abc.ABCMeta):
"""Base classs for post classes.
The following implement this class:
- :class:`.CMPost`
- :class:`.ForumPost`
- :class:`.LastPost`
Attributes
----------
post_id: :class:`int`
The internal ID of the post.
"""
__slots__ = (
"post_id",
)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.post_id == other.post_id
return False
@property
def url(self):
""":class:`str`: Get the URL to this specific post."""
return self.get_url(self.post_id)
[docs] @classmethod
def get_url(cls, post_id):
"""Get the URL to a specific post.
Parameters
----------
post_id: :class:`int`
The ID of the desired post.
Returns
-------
:class:`str`
The URL to the post.
"""
return get_tibia_url("forum", None, anchor=f"post{post_id}", action="thread", postid=post_id)
[docs]class BaseThread(metaclass=abc.ABCMeta):
"""Base class for thread classes.
The following implement this class:
- :class:`.ThreadEntry`
- :class:`.ForumThread`
Attributes
----------
thread_id: :class:`int`
The internal ID of the thread.
"""
__slots__ = (
"thread_id",
)
@property
def url(self):
""":class:`str`: The URL to the thread in Tibia.com."""
return self.get_url(self.thread_id)
[docs] @classmethod
def get_url(cls, thread_id, page=1):
"""Get the URL to a thread with a given id.
Parameters
----------
thread_id: :class:`int`
The id of the desired thread.
page: :class:`int`
The desired page, by default 1.
Returns
-------
:class:`str`
The URL to the thread.
"""
return get_tibia_url("forum", None, action="thread", threadid=thread_id, pagenumber=page)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.thread_id == other.thread_id
return False
[docs]class BaseTournament(metaclass=abc.ABCMeta):
"""Base class for tournament classes.
The following implement this class:
- :class:`.TournamentEntry`
- :class:`.Tournament`
Attributes
----------
title: :class:`str`
The tournament's title.
cycle: :class:`int`
The tournament's cycle.
"""
__slots__ = (
"title",
"cycle",
)
def __eq__(self, other):
"""Two tournaments are considered the same when they have the same title or cycle."""
if isinstance(other, self.__class__):
return self.title.lower() == other.title.lower() or (self.cycle > 0 and self.cycle == other.cycle)
return False
@property
def url(self):
""":class:`str`: The URL to the tournament's information page."""
return self.get_url(self.cycle)
[docs] @classmethod
def get_url(cls, tournament_cycle):
"""Get the URL to a tournament's information page.
If its cycle is provided, otherwise it shows the current tournament.
Parameters
----------
tournament_cycle: :class:`int`
The tournament's cycle.
Returns
-------
:class:`str`
The URL to the specified tournament.
"""
params = None
if tournament_cycle:
params = {
"tournamentcycle": tournament_cycle,
"action": "archive",
}
return get_tibia_url("community", "tournament", **params)
[docs]class BaseWorld(metaclass=abc.ABCMeta):
"""Base class for all World classes.
The following implement this class:
- :class:`.WorldEntry`
- :class:`.World`
Attributes
----------
name: :class:`str`
The name of the world.
"""
name: str
location: WorldLocation
pvp_type: PvpType
__slots__ = (
"name",
)
def __repr__(self):
return f"<{self.__class__.__name__} name={self.name!r} location={self.location!r} pvp_type={self.pvp_type!r}>"
@property
def url(self):
""":class:`str`: URL to the world's information page on Tibia.com."""
return self.get_url(self.name)
[docs] @classmethod
def get_url(cls, name):
"""Get the URL to the World's information page on Tibia.com.
Parameters
----------
name: :class:`str`
The name of the world.
Returns
-------
:class:`str`
The URL to the world's information page.
"""
return get_tibia_url("community", "worlds", world=name.title())
[docs]class HouseWithId:
"""Implements the :py:attr:`id` attribute and dependant functions and properties.
Subclasses mut also implement :class:`.BaseHouse`
"""
name: str
id: int
world: str
get_url: Callable[[int, str], str]
def __eq__(self, o: object) -> bool:
"""Two houses are considered equal if their names or ids are equal."""
if isinstance(o, self.__class__):
return self.name.lower() == o.name.lower() or self.id == o.id
return False
@property
def url(self):
""":class:`str`: The URL to the Tibia.com page of the house."""
return self.get_url(self.id, self.world) if self.id and self.world else None