diff --git a/royalpack/commands/__init__.py b/royalpack/commands/__init__.py index 85ca8b39..d3057a48 100644 --- a/royalpack/commands/__init__.py +++ b/royalpack/commands/__init__.py @@ -35,6 +35,7 @@ from .steampowered import SteampoweredCommand from .steammatch import SteammatchCommand from .dota import DotaCommand from .magickfiorygi import MagickfiorygiCommand +from .brawlhalla import BrawlhallaCommand # Enter the commands of your Pack here! available_commands = [ @@ -74,6 +75,7 @@ available_commands = [ SteammatchCommand, DotaCommand, MagickfiorygiCommand, + BrawlhallaCommand, ] # Don't change this, it should automatically generate __all__ diff --git a/royalpack/commands/brawlhalla.py b/royalpack/commands/brawlhalla.py new file mode 100644 index 00000000..8f0da522 --- /dev/null +++ b/royalpack/commands/brawlhalla.py @@ -0,0 +1,143 @@ +import asyncio +import logging +import sentry_sdk +import aiohttp +from typing import * +from royalnet.commands import * +from royalnet.utils import * +from royalnet.serf.telegram.escape import escape as tg_escape +from ..tables import Steam, Brawlhalla +from ..types import BrawlhallaRank, BrawlhallaMetal, BrawlhallaTier + +log = logging.getLogger(__name__) + + +class BrawlhallaCommand(Command): + name: str = "brawlhalla" + + aliases = ["bh", "bruhalla", "bruhlalla"] + + description: str = "Visualizza le tue statistiche di Dota!" + + syntax: str = "" + + def __init__(self, interface: CommandInterface): + super().__init__(interface) + if self.interface.name == "telegram": + self.loop.create_task(self._updater(900)) + + async def _send(self, message): + client = self.serf.client + await self.serf.api_call(client.send_message, + chat_id=self.config["Telegram"]["main_group_id"], + text=tg_escape(message), + parse_mode="HTML", + disable_webpage_preview=True) + + @staticmethod + def _display(bh: Brawlhalla) -> str: + string = f"ℹī¸ [b]{bh.name}[/b]\n\n" + + if bh.rank_1v1: + string += f"1v1: [b]{bh.rank_1v1}[/b]\n" + + return string + + async def _notify(self, + obj: Brawlhalla, + attribute_name: str, + old_value: Any, + new_value: Any): + if attribute_name == "rank_1v1": + old_rank: Optional[BrawlhallaRank] = old_value + new_rank: Optional[BrawlhallaRank] = new_value + if new_rank > old_rank: + message = f"📈 [b]{obj.steam.user}[/b] è salito a [b]{new_value}[/b] ({obj.rating_1v1} MMR) in 1v1 su Brawlhalla! Congratulazioni!" + elif new_rank < old_rank: + message = f"📉 [b]{obj.steam.user}[/b] è sceso a [b]{new_value}[/b] ({obj.rating_1v1} MMR) in 1v1 su Brawlhalla." + else: + return + await self._send(message) + + @staticmethod + async def _change(obj: Brawlhalla, + attribute_name: str, + new_value: Any, + callback: Callable[[Brawlhalla, str, Any, Any], Awaitable[None]]): + old_value = obj.__getattribute__(attribute_name) + if old_value != new_value: + await callback(obj, attribute_name, old_value, new_value) + obj.__setattr__(attribute_name, new_value) + + async def _update(self, steam: Steam, db_session): + BrawlhallaT = self.alchemy.get(Brawlhalla) + log.info(f"Updating: {steam}") + async with aiohttp.ClientSession() as session: + bh: Brawlhalla = steam.brawlhalla + if bh is None: + log.debug(f"Checking if player has an account...") + async with session.get(f"https://api.brawlhalla.com/search?steamid={steam.steamid.as_64}&api_key={self.config['Brawlhalla']['api_key']}") as response: + if response.status != 200: + raise ExternalError(f"Brawlhalla API /search returned {response.status}!") + j = await response.json() + if j == {} or j == []: + log.debug("No account found.") + return + bh = BrawlhallaT( + steam=steam, + brawlhalla_id=j["brawlhalla_id"], + name=j["name"] + ) + db_session.add(bh) + message = f"↔ī¸ Account {bh} connesso a {bh.steam.user}!" + await self._send(message) + async with session.get(f"https://api.brawlhalla.com/player/{bh.brawlhalla_id}/ranked?api_key={self.config['Brawlhalla']['api_key']}") as response: + if response.status != 200: + raise ExternalError(f"Brawlhalla API /ranked returned {response.status}!") + j = await response.json() + if j == {} or j == []: + log.debug("No ranked info found.") + else: + await self._change(bh, "rating_1v1", j["rating"], self._notify) + metal_name, tier_name = j["tier"].split(" ", 1) + metal = BrawlhallaMetal[metal_name.upper()] + tier = BrawlhallaTier(int(tier_name)) + rank = BrawlhallaRank(metal=metal, tier=tier) + await self._change(bh, "rank_1v1", rank, self._notify) + await asyncify(db_session.commit) + + async def _updater(self, period: int): + log.info(f"Started updater with {period}s period") + while True: + log.info(f"Updating...") + session = self.alchemy.Session() + log.info("") + steams = session.query(self.alchemy.get(Steam)).all() + for steam in steams: + try: + await self._update(steam, session) + except Exception as e: + sentry_sdk.capture_exception(e) + log.error(f"Error while updating {steam.user.username}: {e}") + await asyncio.sleep(1) + await asyncify(session.commit) + session.close() + log.info(f"Sleeping for {period}s") + await asyncio.sleep(period) + + async def run(self, args: CommandArgs, data: CommandData) -> None: + author = await data.get_author(error_if_none=True) + + found_something = False + + message = "" + for steam in author.steam: + await self._update(steam, data.session) + if steam.brawlhalla is None: + continue + found_something = True + message += self._display(steam.brawlhalla) + message += "\n" + if not found_something: + raise UserError("Nessun account di Brawlhalla trovato.") + await data.reply(message) diff --git a/royalpack/commands/dota.py b/royalpack/commands/dota.py index ff72c7d8..bfeea743 100644 --- a/royalpack/commands/dota.py +++ b/royalpack/commands/dota.py @@ -7,7 +7,7 @@ from royalnet.commands import * from royalnet.utils import * from royalnet.serf.telegram.escape import escape as tg_escape from ..tables import Steam, Dota -from ..utils import DotaRank +from ..types import DotaRank log = logging.getLogger(__name__) @@ -15,7 +15,7 @@ log = logging.getLogger(__name__) class DotaCommand(Command): name: str = "dota" - aliases = ["dota2", "doto", "doto2"] + aliases = ["dota2", "doto", "doto2", "dotka", "dotka2"] description: str = "Visualizza le tue statistiche di Dota!" diff --git a/royalpack/commands/leagueoflegends.py b/royalpack/commands/leagueoflegends.py index e5d05a1d..a54c7ee3 100644 --- a/royalpack/commands/leagueoflegends.py +++ b/royalpack/commands/leagueoflegends.py @@ -7,7 +7,7 @@ from royalnet.commands import * from royalnet.utils import * from royalnet.serf.telegram import * from ..tables import LeagueOfLegends -from ..utils import LeagueLeague +from ..types import LeagueLeague log = logging.getLogger(__name__) diff --git a/royalpack/tables/brawlhalla.py b/royalpack/tables/brawlhalla.py index df90e723..99d71cce 100644 --- a/royalpack/tables/brawlhalla.py +++ b/royalpack/tables/brawlhalla.py @@ -2,8 +2,10 @@ from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.ext.declarative import declared_attr import steam +from ..types import BrawlhallaRank, BrawlhallaTier, BrawlhallaMetal +# noinspection PyAttributeOutsideInit class Brawlhalla: __tablename__ = "brawlhalla" @@ -25,12 +27,33 @@ class Brawlhalla: @declared_attr def name(self): - return Column(String) + return Column(String, nullable=False) @declared_attr def rating_1v1(self): return Column(Integer) - @property + @declared_attr def tier_1v1(self): - return Column(String) + return Column(Enum(BrawlhallaTier)) + + @declared_attr + def metal_1v1(self): + return Column(Enum(BrawlhallaMetal)) + + @property + def rank_1v1(self): + return BrawlhallaRank(metal=self.metal_1v1, tier=self.tier_1v1) + + @rank_1v1.setter + def rank_1v1(self, value): + if not isinstance(value, BrawlhallaRank): + raise TypeError("rank_1v1 can only be set to BrawlhallaRank values.") + self.metal_1v1 = value.metal + self.tier_1v1 = value.tier + + def __repr__(self): + return f"" + + def __str__(self): + return f"[c]brawlhalla:{self.brawlhalla_id}[/c]" \ No newline at end of file diff --git a/royalpack/tables/dota.py b/royalpack/tables/dota.py index 3ec2100d..3c75b0b0 100644 --- a/royalpack/tables/dota.py +++ b/royalpack/tables/dota.py @@ -2,9 +2,7 @@ from typing import * from sqlalchemy import * from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declared_attr -from ..utils.dotamedal import DotaMedal -from ..utils.dotastars import DotaStars -from ..utils.dotarank import DotaRank +from ..types import DotaMedal, DotaStars, DotaRank import steam diff --git a/royalpack/tables/leagueoflegends.py b/royalpack/tables/leagueoflegends.py index 87763fc3..4636324a 100644 --- a/royalpack/tables/leagueoflegends.py +++ b/royalpack/tables/leagueoflegends.py @@ -1,7 +1,7 @@ from sqlalchemy import * from sqlalchemy.orm import relationship, composite from sqlalchemy.ext.declarative import declared_attr -from ..utils import LeagueRank, LeagueTier, LeagueLeague +from ..types import LeagueRank, LeagueTier, LeagueLeague class LeagueOfLegends: diff --git a/royalpack/types/__init__.py b/royalpack/types/__init__.py new file mode 100644 index 00000000..722f6b4e --- /dev/null +++ b/royalpack/types/__init__.py @@ -0,0 +1,27 @@ +from .mmchoice import MMChoice +from .mminterfacedata import MMInterfaceData, MMInterfaceDataTelegram +from .leaguetier import LeagueTier +from .leaguerank import LeagueRank +from .leagueleague import LeagueLeague +from .dotamedal import DotaMedal +from .dotastars import DotaStars +from .dotarank import DotaRank +from .brawlhallatier import BrawlhallaTier +from .brawlhallametal import BrawlhallaMetal +from .brawlhallarank import BrawlhallaRank + + +__all__ = [ + "MMChoice", + "MMInterfaceData", + "MMInterfaceDataTelegram", + "LeagueTier", + "LeagueRank", + "LeagueLeague", + "DotaMedal", + "DotaStars", + "DotaRank", + "BrawlhallaMetal", + "BrawlhallaRank", + "BrawlhallaTier", +] diff --git a/royalpack/types/brawlhallametal.py b/royalpack/types/brawlhallametal.py new file mode 100644 index 00000000..6ab20f92 --- /dev/null +++ b/royalpack/types/brawlhallametal.py @@ -0,0 +1,23 @@ +import enum + + +class BrawlhallaMetal(enum.Enum): + TIN = 0 + BRONZE = 1 + SILVER = 2 + GOLD = 3 + PLATINUM = 4 + DIAMOND = 5 + + def __str__(self): + return self.name.capitalize() + + def __repr__(self): + return f"{self.__class__.__qualname__}.{self.name}" + + def __gt__(self, other): + if other is None: + return True + if not isinstance(other, self.__class__): + raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") + return self.value > other.value diff --git a/royalpack/types/brawlhallarank.py b/royalpack/types/brawlhallarank.py new file mode 100644 index 00000000..f1419dc1 --- /dev/null +++ b/royalpack/types/brawlhallarank.py @@ -0,0 +1,36 @@ +from .brawlhallametal import BrawlhallaMetal +from .brawlhallatier import BrawlhallaTier + + +class BrawlhallaRank: + __slots__ = "metal", "tier" + + def __init__(self, metal: BrawlhallaMetal, tier: BrawlhallaTier): + self.metal: BrawlhallaMetal = metal + self.tier: BrawlhallaTier = tier + + def __gt__(self, other): + if other is None: + return True + if not isinstance(other, self.__class__): + raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") + if self.metal > other.metal: + return True + elif self.metal < other.metal: + return False + elif self.tier > other.tier: + return True + return False + + def __eq__(self, other): + if other is None: + return False + if not isinstance(other, self.__class__): + raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") + return self.metal == other.metal and self.tier == other.tier + + def __repr__(self): + return f"<{self.__class__.__qualname__}: {self.metal} {self.tier}>" + + def __str__(self): + return f"{self.metal} {self.tier}" diff --git a/royalpack/types/brawlhallatier.py b/royalpack/types/brawlhallatier.py new file mode 100644 index 00000000..cac66713 --- /dev/null +++ b/royalpack/types/brawlhallatier.py @@ -0,0 +1,23 @@ +import enum + + +class BrawlhallaTier(enum.Enum): + ZERO = 0 + I = 1 + II = 2 + III = 3 + IV = 4 + V = 5 + + def __str__(self): + return str(self.value) + + def __repr__(self): + return f"{self.__class__.__qualname__}.{self.name}" + + def __gt__(self, other): + if other is None: + return True + if not isinstance(other, self.__class__): + raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}") + return self.value > other.value diff --git a/royalpack/utils/dotamedal.py b/royalpack/types/dotamedal.py similarity index 100% rename from royalpack/utils/dotamedal.py rename to royalpack/types/dotamedal.py diff --git a/royalpack/utils/dotarank.py b/royalpack/types/dotarank.py similarity index 100% rename from royalpack/utils/dotarank.py rename to royalpack/types/dotarank.py diff --git a/royalpack/utils/dotastars.py b/royalpack/types/dotastars.py similarity index 100% rename from royalpack/utils/dotastars.py rename to royalpack/types/dotastars.py diff --git a/royalpack/utils/leagueleague.py b/royalpack/types/leagueleague.py similarity index 100% rename from royalpack/utils/leagueleague.py rename to royalpack/types/leagueleague.py diff --git a/royalpack/utils/leaguerank.py b/royalpack/types/leaguerank.py similarity index 100% rename from royalpack/utils/leaguerank.py rename to royalpack/types/leaguerank.py diff --git a/royalpack/utils/leaguetier.py b/royalpack/types/leaguetier.py similarity index 100% rename from royalpack/utils/leaguetier.py rename to royalpack/types/leaguetier.py diff --git a/royalpack/utils/mmchoice.py b/royalpack/types/mmchoice.py similarity index 100% rename from royalpack/utils/mmchoice.py rename to royalpack/types/mmchoice.py diff --git a/royalpack/utils/mminterfacedata.py b/royalpack/types/mminterfacedata.py similarity index 100% rename from royalpack/utils/mminterfacedata.py rename to royalpack/types/mminterfacedata.py diff --git a/royalpack/utils/__init__.py b/royalpack/utils/__init__.py index ba204e8f..814c9eed 100644 --- a/royalpack/utils/__init__.py +++ b/royalpack/utils/__init__.py @@ -1,24 +1,7 @@ -from .mmchoice import MMChoice -from .mminterfacedata import MMInterfaceData, MMInterfaceDataTelegram -from .leaguetier import LeagueTier -from .leaguerank import LeagueRank -from .leagueleague import LeagueLeague from .royalqueue import RoyalQueue -from .dotamedal import DotaMedal -from .dotastars import DotaStars -from .dotarank import DotaRank from .finduser import find_user_api __all__ = [ - "MMChoice", - "MMInterfaceData", - "MMInterfaceDataTelegram", - "LeagueTier", - "LeagueRank", - "LeagueLeague", "RoyalQueue", - "DotaMedal", - "DotaStars", - "DotaRank", "find_user_api", ]