geniuslib 5.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. geniuslib/__init__.py +186 -0
  2. geniuslib/__main__.py +8 -0
  3. geniuslib/abc.py +274 -0
  4. geniuslib/battlelog.py +458 -0
  5. geniuslib/battlelog_analytics.py +672 -0
  6. geniuslib/buildings.py +510 -0
  7. geniuslib/characters.py +144 -0
  8. geniuslib/clans.py +289 -0
  9. geniuslib/cli.py +239 -0
  10. geniuslib/client.py +2924 -0
  11. geniuslib/comparer.py +122 -0
  12. geniuslib/constants.py +331 -0
  13. geniuslib/cosmetics.py +198 -0
  14. geniuslib/entry_logs.py +260 -0
  15. geniuslib/enums.py +186 -0
  16. geniuslib/errors.py +139 -0
  17. geniuslib/events.py +1153 -0
  18. geniuslib/events.pyi +232 -0
  19. geniuslib/exporter.py +248 -0
  20. geniuslib/ext/discordlinks/__init__.py +279 -0
  21. geniuslib/ext/triggers/__init__.py +15 -0
  22. geniuslib/ext/triggers/cron.py +215 -0
  23. geniuslib/ext/triggers/triggers.py +503 -0
  24. geniuslib/formatters.py +151 -0
  25. geniuslib/game_data.py +687 -0
  26. geniuslib/hero.py +414 -0
  27. geniuslib/http.py +707 -0
  28. geniuslib/iterators.py +217 -0
  29. geniuslib/middleware.py +197 -0
  30. geniuslib/miscmodels.py +918 -0
  31. geniuslib/player_clan.py +41 -0
  32. geniuslib/players.py +758 -0
  33. geniuslib/raid.py +502 -0
  34. geniuslib/raid_analytics.py +130 -0
  35. geniuslib/spell.py +126 -0
  36. geniuslib/static/__init__.py +0 -0
  37. geniuslib/static/static_data.json +63983 -0
  38. geniuslib/static/update_static.py +1481 -0
  39. geniuslib/troop.py +192 -0
  40. geniuslib/upgrade_tracker.py +510 -0
  41. geniuslib/utils.py +856 -0
  42. geniuslib/war_analytics.py +218 -0
  43. geniuslib/war_attack.py +105 -0
  44. geniuslib/war_clans.py +199 -0
  45. geniuslib/war_members.py +143 -0
  46. geniuslib/wars.py +538 -0
  47. geniuslib-5.5.0.dist-info/METADATA +375 -0
  48. geniuslib-5.5.0.dist-info/RECORD +51 -0
  49. geniuslib-5.5.0.dist-info/WHEEL +5 -0
  50. geniuslib-5.5.0.dist-info/licenses/LICENSE +21 -0
  51. geniuslib-5.5.0.dist-info/top_level.txt +1 -0
