11"""Models related to the creatures section in the library."""
2+ import os
23import re
34import urllib .parse
45from typing import List , Optional
910from tibiapy .errors import InvalidContent
1011
1112__all__ = (
13+ "BoostedCreatures" ,
14+ "BoostableBosses" ,
15+ "BossEntry" ,
1216 "CreatureEntry" ,
1317 "CreaturesSection" ,
1418 "Creature" ,
1519)
1620
1721from tibiapy .utils import parse_tibiacom_content , get_tibia_url
1822
19- BOOSTED_ALT = "Today's boosted creature: "
23+ BOOSTED_ALT = re .compile ("Today's boosted \w+: " )
24+
2025
2126HP_PATTERN = re .compile (r"have (\d+) hitpoints" )
2227EXP_PATTERN = re .compile (r"yield (\d+) experience" )
2732MANA_COST = re .compile (r"takes (\d+) mana" )
2833
2934
35+ class BoostedCreatures (abc .Serializable ):
36+ """Contains both boosted creature and boosted boss.
37+
38+ Attributes
39+ ----------
40+ creature: :class:`CreatureEntry`
41+ The boosted creature of the day.
42+ boss: :class:`BossEntry`
43+ The boosted boss of the day.
44+ """
45+
46+ __slots__ = (
47+ "creature" ,
48+ "boss" ,
49+ )
50+
51+ def __init__ (self , creature , boss ):
52+ self .creature : CreatureEntry = creature
53+ self .boss : BossEntry = boss
54+
55+ @classmethod
56+ def _parse_boosted_platform (cls , parsed_content : bs4 .BeautifulSoup , tag_id : str ):
57+ img = parsed_content .find ("img" , attrs = {"id" : tag_id })
58+ name = BOOSTED_ALT .sub ("" , img ["title" ]).strip ()
59+ image_url = img ["src" ]
60+ identifier = image_url .split ("/" )[- 1 ].replace (".gif" , "" )
61+ return name , identifier
62+
63+ @classmethod
64+ def from_header (cls , content : str ):
65+ """Parses both boosted creature and boss from the content of any section in Tibia.com
66+
67+ Parameters
68+ ----------
69+ content: :class:`str`
70+ The HTML content of the page.
71+
72+ Returns
73+ -------
74+ :class:`BoostedCreatures`
75+ The boosted creature an boss.
76+
77+ Raises
78+ ------
79+ InvalidContent
80+ If content is not the HTML of a Tibia.com page.
81+ """
82+ try :
83+ parsed_content = bs4 .BeautifulSoup (content .replace ('ISO-8859-1' , 'utf-8' ), "lxml" ,
84+ parse_only = bs4 .SoupStrainer ("div" , attrs = {"id" : "RightArtwork" }))
85+ creature_name , creature_identifier = cls ._parse_boosted_platform (parsed_content , "Monster" )
86+ boss_name , boss_identifier = cls ._parse_boosted_platform (parsed_content , "Boss" )
87+ return cls (CreatureEntry (creature_name , creature_identifier ), BossEntry (boss_name , boss_identifier ))
88+ except (TypeError , NameError , KeyError ) as e :
89+ raise InvalidContent ("content is not from Tibia.com" , e )
90+
91+
92+ class BoostableBosses (abc .Serializable ):
93+ """Represents the boostable bosses section in the Tibia.com library
94+
95+ Attributes
96+ ----------
97+ boosted_boss: :class:`BossEntry`
98+ The current boosted boss.
99+ bosses: list of :class:`BossEntry`
100+ The list of boostable bosses.
101+ """
102+
103+ __slots__ = (
104+ "boosted_boss" ,
105+ "bosses" ,
106+ )
107+
108+ def __init__ (self , boosted_boss , bosses ):
109+ self .boosted_boss : BossEntry = boosted_boss
110+ self .bosses : List [BossEntry ] = bosses
111+
112+ @classmethod
113+ def get_url (cls ):
114+ """Get the URL to the Tibia.com boostable bosses.
115+
116+ Returns
117+ -------
118+ :class:`str`:
119+ The URL to the Tibia.com library section.
120+ """
121+ return get_tibia_url ("library" , "boostablebosses" )
122+
123+ @classmethod
124+ def from_content (cls , content ):
125+ """Create an instance of the class from the html content of the boostable bosses library's page.
126+
127+ Parameters
128+ ----------
129+ content: :class:`str`
130+ The HTML content of the page.
131+
132+ Returns
133+ -------
134+ :class:`BoostableBosses`
135+ The Boostable Bosses section.
136+
137+ Raises
138+ ------
139+ InvalidContent
140+ If content is not the HTML of a creature library's page.
141+ """
142+ try :
143+ parsed_content = parse_tibiacom_content (content )
144+ boosted_creature_table = parsed_content .find ("div" , {"class" : "TableContainer" })
145+ boosted_creature_text = boosted_creature_table .find ("div" , {"class" : "Text" })
146+ if not boosted_creature_text or "Boosted" not in boosted_creature_text .text :
147+ return None
148+ boosted_boss_tag = boosted_creature_table .find ("b" )
149+ boosted_boss_image = boosted_creature_table .find ("img" )
150+ image_url = urllib .parse .urlparse (boosted_boss_image ["src" ])
151+ boosted_boss = BossEntry (boosted_boss_tag .text , os .path .basename (image_url .path ).replace (".gif" , "" ))
152+
153+ list_table = parsed_content .find ("div" , style = lambda v : v and 'display: table' in v )
154+ entries_container = list_table .find_all ("div" , style = lambda v : v and 'float: left' in v )
155+ entries = []
156+ for entry_container in entries_container :
157+ name = entry_container .text .strip ()
158+ image = entry_container .find ("img" )
159+ image_url = urllib .parse .urlparse (image ["src" ])
160+ identifier = os .path .basename (image_url .path ).replace (".gif" , "" )
161+ entries .append (BossEntry (name , identifier ))
162+ return cls (boosted_boss , entries )
163+ except (AttributeError , ValueError ) as e :
164+ raise InvalidContent ("content is not the boosted boss's library" , e )
165+
166+
167+ @classmethod
168+ def boosted_boss_from_header (cls , content ):
169+ """Get the boosted boss from any Tibia.com page.
170+
171+ Parameters
172+ ----------
173+ content: :class:`str`
174+ The HTML content of a Tibia.com page.
175+
176+ Returns
177+ -------
178+ :class:`BossEntry`
179+ The boosted boss of the day.
180+
181+ Raises
182+ ------
183+ InvalidContent
184+ If content is not the HTML of a Tibia.com's page.
185+ """
186+ return BoostedCreatures .from_header (content ).boss
187+
188+
189+ class BossEntry (abc .Serializable ):
190+ """Represents a boss in the boostable bosses section in the Tibia.com library.
191+
192+ Attributes
193+ ----------
194+ name: :class:`str`
195+ The name of the boss..
196+ identifier: :class:`str`
197+ The internal name of the boss. Used for images.
198+ """
199+
200+ __slots__ = (
201+ "name" ,
202+ "identifier" ,
203+ )
204+
205+ _serializable_properties = (
206+ "image_url" ,
207+ )
208+
209+ def __init__ (self , name , identifier = None ):
210+ self .name : str = name
211+ self .identifier : str = identifier
212+
213+ def __repr__ (self ):
214+ return f"<{ self .__class__ .__name__ } name={ self .name !r} identifier={ self .identifier !r} >"
215+
216+ @property
217+ def image_url (self ):
218+ """:class:`str`: The URL to this boss's image."""
219+ return f"https://static.tibia.com/images/library/{ self .identifier } .gif"
220+
221+
30222class CreaturesSection (abc .Serializable ):
31223 """Represents the creature's section in the Tibia.com library.
32224
@@ -77,16 +269,7 @@ def boosted_creature_from_header(cls, content):
77269 InvalidContent
78270 If content is not the HTML of a Tibia.com's page.
79271 """
80- try :
81- parsed_content = bs4 .BeautifulSoup (content .replace ('ISO-8859-1' , 'utf-8' ), "lxml" ,
82- parse_only = bs4 .SoupStrainer ("div" , attrs = {"id" : "RightArtwork" }))
83- img = parsed_content .find ("img" , attrs = {"id" : "Monster" })
84- name = img ["title" ].replace (BOOSTED_ALT , "" ).strip ()
85- image_url = img ["src" ]
86- identifier = image_url .split ("/" )[- 1 ].replace (".gif" , "" )
87- return CreatureEntry (name , identifier )
88- except TypeError as e :
89- raise InvalidContent ("content is not from Tibia.com" , e )
272+ return BoostedCreatures .from_header (content ).creature
90273
91274 @classmethod
92275 def from_content (cls , content ):
@@ -99,8 +282,8 @@ def from_content(cls, content):
99282
100283 Returns
101284 -------
102- :class:`Character `
103- The character contained in the page .
285+ :class:`CreaturesSection `
286+ The creatures section from Tibia.com .
104287
105288 Raises
106289 ------
0 commit comments