mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-27 13:34:28 +00:00
Add dota command
This commit is contained in:
parent
0b6139ac90
commit
7b4a3b8fb3
11 changed files with 365 additions and 32 deletions
|
@ -33,6 +33,7 @@ from .exec import ExecCommand
|
||||||
from .trivia import TriviaCommand
|
from .trivia import TriviaCommand
|
||||||
from .steampowered import SteampoweredCommand
|
from .steampowered import SteampoweredCommand
|
||||||
from .steammatch import SteammatchCommand
|
from .steammatch import SteammatchCommand
|
||||||
|
from .dota import DotaCommand
|
||||||
|
|
||||||
# Enter the commands of your Pack here!
|
# Enter the commands of your Pack here!
|
||||||
available_commands = [
|
available_commands = [
|
||||||
|
@ -69,7 +70,8 @@ available_commands = [
|
||||||
FunkwhaleCommand,
|
FunkwhaleCommand,
|
||||||
TriviaCommand,
|
TriviaCommand,
|
||||||
SteampoweredCommand,
|
SteampoweredCommand,
|
||||||
SteammatchCommand
|
SteammatchCommand,
|
||||||
|
DotaCommand,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Don't change this, it should automatically generate __all__
|
# Don't change this, it should automatically generate __all__
|
||||||
|
|
152
royalpack/commands/dota.py
Normal file
152
royalpack/commands/dota.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
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, Dota
|
||||||
|
from ..utils import DotaRank
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DotaCommand(Command):
|
||||||
|
name: str = "dota"
|
||||||
|
|
||||||
|
aliases = ["dota2", "doto", "doto2"]
|
||||||
|
|
||||||
|
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(dota: Dota) -> str:
|
||||||
|
string = f"ℹ️ [b]{dota.steam}[/b]\n"
|
||||||
|
|
||||||
|
if dota.rank:
|
||||||
|
string += f"{dota.rank}\n"
|
||||||
|
|
||||||
|
string += f"\n" \
|
||||||
|
f"Wins: [b]{dota.wins}[/b]\n" \
|
||||||
|
f"Losses: [b]{dota.losses}[/b]\n" \
|
||||||
|
f"\n"
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
|
async def _notify(self,
|
||||||
|
obj: Dota,
|
||||||
|
attribute_name: str,
|
||||||
|
old_value: Any,
|
||||||
|
new_value: Any):
|
||||||
|
if attribute_name == "wins":
|
||||||
|
if old_value is None:
|
||||||
|
message = f"↔️ Account {obj} connesso a {obj.steam.user}!"
|
||||||
|
await self._send(message)
|
||||||
|
elif attribute_name == "rank":
|
||||||
|
old_rank: Optional[DotaRank] = old_value
|
||||||
|
new_rank: Optional[DotaRank] = new_value
|
||||||
|
if new_rank > old_rank:
|
||||||
|
message = f"📈 [b]{obj.steam.user}[/b] è salito a [b]{new_value}[/b] su Dota 2! Congratulazioni!"
|
||||||
|
elif new_rank < old_rank:
|
||||||
|
message = f"📉 [b]{obj.steam.user}[/b] è sceso a [b]{new_value}[/b] su Dota 2."
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
await self._send(message)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _change(obj: Dota,
|
||||||
|
attribute_name: str,
|
||||||
|
new_value: Any,
|
||||||
|
callback: Callable[[Dota, 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):
|
||||||
|
log.info(f"Updating: {steam}")
|
||||||
|
log.debug(f"Getting player data from OpenDota...")
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
# Get profile data
|
||||||
|
async with session.get(f"https://api.opendota.com/api/players/{steam.steamid.as_32}/") as response:
|
||||||
|
if response.status != 200:
|
||||||
|
raise ExternalError(f"OpenDota / returned {response.status}!")
|
||||||
|
p = await response.json()
|
||||||
|
# No such user
|
||||||
|
if "profile" not in p:
|
||||||
|
log.debug(f"Not found: {steam}")
|
||||||
|
return
|
||||||
|
# Get win/loss data
|
||||||
|
async with session.get(f"https://api.opendota.com/api/players/{steam.steamid.as_32}/wl") as response:
|
||||||
|
if response.status != 200:
|
||||||
|
raise ExternalError(f"OpenDota /wl returned {response.status}!")
|
||||||
|
wl = await response.json()
|
||||||
|
# No such user
|
||||||
|
if wl["win"] == 0 and wl["lose"] == 0:
|
||||||
|
log.debug(f"Not found: {steam}")
|
||||||
|
return
|
||||||
|
# Find the Dota record, if it exists
|
||||||
|
dota: Dota = steam.dota
|
||||||
|
if dota is None:
|
||||||
|
dota = self.alchemy.get(Dota)(steam=steam)
|
||||||
|
db_session.add(dota)
|
||||||
|
db_session.flush()
|
||||||
|
await self._change(dota, "wins", wl["win"], self._notify)
|
||||||
|
await self._change(dota, "losses", wl["lose"], self._notify)
|
||||||
|
if p["rank_tier"]:
|
||||||
|
await self._change(dota, "rank", DotaRank(rank_tier=p["rank_tier"]), self._notify)
|
||||||
|
else:
|
||||||
|
await self._change(dota, "rank", None, self._notify)
|
||||||
|
|
||||||
|
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:
|
||||||
|
dota = steam.dota
|
||||||
|
if dota is None:
|
||||||
|
continue
|
||||||
|
found_something = True
|
||||||
|
await self._update(steam)
|
||||||
|
message += self._display(steam)
|
||||||
|
message += "\n"
|
||||||
|
if not found_something:
|
||||||
|
if len(author.leagueoflegends) == 0:
|
||||||
|
raise UserError("Nessun account di Dota 2 trovato.")
|
||||||
|
await data.reply(message)
|
|
@ -40,32 +40,31 @@ class LeagueoflegendsCommand(Command):
|
||||||
attribute_name: str,
|
attribute_name: str,
|
||||||
old_value: typing.Any,
|
old_value: typing.Any,
|
||||||
new_value: typing.Any):
|
new_value: typing.Any):
|
||||||
if self.interface.name == "telegram":
|
if isinstance(old_value, LeagueLeague):
|
||||||
if isinstance(old_value, LeagueLeague):
|
# This is a rank change!
|
||||||
# This is a rank change!
|
# Don't send messages for every rank change, send messages just if the TIER or RANK changes!
|
||||||
# Don't send messages for every rank change, send messages just if the TIER or RANK changes!
|
if old_value.tier == new_value.tier and old_value.rank == new_value.rank:
|
||||||
if old_value.tier == new_value.tier and old_value.rank == new_value.rank:
|
return
|
||||||
return
|
# Find the queue
|
||||||
# Find the queue
|
queue_names = {
|
||||||
queue_names = {
|
"rank_soloq": "Solo/Duo",
|
||||||
"rank_soloq": "Solo/Duo",
|
"rank_flexq": "Flex",
|
||||||
"rank_flexq": "Flex",
|
"rank_twtrq": "3v3",
|
||||||
"rank_twtrq": "3v3",
|
"rank_tftq": "TFT"
|
||||||
"rank_tftq": "TFT"
|
}
|
||||||
}
|
# Prepare the message
|
||||||
# Prepare the message
|
if new_value > old_value:
|
||||||
if new_value > old_value:
|
message = f"📈 [b]{obj.user}[/b] è salito a {new_value} su League of Legends " \
|
||||||
message = f"📈 [b]{obj.user}[/b] è salito a {new_value} su League of Legends " \
|
f"({queue_names[attribute_name]})! Congratulazioni!"
|
||||||
f"({queue_names[attribute_name]})! Congratulazioni!"
|
else:
|
||||||
else:
|
message = f"📉 [b]{obj.user}[/b] è sceso a {new_value} su League of Legends " \
|
||||||
message = f"📉 [b]{obj.user}[/b] è sceso a {new_value} su League of Legends " \
|
f"({queue_names[attribute_name]})."
|
||||||
f"({queue_names[attribute_name]})."
|
# Send the message
|
||||||
# Send the message
|
await self._send(message)
|
||||||
await self._send(message)
|
# Level up!
|
||||||
# Level up!
|
elif attribute_name == "summoner_level":
|
||||||
elif attribute_name == "summoner_level":
|
if new_value == 30 or (new_value >= 50 and (new_value % 25 == 0)):
|
||||||
if new_value == 30 or (new_value >= 50 and (new_value % 25 == 0)):
|
await self._send(f"🆙 [b]{obj.user}[/b] è salito al livello [b]{new_value}[/b] su League of Legends!")
|
||||||
await self._send(f"🆙 [b]{obj.user}[/b] è salito al livello [b]{new_value}[/b] su League of Legends!")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _change(obj: LeagueOfLegends,
|
async def _change(obj: LeagueOfLegends,
|
||||||
|
|
|
@ -10,11 +10,13 @@ class RageCommand(Command):
|
||||||
|
|
||||||
description: str = "Arrabbiati per qualcosa, come una software house californiana."
|
description: str = "Arrabbiati per qualcosa, come una software house californiana."
|
||||||
|
|
||||||
_MAD = ["MADDEN MADDEN MADDEN MADDEN",
|
_MAD = [
|
||||||
"EA bad, praise Geraldo!",
|
"MADDEN MADDEN MADDEN MADDEN",
|
||||||
"Stai sfogando la tua ira sul bot!",
|
"EA bad, praise Geraldo!",
|
||||||
"Basta, io cambio gilda!",
|
"Stai sfogando la tua ira sul bot!",
|
||||||
"Fondiamo la RRYG!"]
|
"Basta, io cambio gilda!",
|
||||||
|
"Fondiamo la RRYG!"
|
||||||
|
]
|
||||||
|
|
||||||
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
async def run(self, args: CommandArgs, data: CommandData) -> None:
|
||||||
await data.reply(f"😠 {random.sample(self._MAD, 1)[0]}")
|
await data.reply(f"😠 {random.sample(self._MAD, 1)[0]}")
|
||||||
|
|
|
@ -44,6 +44,8 @@ class UserinfoCommand(Command):
|
||||||
|
|
||||||
for account in user.steam:
|
for account in user.steam:
|
||||||
r.append(f"{account}")
|
r.append(f"{account}")
|
||||||
|
if account.dota is not None:
|
||||||
|
r.append(f"{account.dota}")
|
||||||
|
|
||||||
for account in user.leagueoflegends:
|
for account in user.leagueoflegends:
|
||||||
r.append(f"{account}")
|
r.append(f"{account}")
|
||||||
|
|
|
@ -11,6 +11,7 @@ from .mmresponse import MMResponse
|
||||||
from .leagueoflegends import LeagueOfLegends
|
from .leagueoflegends import LeagueOfLegends
|
||||||
from .fiorygi import Fiorygi
|
from .fiorygi import Fiorygi
|
||||||
from .steam import Steam
|
from .steam import Steam
|
||||||
|
from .dota import Dota
|
||||||
|
|
||||||
# Enter the tables of your Pack here!
|
# Enter the tables of your Pack here!
|
||||||
available_tables = [
|
available_tables = [
|
||||||
|
@ -26,6 +27,7 @@ available_tables = [
|
||||||
LeagueOfLegends,
|
LeagueOfLegends,
|
||||||
Fiorygi,
|
Fiorygi,
|
||||||
Steam,
|
Steam,
|
||||||
|
Dota,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Don't change this, it should automatically generate __all__
|
# Don't change this, it should automatically generate __all__
|
||||||
|
|
81
royalpack/tables/dota.py
Normal file
81
royalpack/tables/dota.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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
|
||||||
|
import steam
|
||||||
|
|
||||||
|
|
||||||
|
class Dota:
|
||||||
|
__tablename__ = "dota"
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def _steamid(self):
|
||||||
|
return Column(BigInteger, ForeignKey("steam._steamid"), primary_key=True)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def steam(self):
|
||||||
|
return relationship("Steam", backref=backref("dota", uselist=False))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def steamid(self):
|
||||||
|
return steam.SteamID(self._steamid)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def _rank_tier(self):
|
||||||
|
return Column(Integer)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def medal(self) -> Optional[DotaMedal]:
|
||||||
|
if self._rank_tier is None:
|
||||||
|
return None
|
||||||
|
return DotaMedal(self._rank_tier // 10)
|
||||||
|
|
||||||
|
@medal.setter
|
||||||
|
def medal(self, value: DotaMedal):
|
||||||
|
if not isinstance(value, DotaMedal):
|
||||||
|
raise AttributeError("medal can only be set to DotaMedal objects.")
|
||||||
|
self._rank_tier = value.value * 10 + self.stars.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stars(self) -> Optional[DotaStars]:
|
||||||
|
if self._rank_tier is None:
|
||||||
|
return None
|
||||||
|
return DotaStars(self._rank_tier % 10)
|
||||||
|
|
||||||
|
@stars.setter
|
||||||
|
def stars(self, value: DotaStars):
|
||||||
|
if not isinstance(value, DotaStars):
|
||||||
|
raise AttributeError("stars can only be set to DotaStars objects.")
|
||||||
|
self._rank_tier = self.medal.value * 10 + value.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rank(self) -> Optional[DotaRank]:
|
||||||
|
if self._rank_tier is None:
|
||||||
|
return None
|
||||||
|
return DotaRank(self.medal, self.stars)
|
||||||
|
|
||||||
|
@rank.setter
|
||||||
|
def rank(self, value: Optional[DotaRank]):
|
||||||
|
if value is None:
|
||||||
|
self._rank_tier = None
|
||||||
|
return
|
||||||
|
if not isinstance(value, DotaRank):
|
||||||
|
raise AttributeError("rank can only be set to DotaRank objects (or None).")
|
||||||
|
self._rank_tier = value.rank_tier
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def wins(self):
|
||||||
|
return Column(Integer)
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def losses(self):
|
||||||
|
return Column(Integer)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Dota account {self._steamid}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"[c]dota:{self._steamid}[/c]"
|
|
@ -4,6 +4,9 @@ from .leaguetier import LeagueTier
|
||||||
from .leaguerank import LeagueRank
|
from .leaguerank import LeagueRank
|
||||||
from .leagueleague import LeagueLeague
|
from .leagueleague import LeagueLeague
|
||||||
from .royalqueue import RoyalQueue
|
from .royalqueue import RoyalQueue
|
||||||
|
from .dotamedal import DotaMedal
|
||||||
|
from .dotastars import DotaStars
|
||||||
|
from .dotarank import DotaRank
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"MMChoice",
|
"MMChoice",
|
||||||
|
@ -13,4 +16,7 @@ __all__ = [
|
||||||
"LeagueRank",
|
"LeagueRank",
|
||||||
"LeagueLeague",
|
"LeagueLeague",
|
||||||
"RoyalQueue",
|
"RoyalQueue",
|
||||||
|
"DotaMedal",
|
||||||
|
"DotaStars",
|
||||||
|
"DotaRank",
|
||||||
]
|
]
|
||||||
|
|
21
royalpack/utils/dotamedal.py
Normal file
21
royalpack/utils/dotamedal.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class DotaMedal(enum.Enum):
|
||||||
|
HERALD = 1
|
||||||
|
GUARDIAN = 2
|
||||||
|
CRUSADER = 3
|
||||||
|
ARCHON = 4
|
||||||
|
LEGEND = 5
|
||||||
|
ANCIENT = 6
|
||||||
|
DIVINE = 7
|
||||||
|
IMMORTAL = 8
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name.capitalize()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__qualname__}.{self.name}"
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
46
royalpack/utils/dotarank.py
Normal file
46
royalpack/utils/dotarank.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
from .dotamedal import DotaMedal
|
||||||
|
from .dotastars import DotaStars
|
||||||
|
|
||||||
|
|
||||||
|
class DotaRank:
|
||||||
|
__slots__ = "medal", "stars"
|
||||||
|
|
||||||
|
def __init__(self, medal: DotaMedal = None, stars: DotaStars = None, *, rank_tier: int = None):
|
||||||
|
if rank_tier is not None:
|
||||||
|
self.medal: DotaMedal = DotaMedal(rank_tier // 10)
|
||||||
|
self.stars: DotaStars = DotaStars(rank_tier % 10)
|
||||||
|
else:
|
||||||
|
if medal is None or stars is None:
|
||||||
|
raise AttributeError("Missing medal, stars or rank_tier.")
|
||||||
|
self.medal = medal
|
||||||
|
self.stars = stars
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if other is None:
|
||||||
|
return True
|
||||||
|
if not isinstance(other, DotaRank):
|
||||||
|
raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}")
|
||||||
|
if self.medal > other.medal:
|
||||||
|
return True
|
||||||
|
elif self.medal < other.medal:
|
||||||
|
return False
|
||||||
|
elif self.stars > other.stars:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if other is None:
|
||||||
|
return False
|
||||||
|
if not isinstance(other, DotaRank):
|
||||||
|
raise TypeError(f"Can't compare {self.__class__.__qualname__} with {other.__class__.__qualname__}")
|
||||||
|
return self.medal == other.medal and self.stars == other.stars
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<{self.__class__.__qualname__}: {self.medal.name} {self.stars.name}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.medal} {self.stars}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rank_tier(self) -> int:
|
||||||
|
return (self.medal.value * 10 + self.stars.value)
|
20
royalpack/utils/dotastars.py
Normal file
20
royalpack/utils/dotastars.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
class DotaStars(enum.Enum):
|
||||||
|
I = 1
|
||||||
|
II = 2
|
||||||
|
III = 3
|
||||||
|
IV = 4
|
||||||
|
V = 5
|
||||||
|
VI = 6
|
||||||
|
VII = 7
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name.upper()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__qualname__}.{self.name}"
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self.value > other.value
|
Loading…
Reference in a new issue