geniuslib/battlelog.py ADDED
@@ -0,0 +1,458 @@
1
+ # GeniusLib - Clash of Clans API wrapper
2
+ # Based on coc.py (MIT License, copyright (c) 2019-2020 mathsman5133)
3
+ # (c) 2026 AkumaHalls / ClashGenius
4
+
5
+ from __future__ import annotations
6
+
7
+ from datetime import datetime
8
+ from typing import List, Optional, TYPE_CHECKING
9
+
10
+ from .utils import from_timestamp
11
+
12
+ if TYPE_CHECKING:
13
+ from .client import Client
14
+
15
+
16
+ class BattleLogResource:
17
+ """Represents a resource amount in a player battle log entry.
18
+
19
+ Attributes
20
+ ----------
21
+ name:
22
+ :class:`str`: The resource name (e.g. ``gold``, ``elixir``, ``darkElixir``).
23
+ amount:
24
+ :class:`int`: The amount of this resource.
25
+ """
26
+
27
+ __slots__ = ("name", "amount")
28
+
29
+ def __init__(self, *, data):
30
+ self._from_data(data)
31
+
32
+ def _from_data(self, data: dict) -> None:
33
+ self.name: str = data.get("name")
34
+ self.amount: int = data.get("amount")
35
+
36
+ def __repr__(self):
37
+ attrs = [("name", self.name), ("amount", self.amount)]
38
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
39
+
40
+ def __eq__(self, other):
41
+ return isinstance(other, self.__class__) and self.name == other.name and self.amount == other.amount
42
+
43
+
44
+ class BattleLogEntry:
45
+ """Represents a single player battle log entry from the ``/players/{tag}/battlelog`` endpoint.
46
+
47
+ Attributes
48
+ ----------
49
+ battle_type:
50
+ :class:`str`: The type of battle (e.g. ``legendLeague``).
51
+ attack:
52
+ :class:`bool`: ``True`` if this was an attack, ``False`` if a defense.
53
+ timestamp:
54
+ Optional[:class:`datetime`]: When the battle occurred.
55
+ army_share_code:
56
+ :class:`str`: Code to share the army composition used.
57
+ opponent_player_tag:
58
+ :class:`str`: The tag of the opponent player.
59
+ stars:
60
+ :class:`int`: Stars earned in this battle.
61
+ destruction_percentage:
62
+ :class:`int`: Destruction percentage achieved.
63
+ looted_resources:
64
+ List[:class:`BattleLogResource`]: Resources looted.
65
+ extra_looted_resources:
66
+ List[:class:`BattleLogResource`]: Bonus resources looted.
67
+ available_loot:
68
+ List[:class:`BattleLogResource`]: Resources available to loot before the battle.
69
+ """
70
+
71
+ __slots__ = (
72
+ "battle_type",
73
+ "attack",
74
+ "timestamp",
75
+ "army_share_code",
76
+ "opponent_player_tag",
77
+ "stars",
78
+ "destruction_percentage",
79
+ "looted_resources",
80
+ "extra_looted_resources",
81
+ "available_loot",
82
+ "_client",
83
+ "_response_retry",
84
+ "_raw_data",
85
+ )
86
+
87
+ def __init__(self, *, data, client: Optional[Client] = None, **_):
88
+ self._client = client
89
+ self._response_retry = data.get("_response_retry")
90
+ self._raw_data = data if client and client.raw_attribute else None
91
+ self._from_data(data)
92
+
93
+ def _from_data(self, data: dict) -> None:
94
+ data_get = data.get
95
+ self.battle_type: str = data_get("battleType")
96
+ self.attack: bool = data_get("attack")
97
+ raw_ts = data_get("timestamp")
98
+ self.timestamp: Optional[datetime] = from_timestamp(raw_ts) if raw_ts else None
99
+ self.army_share_code: str = data_get("armyShareCode")
100
+ self.opponent_player_tag: str = data_get("opponentPlayerTag")
101
+ self.stars: int = data_get("stars")
102
+ self.destruction_percentage: int = data_get("destructionPercentage")
103
+ self.looted_resources: List[BattleLogResource] = [
104
+ BattleLogResource(data=item) for item in data_get("lootedResources", [])
105
+ ]
106
+ self.extra_looted_resources: List[BattleLogResource] = [
107
+ BattleLogResource(data=item) for item in data_get("extraLootedResources", [])
108
+ ]
109
+ self.available_loot: List[BattleLogResource] = [
110
+ BattleLogResource(data=item) for item in data_get("availableLoot", [])
111
+ ]
112
+
113
+ def __repr__(self):
114
+ attrs = [
115
+ ("battle_type", self.battle_type),
116
+ ("attack", self.attack),
117
+ ("stars", self.stars),
118
+ ("destruction_percentage", self.destruction_percentage),
119
+ ("opponent", self.opponent_player_tag),
120
+ ]
121
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
122
+
123
+ def __eq__(self, other):
124
+ if not isinstance(other, self.__class__):
125
+ return False
126
+ return (
127
+ self.opponent_player_tag == other.opponent_player_tag
128
+ and self.timestamp == other.timestamp
129
+ and self.attack == other.attack
130
+ )
131
+
132
+ @property
133
+ def is_attack(self) -> bool:
134
+ """:class:`bool`: Returns ``True`` if this entry is an attack."""
135
+ return self.attack is True
136
+
137
+ @property
138
+ def is_defense(self) -> bool:
139
+ """:class:`bool`: Returns ``True`` if this entry is a defense."""
140
+ return self.attack is False
141
+
142
+ @property
143
+ def total_looted(self) -> int:
144
+ """:class:`int`: Total resources looted across all resource types."""
145
+ return sum(r.amount for r in self.looted_resources)
146
+
147
+ @property
148
+ def total_available(self) -> int:
149
+ """:class:`int`: Total resources available before the battle."""
150
+ return sum(r.amount for r in self.available_loot)
151
+
152
+ @property
153
+ def loot_efficiency(self) -> float:
154
+ """:class:`float`: Percentage of available loot that was actually looted."""
155
+ available = self.total_available
156
+ if available == 0:
157
+ return 0.0
158
+ return (self.total_looted / available) * 100.0
159
+
160
+
161
+ class LeagueHistoryEntry:
162
+ """Represents a player league season history entry from the ``/players/{tag}/leaguehistory`` endpoint.
163
+
164
+ Attributes
165
+ ----------
166
+ league_season_id:
167
+ :class:`int`: The league season identifier.
168
+ league_trophies:
169
+ :class:`int`: Trophies earned in this season.
170
+ league_tier_id:
171
+ :class:`int`: The league tier ID.
172
+ placement:
173
+ :class:`int`: Global or local ranking placement.
174
+ attack_wins:
175
+ :class:`int`: Number of attack wins.
176
+ attack_losses:
177
+ :class:`int`: Number of attack losses.
178
+ attack_stars:
179
+ :class:`int`: Total stars earned from attacks.
180
+ defense_wins:
181
+ :class:`int`: Number of defense wins.
182
+ defense_losses:
183
+ :class:`int`: Number of defense losses.
184
+ defense_stars:
185
+ :class:`int`: Total stars conceded from defenses.
186
+ max_battles:
187
+ :class:`int`: Maximum battles available this season.
188
+ """
189
+
190
+ __slots__ = (
191
+ "league_season_id",
192
+ "league_trophies",
193
+ "league_tier_id",
194
+ "placement",
195
+ "attack_wins",
196
+ "attack_losses",
197
+ "attack_stars",
198
+ "defense_wins",
199
+ "defense_losses",
200
+ "defense_stars",
201
+ "max_battles",
202
+ "_client",
203
+ "_response_retry",
204
+ "_raw_data",
205
+ )
206
+
207
+ def __init__(self, *, data, client: Optional[Client] = None, **_):
208
+ self._client = client
209
+ self._response_retry = data.get("_response_retry")
210
+ self._raw_data = data if client and client.raw_attribute else None
211
+ self._from_data(data)
212
+
213
+ def _from_data(self, data: dict) -> None:
214
+ data_get = data.get
215
+ self.league_season_id: int = data_get("leagueSeasonId")
216
+ self.league_trophies: int = data_get("leagueTrophies")
217
+ self.league_tier_id: int = data_get("leagueTierId")
218
+ self.placement: int = data_get("placement")
219
+ self.attack_wins: int = data_get("attackWins")
220
+ self.attack_losses: int = data_get("attackLosses")
221
+ self.attack_stars: int = data_get("attackStars")
222
+ self.defense_wins: int = data_get("defenseWins")
223
+ self.defense_losses: int = data_get("defenseLosses")
224
+ self.defense_stars: int = data_get("defenseStars")
225
+ self.max_battles: int = data_get("maxBattles")
226
+
227
+ def __repr__(self):
228
+ attrs = [
229
+ ("season", self.league_season_id),
230
+ ("trophies", self.league_trophies),
231
+ ("tier", self.league_tier_id),
232
+ ("placement", self.placement),
233
+ ]
234
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
235
+
236
+ def __eq__(self, other):
237
+ return isinstance(other, self.__class__) and self.league_season_id == other.league_season_id
238
+
239
+ @property
240
+ def total_attacks(self) -> int:
241
+ """:class:`int`: Total attacks performed (wins + losses)."""
242
+ return self.attack_wins + self.attack_losses
243
+
244
+ @property
245
+ def total_defenses(self) -> int:
246
+ """:class:`int`: Total defenses received (wins + losses)."""
247
+ return self.defense_wins + self.defense_losses
248
+
249
+ @property
250
+ def attack_win_rate(self) -> float:
251
+ """:class:`float`: Attack win rate as a percentage (0-100)."""
252
+ total = self.total_attacks
253
+ if total == 0:
254
+ return 0.0
255
+ return (self.attack_wins / total) * 100.0
256
+
257
+ @property
258
+ def defense_win_rate(self) -> float:
259
+ """:class:`float`: Defense win rate as a percentage (0-100)."""
260
+ total = self.total_defenses
261
+ if total == 0:
262
+ return 0.0
263
+ return (self.defense_wins / total) * 100.0
264
+
265
+
266
+ class LeagueTierGroupBattleLogEntry:
267
+ """Represents an attack or defense log entry inside a league tier group.
268
+
269
+ Attributes
270
+ ----------
271
+ opponent_player_tag:
272
+ :class:`str`: The tag of the opponent.
273
+ opponent_name:
274
+ :class:`str`: The name of the opponent.
275
+ stars:
276
+ :class:`int`: Stars earned or conceded.
277
+ destruction_percentage:
278
+ :class:`int`: Destruction percentage.
279
+ trophies:
280
+ :class:`int`: Trophies gained or lost.
281
+ creation_time:
282
+ Optional[:class:`datetime`]: When the battle occurred.
283
+ """
284
+
285
+ __slots__ = (
286
+ "opponent_player_tag",
287
+ "opponent_name",
288
+ "stars",
289
+ "destruction_percentage",
290
+ "trophies",
291
+ "creation_time",
292
+ )
293
+
294
+ def __init__(self, *, data):
295
+ self._from_data(data)
296
+
297
+ def _from_data(self, data: dict) -> None:
298
+ data_get = data.get
299
+ self.opponent_player_tag: str = data_get("opponentPlayerTag")
300
+ self.opponent_name: str = data_get("opponentName")
301
+ self.stars: int = data_get("stars")
302
+ self.destruction_percentage: int = data_get("destructionPercentage")
303
+ self.trophies: int = data_get("trophies")
304
+ raw_time = data_get("creationTime")
305
+ self.creation_time: Optional[datetime] = from_timestamp(raw_time) if raw_time else None
306
+
307
+ def __repr__(self):
308
+ attrs = [
309
+ ("opponent", self.opponent_name),
310
+ ("stars", self.stars),
311
+ ("trophies", self.trophies),
312
+ ]
313
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
314
+
315
+
316
+ class LeagueTierGroupMember:
317
+ """Represents a member inside a league tier group.
318
+
319
+ Attributes
320
+ ----------
321
+ player_tag:
322
+ :class:`str`: The player's tag.
323
+ player_name:
324
+ :class:`str`: The player's name.
325
+ clan_tag:
326
+ :class:`str`: The player's clan tag.
327
+ clan_name:
328
+ :class:`str`: The player's clan name.
329
+ league_trophies:
330
+ :class:`int`: Current league trophies.
331
+ attack_win_count:
332
+ :class:`int`: Number of attack wins in this group.
333
+ attack_lose_count:
334
+ :class:`int`: Number of attack losses in this group.
335
+ defense_win_count:
336
+ :class:`int`: Number of defense wins in this group.
337
+ defense_lose_count:
338
+ :class:`int`: Number of defense losses in this group.
339
+ """
340
+
341
+ __slots__ = (
342
+ "player_tag",
343
+ "player_name",
344
+ "clan_tag",
345
+ "clan_name",
346
+ "league_trophies",
347
+ "attack_win_count",
348
+ "attack_lose_count",
349
+ "defense_win_count",
350
+ "defense_lose_count",
351
+ )
352
+
353
+ def __init__(self, *, data):
354
+ self._from_data(data)
355
+
356
+ def _from_data(self, data: dict) -> None:
357
+ data_get = data.get
358
+ self.player_tag: str = data_get("playerTag")
359
+ self.player_name: str = data_get("playerName")
360
+ self.clan_tag: str = data_get("clanTag")
361
+ self.clan_name: str = data_get("clanName")
362
+ self.league_trophies: int = data_get("leagueTrophies")
363
+ self.attack_win_count: int = data_get("attackWinCount")
364
+ self.attack_lose_count: int = data_get("attackLoseCount")
365
+ self.defense_win_count: int = data_get("defenseWinCount")
366
+ self.defense_lose_count: int = data_get("defenseLoseCount")
367
+
368
+ def __repr__(self):
369
+ attrs = [
370
+ ("name", self.player_name),
371
+ ("clan", self.clan_name),
372
+ ("trophies", self.league_trophies),
373
+ ]
374
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
375
+
376
+ @property
377
+ def total_attacks(self) -> int:
378
+ """:class:`int`: Total attacks (wins + losses)."""
379
+ return self.attack_win_count + self.attack_lose_count
380
+
381
+ @property
382
+ def total_defenses(self) -> int:
383
+ """:class:`int`: Total defenses (wins + losses)."""
384
+ return self.defense_win_count + self.defense_lose_count
385
+
386
+ @property
387
+ def attack_win_rate(self) -> float:
388
+ """:class:`float`: Attack win rate as a percentage (0-100)."""
389
+ total = self.total_attacks
390
+ if total == 0:
391
+ return 0.0
392
+ return (self.attack_win_count / total) * 100.0
393
+
394
+
395
+ class LeagueTierGroup:
396
+ """Represents a league tier group for a league season from the ``/leaguegroup/{tag}/{seasonId}`` endpoint.
397
+
398
+ Attributes
399
+ ----------
400
+ members:
401
+ List[:class:`LeagueTierGroupMember`]: All members in the group.
402
+ attack_logs:
403
+ List[:class:`LeagueTierGroupBattleLogEntry`]: Detailed attack history.
404
+ defense_logs:
405
+ List[:class:`LeagueTierGroupBattleLogEntry`]: Detailed defense history.
406
+ """
407
+
408
+ __slots__ = (
409
+ "members",
410
+ "attack_logs",
411
+ "defense_logs",
412
+ "_client",
413
+ "_response_retry",
414
+ "_raw_data",
415
+ )
416
+
417
+ def __init__(self, *, data, client: Optional[Client] = None, **_):
418
+ self._client = client
419
+ self._response_retry = data.get("_response_retry")
420
+ self._raw_data = data if client and client.raw_attribute else None
421
+ self._from_data(data)
422
+
423
+ def _from_data(self, data: dict) -> None:
424
+ self.members: List[LeagueTierGroupMember] = [
425
+ LeagueTierGroupMember(data=item) for item in data.get("members", [])
426
+ ]
427
+ self.attack_logs: List[LeagueTierGroupBattleLogEntry] = [
428
+ LeagueTierGroupBattleLogEntry(data=item) for item in data.get("attackLogs", [])
429
+ ]
430
+ self.defense_logs: List[LeagueTierGroupBattleLogEntry] = [
431
+ LeagueTierGroupBattleLogEntry(data=item) for item in data.get("defenseLogs", [])
432
+ ]
433
+
434
+ def __repr__(self):
435
+ attrs = [
436
+ ("members", len(self.members)),
437
+ ("attacks", len(self.attack_logs)),
438
+ ("defenses", len(self.defense_logs)),
439
+ ]
440
+ return "<%s %s>" % (self.__class__.__name__, " ".join("%s=%r" % t for t in attrs),)
441
+
442
+ def get_member(self, tag: str) -> Optional[LeagueTierGroupMember]:
443
+ """Find a member by their player tag.
444
+
445
+ Parameters
446
+ ----------
447
+ tag:
448
+ :class:`str`: The player tag to search for.
449
+
450
+ Returns
451
+ -------
452
+ Optional[:class:`LeagueTierGroupMember`]
453
+ The member, or ``None`` if not found.
454
+ """
455
+ for member in self.members:
456
+ if member.player_tag == tag:
457
+ return member
458
+ return None