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/__init__.py ADDED
@@ -0,0 +1,186 @@
1
+ # GeniusLib - Clash of Clans API wrapper
2
+ # Based on coc.py v4.0.0 (MIT License, copyright (c) 2019-2020 mathsman5133)
3
+ # (c) 2026 AkumaHalls / ClashGenius
4
+
5
+ __version__ = "5.5.0"
6
+
7
+ from .abc import BasePlayer, BaseClan
8
+ from .battlelog import (
9
+ BattleLogEntry,
10
+ BattleLogResource,
11
+ LeagueHistoryEntry,
12
+ LeagueTierGroup,
13
+ LeagueTierGroupBattleLogEntry,
14
+ LeagueTierGroupMember,
15
+ )
16
+ from .buildings import (
17
+ Building,
18
+ GearUp,
19
+ MergeRequirement,
20
+ SeasonalDefenseModule,
21
+ SeasonalDefense,
22
+ Supercharge,
23
+ TownhallUnlock,
24
+ TownhallWeapon,
25
+ Trap,
26
+ )
27
+ from .characters import Guardian, Helper
28
+ from .clans import RankedClan, Clan
29
+ from .client import Client
30
+ from .constants import *
31
+ from .cosmetics import Skin, Scenery, Obstacle, Decoration, ClanCapitalHousePart
32
+ from .events import PlayerEvents, ClanEvents, WarEvents, EventsClient, ClientEvents
33
+ from .enums import (
34
+ PlayerHouseElementType,
35
+ Resource,
36
+ Role,
37
+ WarRound,
38
+ WarState,
39
+ BattleModifier,
40
+ WarResult,
41
+ ProductionBuildingType,
42
+ BuildingType,
43
+ VillageType,
44
+ SceneryType,
45
+ EquipmentRarity,
46
+ SkinTier
47
+ )
48
+ from .errors import (
49
+ ClashOfClansException,
50
+ HTTPException,
51
+ NotFound,
52
+ InvalidArgument,
53
+ InvalidCredentials,
54
+ LoginError,
55
+ Forbidden,
56
+ Maintenance,
57
+ GatewayError,
58
+ PrivateWarLog,
59
+ )
60
+ from .game_data import AccountData, Upgrade, Boosts, ArmyRecipe, HeroLoadout, StaticData
61
+ from .hero import Equipment, Hero, Pet
62
+ from .http import BasicThrottler, BatchThrottler, HTTPClient
63
+ from .iterators import (
64
+ ClanIterator,
65
+ ClanWarIterator,
66
+ PlayerIterator,
67
+ LeagueWarIterator,
68
+ CurrentWarIterator,
69
+ SeasonIterator,
70
+ )
71
+ from .miscmodels import (
72
+ Achievement,
73
+ Badge,
74
+ BaseLeague,
75
+ CapitalDistrict,
76
+ ChatLanguage,
77
+ GoldPassSeason,
78
+ Icon,
79
+ Label,
80
+ League,
81
+ LeagueGroupClan,
82
+ LeagueGroupInfo,
83
+ LegendStatistics,
84
+ LoadGameData,
85
+ Location,
86
+ PlayerHouseElement,
87
+ Season,
88
+ Timestamp,
89
+ TimeDelta,
90
+ TID,
91
+ Translation
92
+ )
93
+ from .players import Player, ClanMember, RankedPlayer
94
+ from .player_clan import PlayerClan
95
+ from .raid import RaidClan, RaidMember, RaidLogEntry, RaidDistrict, RaidAttack
96
+ from .spell import Spell
97
+ from .troop import Troop
98
+ from .war_clans import WarClan, ClanWarLeagueClan
99
+ from .war_attack import WarAttack
100
+ from .war_members import ClanWarLeagueClanMember, ClanWarMember
101
+ from .wars import ClanWar, ClanWarLogEntry, ClanWarLeagueGroup, ExtendedCWLGroup
102
+ from .war_analytics import (
103
+ new_stars,
104
+ previous_best_attack,
105
+ best_attack_on,
106
+ best_defense_on,
107
+ total_attack_stars,
108
+ total_attack_destruction,
109
+ get_cleanup_attacks,
110
+ count_missed_attacks,
111
+ get_attack_order,
112
+ get_war_result,
113
+ )
114
+ from .raid_analytics import (
115
+ total_member_attack_stars,
116
+ total_member_destruction,
117
+ best_raid_attack,
118
+ average_attack_destruction,
119
+ count_missed_raid_attacks,
120
+ get_inactive_raid_members,
121
+ member_raid_contribution,
122
+ district_attack_breakdown,
123
+ get_raid_cleanup_attacks,
124
+ get_wasted_attacks,
125
+ clan_offensive_stats,
126
+ clan_defensive_stats,
127
+ raid_summary,
128
+ )
129
+ from .battlelog_analytics import (
130
+ battle_win_rate,
131
+ battle_attack_stats,
132
+ battle_defense_stats,
133
+ battle_loot_summary,
134
+ battle_streak,
135
+ battle_consistency_score,
136
+ battle_daily_summary,
137
+ battle_period_summary,
138
+ league_history_progression,
139
+ league_season_stats,
140
+ league_tier_distribution,
141
+ tier_group_member_stats,
142
+ tier_group_attack_analysis,
143
+ tier_group_defense_analysis,
144
+ tier_group_mvp,
145
+ decode_army_code,
146
+ )
147
+ from .formatters import (
148
+ town_hall_emoji,
149
+ format_th,
150
+ format_role,
151
+ format_league,
152
+ format_builder_base_league,
153
+ format_trophies,
154
+ format_player_brief,
155
+ format_member_brief,
156
+ format_clan_brief,
157
+ format_clan_detailed,
158
+ format_war_state,
159
+ format_war_result,
160
+ format_war_score,
161
+ format_attack,
162
+ format_percentage,
163
+ format_number,
164
+ format_raid_brief,
165
+ )
166
+ from .middleware import (
167
+ Middleware,
168
+ RequestMiddleware,
169
+ ResponseMiddleware,
170
+ request_logger,
171
+ response_logger,
172
+ timing_header,
173
+ middleware,
174
+ )
175
+ from .upgrade_tracker import (
176
+ UpgradeCost,
177
+ UpgradeSummary,
178
+ estimate_upgrade_cost,
179
+ estimate_upgrade_time,
180
+ get_th_upgrade_summary,
181
+ format_upgrade_summary,
182
+ )
183
+ from .exporter import to_json, to_csv, to_dict, export_players, export_clans
184
+ from .comparer import compare_players, compare_clans
185
+ from . import utils
186
+ from .utils import get_assets_dir, ASSETS_PREFIX
geniuslib/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ # GeniusLib - Clash of Clans API wrapper
2
+ # (c) 2026 AkumaHalls / ClashGenius
3
+
4
+ import asyncio
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ asyncio.run(main())
geniuslib/abc.py ADDED
@@ -0,0 +1,274 @@
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 pathlib import Path
6
+ from typing import AsyncIterator, Type, TYPE_CHECKING
7
+
8
+ from .miscmodels import try_enum, Badge
9
+ from .iterators import PlayerIterator
10
+
11
+ if TYPE_CHECKING:
12
+ from .players import Player
13
+
14
+
15
+ class BaseClan:
16
+ """
17
+ Abstract data class that represents base Clan objects
18
+
19
+ Attributes
20
+ ----------
21
+ tag: :class:`str`
22
+ The clan's tag
23
+ name: :class:`str`
24
+ The clan's name
25
+ badge: :class:`Badge`
26
+ The clan's badge
27
+ level: :class:`int`
28
+ The clan's level.
29
+ """
30
+ __slots__ = ("tag", "name", "_client", "badge", "level", "_response_retry", "_raw_data")
31
+
32
+ def __init__(self, *, data, client, **kwargs):
33
+ self._client = client
34
+
35
+ self._response_retry = data.get("_response_retry")
36
+ self.tag = data.get("tag")
37
+ self.name = data.get("name")
38
+ self.badge = try_enum(Badge, data=data.get("badgeUrls"),
39
+ client=self._client)
40
+ self.level = data.get("clanLevel")
41
+ self._raw_data = data if client and client.raw_attribute else None
42
+
43
+ def __str__(self):
44
+ return self.name
45
+
46
+ def __repr__(self):
47
+ return f"<{self.__class__.__name__} tag={self.tag} name={self.name}>"
48
+
49
+ def __eq__(self, other):
50
+ return isinstance(other, BaseClan) and self.tag == other.tag
51
+
52
+ @property
53
+ def share_link(self) -> str:
54
+ """str: A formatted link to open the clan in-game"""
55
+ return f"https://link.clashofclans.com/en?action=OpenClanProfile&tag=%23{self.tag.strip('#')}"
56
+
57
+ @property
58
+ def members(self):
59
+ # pylint: disable=missing-function-docstring
60
+ return NotImplemented
61
+
62
+ def get_detailed_members(self, cls: Type["Player"] = None,
63
+ load_game_data: bool = None) -> AsyncIterator["Player"]:
64
+ """Get detailed player information for every player in the clan.
65
+
66
+ This returns a :class:`PlayerIterator` which fetches all player
67
+ tags in the clan in parallel.
68
+
69
+ Example
70
+ ---------
71
+
72
+ .. code-block:: python3
73
+
74
+ clan = await client.get_clan(clan_tag)
75
+
76
+ async for player in clan.get_detailed_members():
77
+ print(player.name)
78
+
79
+
80
+ Yields
81
+ ------
82
+ :class:`Player`
83
+ A full player object of a clan member.
84
+ """
85
+ if self.members is NotImplemented:
86
+ return NotImplemented
87
+ if load_game_data and not isinstance(load_game_data, bool):
88
+ raise TypeError("load_game_data must be either True or False.")
89
+
90
+ return PlayerIterator(self._client,
91
+ (p.tag for p in self.members),
92
+ cls=cls,
93
+ load_game_data=load_game_data,
94
+ members=self.members_dict)
95
+
96
+
97
+ class BasePlayer:
98
+ """An ABC that implements some common operations on players, regardless of type.
99
+
100
+ Attributes
101
+ ----------
102
+ tag: :class:`str`
103
+ The player's tag
104
+ name: :class:`str`
105
+ The player's name
106
+ """
107
+
108
+ __slots__ = ("tag", "name", "_client", "_response_retry", "_raw_data")
109
+
110
+ def __str__(self):
111
+ return self.name
112
+
113
+ def __repr__(self):
114
+ return f"<{self.__class__.__name__} tag={self.tag} name={self.name}>"
115
+
116
+ def __eq__(self, other):
117
+ return isinstance(other, BasePlayer) and self.tag == other.tag
118
+
119
+ def __init__(self, *, data, client, **_):
120
+ self._client = client
121
+ self._response_retry = data.get("_response_retry")
122
+ self._raw_data = data if client and client.raw_attribute else None
123
+ self.tag = data.get("tag")
124
+ self.name = data.get("name")
125
+
126
+ @property
127
+ def share_link(self) -> str:
128
+ """:class:`str` - A formatted link to open the player in-game"""
129
+ return f"https://link.clashofclans.com/en?action=OpenPlayerProfile&tag=%23{self.tag.strip('#')}"
130
+
131
+
132
+ class BaseDataClass:
133
+ """Base class for simple data classes with standard repr.
134
+
135
+ This class provides a default __repr__ implementation that displays
136
+ the name and id attributes if they exist.
137
+ """
138
+
139
+ __slots__ = ()
140
+
141
+ def __repr__(self):
142
+ attrs = [
143
+ ("name", getattr(self, "name", "Unknown")),
144
+ ("id", getattr(self, "id", None)),
145
+ ]
146
+ # Filter out None values
147
+ attrs = [(k, v) for k, v in attrs if v is not None]
148
+ return "<%s %s>" % (
149
+ self.__class__.__name__, " ".join("%s=%r" % t for t in attrs))
150
+
151
+
152
+ class LevelManager:
153
+ """Handles level property and level data loading.
154
+
155
+ This class manages the level state and triggers level-specific data loading
156
+ when the level changes. Subclasses must implement _load_level_data().
157
+
158
+ Attributes
159
+ ----------
160
+ level: :class:`int`
161
+ The current level of the unit.
162
+ max_level: :class:`int`
163
+ The maximum level this unit can reach.
164
+ is_max: :class:`bool`
165
+ Whether the unit is at its maximum level.
166
+ """
167
+
168
+ __slots__ = (
169
+ "_level",
170
+ "_static_data",
171
+ "max_level",
172
+ )
173
+
174
+ def __init__(self, initial_level: int, static_data: dict | None):
175
+
176
+ #if the level is set to 0, initalize it as the lowest available level
177
+ if initial_level == 0 and static_data:
178
+ min_level = 1
179
+ if static_data.get("levels"):
180
+ min_level = static_data["levels"][0].get("level")
181
+ initial_level = min_level
182
+
183
+ self._level: int = initial_level
184
+ self._static_data = static_data
185
+
186
+ self.max_level: int = len(static_data["levels"]) if static_data else 0
187
+
188
+ @property
189
+ def level(self) -> int:
190
+ """:class:`int`: The current level of the unit."""
191
+ return self._level
192
+
193
+ @level.setter
194
+ def level(self, value: int):
195
+ if not self._static_data:
196
+ self._level = value
197
+ return
198
+
199
+ if value < 1:
200
+ raise ValueError(f"Level must be greater than 1")
201
+
202
+ self._level = value
203
+ self._load_level_data()
204
+
205
+ @property
206
+ def is_max(self) -> bool:
207
+ """:class:`bool`: Whether the unit is at its maximum level."""
208
+ return self.max_level == self.level
209
+
210
+ def _load_level_data(self):
211
+ raise NotImplementedError("Subclasses must implement _load_level_data()")
212
+
213
+ def __getattribute__(self, name):
214
+ try:
215
+ return object.__getattribute__(self, name)
216
+ except AttributeError:
217
+ if name == "_raw_data":
218
+ raise AttributeError(
219
+ f"'{self.__class__.__name__}' object has no attribute '{name}'. "
220
+ f"This attribute requires client raw_attribute to be enabled and set to True."
221
+ f"Additionally must be an API data filled unit."
222
+ )
223
+ else:
224
+ raise AttributeError(
225
+ f"'{self.__class__.__name__}' object has no attribute '{name}'. "
226
+ f"This attribute requires static game data to be loaded. "
227
+ f"Use load_game_data=True when fetching this object."
228
+ )
229
+
230
+
231
+ def __repr__(self):
232
+ attrs = [
233
+ ("name", getattr(self, "name", "Unknown")),
234
+ ("level", self.level),
235
+ ]
236
+ if hasattr(self, "id"):
237
+ attrs.insert(1, ("id", self.id))
238
+ return "<%s %s>" % (
239
+ self.__class__.__name__, " ".join("%s=%r" % t for t in attrs))
240
+
241
+
242
+ class LeveledUnit(LevelManager):
243
+ """Base class for units with level-dependent data and additional utilities.
244
+
245
+ Extends LevelManager with townhall-specific level queries.
246
+ """
247
+
248
+ __slots__ = ()
249
+
250
+ def get_max_level_for_townhall(self, townhall: int) -> int | None:
251
+ """Get the maximum level for this unit at a given townhall level.
252
+
253
+ Parameters
254
+ ----------
255
+ townhall: :class:`int`
256
+ The townhall level to check.
257
+
258
+ Returns
259
+ -------
260
+ Optional[:class:`int`]
261
+ The maximum level this unit can reach at the given townhall level,
262
+ or ``None`` if no static data is available.
263
+ """
264
+ if not self._static_data or not self._static_data["levels"]:
265
+ return None
266
+
267
+ max_level = None
268
+ for level_data in self._static_data.get("levels", []):
269
+ if level_data.get("required_townhall", 0) <= townhall:
270
+ max_level = level_data.get("level")
271
+ else:
272
+ break
273
+
274
+ return max_level