1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 11:34:18 +00:00

Start porting to 5.11 (sigh)

This commit is contained in:
Steffo 2020-08-20 03:20:53 +02:00
parent c1145550dd
commit 3f1c8a57d3
30 changed files with 746 additions and 1045 deletions

779
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
[tool.poetry]
name = "royalpack"
version = "5.13.9"
version = "5.14.0"
description = "A Royalnet command pack for the Royal Games community"
authors = ["Stefano Pigozzi <ste.pigozzi@gmail.com>"]
license = "AGPL-3.0+"
@ -28,13 +28,12 @@
itsdangerous = "^1.1.0"
[tool.poetry.dependencies.royalnet]
version = "~5.10.4"
version = "~5.11.0"
# Maybe... there is a way to make these selectable?
extras = [
"telegram",
"discord",
"alchemy_easy",
"bard",
"constellation",
"sentry",
"herald",

View file

@ -23,7 +23,6 @@ from .leagueoflegends import LeagueoflegendsCommand
from .magickfiorygi import MagickfiorygiCommand
from .magicktreasure import MagicktreasureCommand
from .matchmaking import MatchmakingCommand
from .peertubeupdates import PeertubeUpdatesCommand
from .ping import PingCommand
from .pmots import PmotsCommand
from .dog import DogCommand
@ -37,7 +36,6 @@ from .steammatch import SteammatchCommand
from .steampowered import SteampoweredCommand
from .treasure import TreasureCommand
from .trivia import TriviaCommand
from .userinfo import UserinfoCommand
from .osu import OsuCommand
# Enter the commands of your Pack here!
@ -66,7 +64,6 @@ available_commands = [
MagickfiorygiCommand,
MagicktreasureCommand,
MatchmakingCommand,
PeertubeUpdatesCommand,
PingCommand,
PmotsCommand,
DogCommand,
@ -80,7 +77,6 @@ available_commands = [
SteampoweredCommand,
TreasureCommand,
TriviaCommand,
UserinfoCommand,
OsuCommand,
]

View file

@ -14,37 +14,37 @@ log = logging.getLogger(__name__)
class LinkerCommand(rc.Command, metaclass=abc.ABCMeta):
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
self.updater_task = None
if self.enabled():
self.updater_task = self.loop.create_task(self.run_updater())
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
author = await data.get_author(error_if_none=True)
if len(args) == 0:
message = []
for obj in await self.get_updatables_of_user(session=data.session, user=author):
async def change(attribute: str, value: Any):
"""A shortcut for self.__change."""
await self._change(session=data.session,
obj=obj,
attribute=attribute,
new=value)
async with data.session_acm() as session:
if len(args) == 0:
message = []
for obj in await self.get_updatables_of_user(session=session, user=author):
async def change(attribute: str, value: Any):
"""A shortcut for self.__change."""
await self._change(session=session,
obj=obj,
attribute=attribute,
new=value)
await self.update(session=data.session, obj=obj, change=change)
message.append(self.describe(obj))
if len(message) == 0:
raise rc.UserError("Nessun account connesso.")
await data.session_commit()
await data.reply("\n".join(message))
else:
created = await self.create(session=data.session, user=author, args=args, data=data)
await data.session_commit()
if created is not None:
message = ["🔗 Account collegato!", "", self.describe(created)]
await self.update(session=session, obj=obj, change=change)
message.append(self.describe(obj))
if len(message) == 0:
raise rc.UserError("Nessun account connesso.")
await ru.asyncify(session.commit)
await data.reply("\n".join(message))
else:
created = await self.create(session=session, user=author, args=args, data=data)
await ru.asyncify(session.commit)
if created is not None:
message = ["🔗 Account collegato!", "", self.describe(created)]
await data.reply("\n".join(message))
def describe(self, obj: Updatable) -> str:
"""The text that should be appended to the report message for a given Updatable."""
@ -143,7 +143,7 @@ class LinkerCommand(rc.Command, metaclass=abc.ABCMeta):
def enabled(self) -> bool:
"""Whether the updater is enabled or not."""
return self.config[self.name]["updater"]["enabled"] and self.interface.name == "telegram"
return self.config[self.name]["updater"]["enabled"] and isinstance(self.serf, rst.TelegramSerf)
def period(self) -> int:
"""The time between two updater cycles."""

View file

@ -1,15 +1,16 @@
from typing import *
import telegram
from royalnet.commands import *
import royalnet.commands as rc
import royalnet.serf.telegram as rst
class CiaoruoziCommand(Command):
class CiaoruoziCommand(rc.Command):
name: str = "ciaoruozi"
description: str = "Saluta Ruozi, un leggendario essere che è tornato in Royal Games."
async def run(self, args: CommandArgs, data: CommandData) -> None:
if self.interface.name == "telegram":
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
if isinstance(self.serf, rst.TelegramSerf):
user: telegram.User = data.message.from_user
# Se sei Ruozi, salutati da solo!
if user.id == 112437036:

View file

@ -9,9 +9,6 @@ class CvCommand(Command):
syntax: str = "[a][o][n][d][h]"
def __init__(self, interface: CommandInterface):
super().__init__(interface)
def _render_member(self,
member,
display_nick: bool,
@ -92,7 +89,7 @@ class CvCommand(Command):
return f"{status}{voice} {name}{activity}\n"
async def run(self, args: CommandArgs, data: CommandData) -> None:
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_cv")
response: Dict[str, Any] = await self.serf.call_herald_event("discord", "discord_cv")
flags = args.optional(0, default="")
display_nicks = "n" in flags

View file

@ -4,6 +4,7 @@ import asyncio
import datetime
import royalnet.commands as rc
import royalnet.utils as ru
import royalnet.serf.discord as rsd
from ..tables import Cvstats
@ -18,14 +19,14 @@ class CvstatsCommand(rc.Command):
syntax: str = ""
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
if self.interface.name == "discord":
def __init__(self, serf, config):
super().__init__(serf=serf, config=config)
if isinstance(self.serf, rsd.DiscordSerf):
self.loop.create_task(self._updater(1800))
def _is_ryg_member(self, member: dict):
for role in member["roles"]:
if role["id"] == self.interface.config["Cv"]["displayed_role_id"]:
if role["id"] == self.config["Cv"]["displayed_role_id"]:
return True
return False
@ -33,7 +34,7 @@ class CvstatsCommand(rc.Command):
log.info(f"Gathering Cvstats...")
while True:
try:
response: Dict[str, Any] = await self.interface.call_herald_event("discord", "discord_cv")
response: Dict[str, Any] = await self.serf.call_herald_event("discord", "discord_cv")
except rc.ConfigurationError:
await asyncio.sleep(10)
continue
@ -118,7 +119,8 @@ class CvstatsCommand(rc.Command):
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
CvstatsT = self.alchemy.get(Cvstats)
cvstats = data.session.query(CvstatsT).order_by(CvstatsT.timestamp.desc()).first()
async with data.session_acm() as session:
cvstats = session.query(CvstatsT).order_by(CvstatsT.timestamp.desc()).first()
message = [
f" [b]Statistiche[/b]",

View file

@ -6,6 +6,7 @@ import aiohttp
import royalnet.commands as rc
import royalnet.utils as ru
import royalnet.backpack.tables as rbt
import royalnet.serf.telegram as rst
from ..tables import *
@ -38,169 +39,166 @@ class DiarioCommand(rc.Command):
syntax = "[!] \"{testo}\" --[autore], [contesto]"
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
if self.interface.name == "telegram":
message: telegram.Message = data.message
reply: telegram.Message = message.reply_to_message
creator = await data.get_author()
# noinspection PyUnusedLocal
quoted: Optional[str]
# noinspection PyUnusedLocal
text: Optional[str]
# noinspection PyUnusedLocal
context: Optional[str]
# noinspection PyUnusedLocal
timestamp: datetime.datetime
# noinspection PyUnusedLocal
media_url: Optional[str]
# noinspection PyUnusedLocal
spoiler: bool
if creator is None:
await data.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
return
if reply is not None:
# Get the message text
text = reply.text
# Check if there's an image associated with the reply
photosizes: Optional[List[telegram.PhotoSize]] = reply.photo
if photosizes:
# Text is a caption
text = reply.caption
media_url = await to_imgur(self.interface.config["Imgur"]["token"],
photosizes, text if text is not None else "")
else:
media_url = None
# Ensure there is a text or an image
if not (text or media_url):
raise rc.InvalidInputError("Il messaggio a cui hai risposto non contiene testo o immagini.")
# Find the Royalnet account associated with the sender
quoted_tg = await ru.asyncify(data.session.query(self.alchemy.get(rbt.Telegram))
.filter_by(tg_id=reply.from_user.id)
.one_or_none)
quoted_account = quoted_tg.user if quoted_tg is not None else None
# Find the quoted name to assign
quoted_user: telegram.User = reply.from_user
quoted = quoted_user.full_name
# Get the timestamp
timestamp = reply.date
# Set the other properties
spoiler = False
context = None
else:
# Get the current timestamp
timestamp = datetime.datetime.now()
# Get the message text
raw_text = " ".join(args)
# Check if there's an image associated with the reply
photosizes: Optional[List[telegram.PhotoSize]] = message.photo
if photosizes:
media_url = await to_imgur(self.interface.config["Imgur"]["token"],
photosizes, raw_text if raw_text is not None else "")
else:
media_url = None
# Parse the text, if it exists
if raw_text:
# Pass the sentence through the diario regex
match = re.match(
r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([^,]+))?(?:, *([^ ].*))?',
raw_text)
# Find the corresponding matches
if match is not None:
spoiler = bool(match.group(1))
text = match.group(2)
quoted = match.group(3)
context = match.group(4)
# Otherwise, consider everything part of the text
async with data.session_acm() as session:
if isinstance(self.serf, rst.TelegramSerf):
message: telegram.Message = data.message
reply: telegram.Message = message.reply_to_message
creator = await data.get_author()
# noinspection PyUnusedLocal
quoted: Optional[str]
# noinspection PyUnusedLocal
text: Optional[str]
# noinspection PyUnusedLocal
context: Optional[str]
# noinspection PyUnusedLocal
timestamp: datetime.datetime
# noinspection PyUnusedLocal
media_url: Optional[str]
# noinspection PyUnusedLocal
spoiler: bool
if creator is None:
await data.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!")
return
if reply is not None:
# Get the message text
text = reply.text
# Check if there's an image associated with the reply
photosizes: Optional[List[telegram.PhotoSize]] = reply.photo
if photosizes:
# Text is a caption
text = reply.caption
media_url = await to_imgur(self.config["Imgur"]["token"],
photosizes, text if text is not None else "")
else:
spoiler = False
text = raw_text
quoted = None
context = None
# Ensure there's a quoted
if not quoted:
quoted = None
if not context:
context = None
# Find if there's a Royalnet account associated with the quoted name
if quoted is not None:
quoted_alias = await ru.asyncify(
data.session.query(self.alchemy.get(rbt.Alias))
.filter_by(alias=quoted.lower()).one_or_none
)
else:
quoted_alias = None
quoted_account = quoted_alias.user if quoted_alias is not None else None
else:
text = None
quoted = None
quoted_account = None
media_url = None
# Ensure there is a text or an image
if not (text or media_url):
raise rc.InvalidInputError("Il messaggio a cui hai risposto non contiene testo o immagini.")
# Find the Royalnet account associated with the sender
quoted_tg = await ru.asyncify(session.query(self.alchemy.get(rbt.Telegram))
.filter_by(tg_id=reply.from_user.id)
.one_or_none)
quoted_account = quoted_tg.user if quoted_tg is not None else None
# Find the quoted name to assign
quoted_user: telegram.User = reply.from_user
quoted = quoted_user.full_name
# Get the timestamp
timestamp = reply.date
# Set the other properties
spoiler = False
context = None
# Ensure there is a text or an image
if not (text or media_url):
else:
# Get the current timestamp
timestamp = datetime.datetime.now()
# Get the message text
raw_text = " ".join(args)
# Check if there's an image associated with the reply
photosizes: Optional[List[telegram.PhotoSize]] = message.photo
if photosizes:
media_url = await to_imgur(self.config["Imgur"]["token"],
photosizes, raw_text if raw_text is not None else "")
else:
media_url = None
# Parse the text, if it exists
if raw_text:
# Pass the sentence through the diario regex
match = re.match(
r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([^,]+))?(?:, *([^ ].*))?',
raw_text)
# Find the corresponding matches
if match is not None:
spoiler = bool(match.group(1))
text = match.group(2)
quoted = match.group(3)
context = match.group(4)
# Otherwise, consider everything part of the text
else:
spoiler = False
text = raw_text
quoted = None
context = None
# Ensure there's a quoted
if not quoted:
quoted = None
if not context:
context = None
# Find if there's a Royalnet account associated with the quoted name
if quoted is not None:
quoted_alias = await ru.asyncify(
session.query(self.alchemy.get(rbt.Alias))
.filter_by(alias=quoted.lower()).one_or_none
)
else:
quoted_alias = None
quoted_account = quoted_alias.user if quoted_alias is not None else None
else:
text = None
quoted = None
quoted_account = None
spoiler = False
context = None
# Ensure there is a text or an image
if not (text or media_url):
raise rc.InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
# Create the diario quote
diario = self.alchemy.get(Diario)(creator=creator,
quoted_account=quoted_account,
quoted=quoted,
text=text,
context=context,
timestamp=timestamp,
media_url=media_url,
spoiler=spoiler)
session.add(diario)
await ru.asyncify(session.commit)
await data.reply(f"{str(diario)}")
else:
# Find the creator of the quotes
creator = await data.get_author(error_if_none=True)
# Recreate the full sentence
raw_text = " ".join(args)
# Pass the sentence through the diario regex
match = re.match(r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([^,]+))?(?:, *([^ ].*))?',
raw_text)
# Find the corresponding matches
if match is not None:
spoiler = bool(match.group(1))
text = match.group(2)
quoted = match.group(3)
context = match.group(4)
# Otherwise, consider everything part of the text
else:
spoiler = False
text = raw_text
quoted = None
context = None
timestamp = datetime.datetime.now()
# Ensure there is some text
if not text:
raise rc.InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
# Create the diario quote
diario = self.alchemy.get(Diario)(creator=creator,
quoted_account=quoted_account,
quoted=quoted,
text=text,
context=context,
timestamp=timestamp,
media_url=media_url,
spoiler=spoiler)
data.session.add(diario)
await ru.asyncify(data.session.commit)
await data.reply(f"{str(diario)}")
else:
# Find the creator of the quotes
creator = await data.get_author(error_if_none=True)
# Recreate the full sentence
raw_text = " ".join(args)
# Pass the sentence through the diario regex
match = re.match(r'(!)? *["«‘“‛‟❛❝〝"`]([^"]+)["»’”❜❞〞"`] *(?:(?:-{1,2}|—) *([^,]+))?(?:, *([^ ].*))?',
raw_text)
# Find the corresponding matches
if match is not None:
spoiler = bool(match.group(1))
text = match.group(2)
quoted = match.group(3)
context = match.group(4)
# Otherwise, consider everything part of the text
else:
spoiler = False
text = raw_text
quoted = None
context = None
timestamp = datetime.datetime.now()
# Ensure there is some text
if not text:
raise rc.InvalidInputError("Manca il testo o l'immagine da inserire nel diario.")
# Or a quoted
if not quoted:
quoted = None
if not context:
context = None
# Find if there's a Royalnet account associated with the quoted name
if quoted is not None:
quoted_alias = await ru.asyncify(
data.session.query(self.alchemy.get(rbt.Alias))
.filter_by(alias=quoted.lower())
.one_or_none
)
else:
quoted_alias = None
quoted_account = quoted_alias.user if quoted_alias is not None else None
if quoted_alias is not None and quoted_account is None:
raise rc.UserError("Il nome dell'autore è ambiguo, quindi la riga non è stata aggiunta.\n"
"Per piacere, ripeti il comando con un nome più specifico!")
# Create the diario quote
diario = self.alchemy.Diario(creator=creator,
quoted_account=quoted_account,
quoted=quoted,
text=text,
context=context,
timestamp=timestamp,
media_url=None,
spoiler=spoiler)
data.session.add(diario)
await ru.asyncify(data.session.commit)
await data.reply(f"{str(diario)}")
# Or a quoted
if not quoted:
quoted = None
if not context:
context = None
# Find if there's a Royalnet account associated with the quoted name
if quoted is not None:
quoted_account = await rbt.User.find(self.alchemy, session, quoted)
else:
quoted_account = None
if quoted_account is None:
raise rc.UserError("Il nome dell'autore è ambiguo, quindi la riga non è stata aggiunta.\n"
"Per piacere, ripeti il comando con un nome più specifico!")
# Create the diario quote
DiarioT = self.alchemy.get(Diario)
diario = DiarioT(creator=creator,
quoted_account=quoted_account,
quoted=quoted,
text=text,
context=context,
timestamp=timestamp,
media_url=None,
spoiler=spoiler)
session.add(diario)
await ru.asyncify(session.commit)
await data.reply(f"{str(diario)}")

View file

@ -19,7 +19,8 @@ class DiarioquoteCommand(rc.Command):
entry_id = int(args[0].lstrip("#"))
except ValueError:
raise rc.CommandError("L'id che hai specificato non è valido.")
entry: Diario = await ru.asyncify(data.session.query(self.alchemy.get(Diario)).get, entry_id)
async with data.session_acm() as session:
entry: Diario = await ru.asyncify(session.query(self.alchemy.get(Diario)).get, entry_id)
if entry is None:
raise rc.CommandError("Nessuna riga con quell'id trovata.")
await data.reply(f" {entry}")

View file

@ -16,14 +16,11 @@ class DiarioshuffleCommand(rc.Command):
syntax = ""
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
DiarioT = self.alchemy.get(Diario)
entry: List[Diario] = await ru.asyncify(
data.session
.query(DiarioT)
.order_by(func.random())
.limit(1)
.one_or_none
)
if entry is None:
raise rc.CommandError("Nessuna riga del diario trovata.")
await data.reply(f" {entry}")
async with data.session_acm() as session:
DiarioT = self.alchemy.get(Diario)
entry: List[Diario] = await ru.asyncify(
session.query(DiarioT).order_by(func.random()).limit(1).one_or_none
)
if entry is None:
raise rc.CommandError("Nessuna riga del diario trovata.")
await data.reply(f" {entry}")

View file

@ -15,7 +15,7 @@ class EvalCommand(rc.Command):
user: rbt.User = await data.get_author(error_if_none=True)
if "admin" not in user.roles:
raise rc.CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
try:
result = eval(args.joined(require_at_least=1))
except Exception as e:

View file

@ -15,7 +15,7 @@ class ExecCommand(rc.Command):
user: rbt.User = await data.get_author(error_if_none=True)
if "admin" not in user.roles:
raise rc.CommandError("Non sei autorizzato a eseguire codice arbitrario!\n"
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
"(Sarebbe un po' pericoloso se te lo lasciassi eseguire, non trovi?)")
try:
exec(args.joined(require_at_least=1))
except Exception as e:

View file

@ -20,7 +20,8 @@ class GivefiorygiCommand(rc.Command):
if user_arg is None:
raise rc.InvalidInputError("Non hai specificato un destinatario!")
user = await rbt.User.find(self.alchemy, data.session, user_arg)
async with data.session_acm() as session:
user = await rbt.User.find(self.alchemy, session, user_arg)
if user is None:
raise rc.InvalidInputError("L'utente specificato non esiste!")
if user.uid == author.uid:

View file

@ -13,14 +13,14 @@ class GivetreasureCommand(MagicktreasureCommand):
syntax: str = "{codice} {valore}"
async def _permission_check(self, author, code, value, data):
async def _permission_check(self, author, code, value, data, session):
if author.fiorygi.fiorygi < value:
raise rc.UserError("Non hai abbastanza fiorygi per creare questo Treasure.")
async def _create_treasure(self, author, code, value, data):
async def _create_treasure(self, author, code, value, data, session):
TreasureT = self.alchemy.get(Treasure)
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
treasure = await ru.asyncify(session.query(TreasureT).get, code)
if treasure is not None:
raise rc.UserError("Esiste già un Treasure con quel codice.")

View file

@ -17,19 +17,19 @@ class HelpCommand(rc.Command):
]
for command in sorted(list(set(self.serf.commands.values())), key=lambda c: c.name):
message.append(f"- [c]{self.interface.prefix}{command.name}[/c]")
message.append(f"- [c]{self.serf.prefix}{command.name}[/c]")
await data.reply("\n".join(message))
else:
name: str = args[0].lstrip(self.interface.prefix)
name: str = args[0].lstrip(self.serf.prefix)
try:
command: rc.Command = self.serf.commands[f"{self.interface.prefix}{name}"]
command: rc.Command = self.serf.commands[f"{self.serf.prefix}{name}"]
except KeyError:
raise rc.InvalidInputError("Il comando richiesto non esiste.")
message = [
f" [c]{self.interface.prefix}{command.name} {command.syntax}[/c]",
f" [c]{self.serf.prefix}{command.name} {command.syntax}[/c]",
"",
f"{command.description}"
]

View file

@ -29,8 +29,8 @@ class LeagueoflegendsCommand(LinkerCommand):
"rank_flexq": "Flex",
}
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
self._lolwatcher: Optional[riotwatcher.RiotWatcher] = None
self._tftwatcher: Optional[riotwatcher.RiotWatcher] = None
if self.enabled():

View file

@ -23,7 +23,8 @@ class MagickfiorygiCommand(rc.Command):
if user_arg is None:
raise rc.InvalidInputError("Non hai specificato un destinatario!")
user = await rbt.User.find(self.alchemy, data.session, user_arg)
async with data.session_acm() as session:
user = await rbt.User.find(self.alchemy, session, user_arg)
if user is None:
raise rc.InvalidInputError("L'utente specificato non esiste!")

View file

@ -12,15 +12,15 @@ class MagicktreasureCommand(rc.Command):
syntax: str = "{codice} {valore}"
async def _permission_check(self, author, code, value, data):
async def _permission_check(self, author, code, value, data, session):
if "banker" not in author.roles:
raise rc.UserError("Non hai permessi sufficienti per eseguire questo comando.")
return author
async def _create_treasure(self, author, code, value, data):
async def _create_treasure(self, author, code, value, data, session):
TreasureT = self.alchemy.get(Treasure)
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
treasure = await ru.asyncify(session.query(TreasureT).get, code)
if treasure is not None:
raise rc.UserError("Esiste già un Treasure con quel codice.")
@ -44,10 +44,10 @@ class MagicktreasureCommand(rc.Command):
if value < 0:
raise rc.InvalidInputError("Il valore deve essere maggiore o uguale a 0.")
await self._permission_check(author, code, value, data)
treasure = await self._create_treasure(author, code, value, data)
data.session.add(treasure)
await data.session_commit()
async with data.session_acm() as session:
await self._permission_check(author, code, value, data, session)
treasure = await self._create_treasure(author, code, value, data, session)
session.add(treasure)
await ru.asyncify(session.commit)
await data.reply("✅ Treasure creato!")

View file

@ -3,7 +3,9 @@ import datetime
import re
import dateparser
import typing
import royalnet.utils as ru
import royalnet.commands as rc
import royalnet.serf.telegram as rst
from ..tables import MMEvent
from ..utils import MMTask
@ -18,20 +20,20 @@ class MatchmakingCommand(rc.Command):
aliases = ["mm", "lfg"]
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
# Find all active MMEvents and run the tasks for them
session = self.alchemy.Session()
# Create a new MMEvent and run it
if self.interface.name == "telegram":
if isinstance(self.serf, rst.TelegramSerf):
MMEventT = self.alchemy.get(MMEvent)
active_mmevents = (
session
.query(MMEventT)
.filter(
MMEventT.interface == self.interface.name,
MMEventT.interface == self.serf.interface_name,
MMEventT.interrupted == False
)
.all()
@ -74,13 +76,14 @@ class MatchmakingCommand(rc.Command):
dt, title, description = self._parse_args(args)
# Add the MMEvent to the database
mmevent: MMEvent = self.alchemy.get(MMEvent)(creator=author,
datetime=dt,
title=title,
description=description,
interface=self.interface.name)
data.session.add(mmevent)
await data.session_commit()
async with data.session_acm() as session:
mmevent: MMEvent = self.alchemy.get(MMEvent)(creator=author,
datetime=dt,
title=title,
description=description,
interface=self.serf.interface_name)
session.add(mmevent)
await ru.asyncify(session.commit)
# Create and run a task for the newly created MMEvent
task = MMTask(mmevent.mmid, command=self)

View file

@ -7,7 +7,6 @@ import royalnet.commands as rc
import royalnet.utils as ru
from .abstract.linker import LinkerCommand
from ..types import Updatable
from ..tables import Osu
from ..stars.api_auth_login_osu import ApiAuthLoginOsuStar

View file

@ -1,81 +0,0 @@
from typing import *
import aiohttp
import asyncio
import datetime
import logging
import dateparser
import royalnet.commands as rc
import royalnet.serf.telegram as rst
log = logging.getLogger(__name__)
class PeertubeUpdatesCommand(rc.Command):
name: str = "peertubeupdates"
description: str = "Guarda quando è uscito l'ultimo video su PeerTube."
aliases = ["ptu"]
_ready = asyncio.Event()
_latest_date: datetime.datetime = None
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
if self.interface.name == "telegram":
self.loop.create_task(self._ready_up())
self.loop.create_task(self._update())
async def _get_json(self):
log.debug("Getting jsonfeed")
async with aiohttp.ClientSession() as session:
async with session.get(self.config["Peertube"]["instance_url"] +
"/feeds/videos.json?sort=-publishedAt&filter=local") as response:
log.debug("Parsing jsonfeed")
if response.status != 200:
raise rc.ExternalError("Peertube is unavailable")
j = await response.json()
log.debug("Jsonfeed parsed successfully")
return j
async def _send(self, message):
client = self.interface.bot.client
await self.interface.bot.safe_api_call(client.send_message,
chat_id=self.config["Telegram"]["main_group_id"],
text=rst.escape(message),
parse_mode="HTML",
disable_webpage_preview=True)
async def _ready_up(self):
j = await self._get_json()
if j["version"] != "https://jsonfeed.org/version/1":
raise rc.ConfigurationError("url is not a jsonfeed")
videos = j["items"]
for video in reversed(videos):
date_modified = dateparser.parse(video["date_modified"])
if self._latest_date is None or date_modified > self._latest_date:
log.debug(f"Found newer video: {date_modified}")
self._latest_date = date_modified
self._ready.set()
async def _update(self):
await self._ready.wait()
while True:
j = await self._get_json()
videos = j["items"]
for video in reversed(videos):
date_modified = dateparser.parse(video["date_modified"])
if date_modified > self._latest_date:
log.debug(f"Found newer video: {date_modified}")
self._latest_date = date_modified
await self._send(f"🆕 Nuovo video su RoyalTube!\n"
f"[b]{video['title']}[/b]\n"
f"{video['url']}")
await asyncio.sleep(self.config["Peertube"]["feed_update_timeout"])
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
if self.interface.name != "telegram":
raise rc.UnsupportedError()
await data.reply(f" Ultimo video caricato il: [b]{self._latest_date.isoformat()}[/b]")

View file

@ -12,7 +12,7 @@ class PingCommand(rc.Command):
syntax: str = ""
_targets = ["telegram", "discord", "matrix", "constellation"]
_targets = ["telegram", "discord", "constellation"]
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
await data.reply("📶 Ping...")
@ -21,7 +21,7 @@ class PingCommand(rc.Command):
start = datetime.datetime.now()
for target in self._targets:
tasks[target] = self.loop.create_task(self.interface.call_herald_event(target, "pong"))
tasks[target] = self.loop.create_task(self.serf.call_herald_event(target, "pong"))
await asyncio.sleep(10)

View file

@ -7,8 +7,8 @@ import discord
from sqlalchemy import and_
import royalnet.commands as rc
import royalnet.utils as ru
from royalnet.serf.telegram import escape as telegram_escape
from royalnet.serf.discord import escape as discord_escape
import royalnet.serf.telegram as rst
import royalnet.serf.discord as rsd
from ..tables import Reminder
@ -22,34 +22,35 @@ class ReminderCommand(rc.Command):
syntax: str = "[ {data} ] {messaggio}"
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
session = interface.alchemy.Session()
def __init__(self, serf, config):
super().__init__(serf, config)
session = self.alchemy.Session()
reminders = (
session.query(interface.alchemy.get(Reminder))
session.query(self.alchemy.get(Reminder))
.filter(and_(
interface.alchemy.get(Reminder).datetime >= datetime.datetime.now(),
interface.alchemy.get(Reminder).interface_name == interface.name))
self.alchemy.get(Reminder).datetime >= datetime.datetime.now(),
self.alchemy.get(Reminder).interface_name == self.serf.interface_name))
.all()
)
for reminder in reminders:
interface.loop.create_task(self._remind(reminder))
self.loop.create_task(self._remind(reminder))
async def _remind(self, reminder):
await ru.sleep_until(reminder.datetime)
if self.interface.name == "telegram":
if isinstance(self.serf, rst.TelegramSerf):
chat_id: int = pickle.loads(reminder.interface_data)
client: telegram.Bot = self.serf.client
await self.serf.api_call(client.send_message,
chat_id=chat_id,
text=telegram_escape(f"❗️ {reminder.message}"),
text=rst.escape(f"❗️ {reminder.message}"),
parse_mode="HTML",
disable_web_page_preview=True)
elif self.interface.name == "discord":
elif isinstance(self.serf, rsd.DiscordSerf):
channel_id: int = pickle.loads(reminder.interface_data)
client: discord.Client = self.serf.client
channel = client.get_channel(channel_id)
await channel.send(discord_escape(f"❗️ {reminder.message}"))
await channel.send(rsd.escape(f"❗️ {reminder.message}"))
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
try:
@ -70,18 +71,19 @@ class ReminderCommand(rc.Command):
await data.reply("⚠️ La data che hai specificato è nel passato.")
return
await data.reply(f"✅ Promemoria impostato per [b]{date.strftime('%Y-%m-%d %H:%M:%S')}[/b]")
if self.interface.name == "telegram":
if isinstance(self.serf, rst.TelegramSerf):
interface_data = pickle.dumps(data.message.chat.id)
elif self.interface.name == "discord":
elif isinstance(self.serf, rsd.DiscordSerf):
interface_data = pickle.dumps(data.message.channel.id)
else:
raise rc.UnsupportedError("This command does not support the current interface.")
creator = await data.get_author()
reminder = self.interface.alchemy.get(Reminder)(creator=creator,
interface_name=self.interface.name,
interface_data=interface_data,
datetime=date,
message=reminder_text)
self.interface.loop.create_task(self._remind(reminder))
data.session.add(reminder)
await ru.asyncify(data.session.commit)
async with data.session_acm() as session:
reminder = self.alchemy.get(Reminder)(creator=creator,
interface_name=self.serf.interface_name,
interface_data=interface_data,
datetime=date,
message=reminder_text)
self.loop.create_task(self._remind(reminder))
session.add(reminder)
await ru.asyncify(session.commit)

View file

@ -16,10 +16,7 @@ class RoyalpackCommand(rc.Command):
return pkg_resources.get_distribution("royalpack").version
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
if __debug__:
message = f" Royalpack [url=https://github.com/Steffo99/royalpack/]Unreleased[/url]\n"
else:
message = f" Royalpack [url=https://github.com/Steffo99/royalpack/releases/tag/{self.royalpack_version}]{self.royalpack_version}[/url]\n"
message = f" Royalpack [url=https://github.com/Steffo99/royalpack/releases/tag/{self.royalpack_version}]{self.royalpack_version}[/url]\n"
if "69" in self.royalpack_version:
message += "(Nice.)"
await data.reply(message)

View file

@ -53,8 +53,8 @@ class SteammatchCommand(rc.Command):
syntax: str = "{royalnet_username}+"
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
self._api = steam.webapi.WebAPI(self.config["steampowered"]["token"])
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
@ -64,7 +64,8 @@ class SteammatchCommand(rc.Command):
users.append(author)
for arg in args:
user = await rbt.User.find(self.alchemy, data.session, arg)
async with data.session_acm() as session:
user = await rbt.User.find(self.alchemy, session, arg)
users.append(user)
if len(users) < 2:

View file

@ -22,8 +22,8 @@ class SteampoweredCommand(LinkerCommand):
syntax: str = "{url_profilo}"
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
self._api = steam.webapi.WebAPI(self.token())
def token(self):

View file

@ -16,16 +16,17 @@ class TreasureCommand(rc.Command):
author = await data.get_author(error_if_none=True)
code = args[0].lower()
TreasureT = self.alchemy.get(Treasure)
async with data.session_acm() as session:
TreasureT = self.alchemy.get(Treasure)
treasure = await ru.asyncify(data.session.query(TreasureT).get, code)
if treasure is None:
raise rc.UserError("Non esiste nessun Treasure con quel codice.")
if treasure.redeemed_by is not None:
raise rc.UserError(f"Quel tesoro è già stato riscattato da {treasure.redeemed_by}.")
treasure = await ru.asyncify(session.query(TreasureT).get, code)
if treasure is None:
raise rc.UserError("Non esiste nessun Treasure con quel codice.")
if treasure.redeemed_by is not None:
raise rc.UserError(f"Quel tesoro è già stato riscattato da {treasure.redeemed_by}.")
treasure.redeemed_by = author
await data.session_commit()
treasure.redeemed_by = author
await ru.asyncify(session.commit)
await FiorygiTransaction.spawn_fiorygi(data,
author,
treasure.value,

View file

@ -31,117 +31,116 @@ class TriviaCommand(rc.Command):
# _question_lock: bool = False
def __init__(self, interface: rc.CommandInterface):
super().__init__(interface)
def __init__(self, serf, config):
super().__init__(serf, config)
self._answerers: Dict[uuid.UUID, Dict[str, bool]] = {}
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
arg = args.optional(0)
if arg == "credits":
await data.reply(f" [c]{self.interface.prefix}{self.name}[/c] di [i]Steffo[/i]\n"
f"\n"
f"Tutte le domande vengono dall'[b]Open Trivia Database[/b] di [i]Pixeltail Games[/i],"
f" creatori di Tower Unite, e sono rilasciate sotto la licenza [b]CC BY-SA 4.0[/b].")
return
elif arg == "scores":
trivia_scores = await ru.asyncify(data.session.query(self.alchemy.get(TriviaScore)).all)
strings = ["🏆 [b]Trivia Leaderboards[/b]\n"]
for index, ts in enumerate(sorted(trivia_scores, key=lambda ts: -ts.score)):
if index > 3:
index = 3
strings.append(f"{self._medal_emojis[index]} {ts.user.username}: [b]{ts.score:.0f}p[/b]"
f" ({ts.correct_answers}/{ts.total_answers})")
await data.reply("\n".join(strings))
return
# if self._question_lock:
# raise rc.CommandError("C'è già un'altra domanda attiva!")
# self._question_lock = True
# Fetch the question
async with aiohttp.ClientSession() as session:
async with session.get("https://opentdb.com/api.php?amount=1") as response:
j = await response.json()
# Parse the question
if j["response_code"] != 0:
raise rc.CommandError(f"OpenTDB returned an error response_code ({j['response_code']}).")
question = j["results"][0]
text = f'❓ [b]{question["category"]}[/b]\n' \
f'{html.unescape(question["question"])}'
# Prepare answers
correct_answer: str = question["correct_answer"]
wrong_answers: List[str] = question["incorrect_answers"]
answers: List[str] = [correct_answer, *wrong_answers]
if question["type"] == "multiple":
random.shuffle(answers)
elif question["type"] == "boolean":
answers.sort(key=lambda a: a)
answers.reverse()
else:
raise NotImplementedError("Unknown question type")
# Find the correct index
for index, answer in enumerate(answers):
if answer == correct_answer:
correct_index = index
break
else:
raise ValueError("correct_index not found")
# Add emojis
for index, answer in enumerate(answers):
answers[index] = f"{self._letter_emojis[index]} {html.unescape(answers[index])}"
# Create the question id
question_id = uuid.uuid4()
self._answerers[question_id] = {}
# Create the correct and wrong functions
async def correct(data: rc.CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self._answerers[question_id][answerer_.uid] = True
except KeyError:
raise rc.UserError("Tempo scaduto!")
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
async def wrong(data: rc.CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self._answerers[question_id][answerer_.uid] = False
except KeyError:
raise rc.UserError("Tempo scaduto!")
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
# Add question
keyboard: List[rc.KeyboardKey] = []
for index, answer in enumerate(answers):
if index == correct_index:
keyboard.append(rc.KeyboardKey(interface=self.interface,
short=self._letter_emojis[index],
text=answers[index],
callback=correct))
async with data.session_acm() as session:
if arg == "credits":
await data.reply(f" [c]{self.serf.prefix}{self.name}[/c] di [i]Steffo[/i]\n"
f"\n"
f"Tutte le domande vengono dall'[b]Open Trivia Database[/b] di [i]Pixeltail Games[/i],"
f" creatori di Tower Unite, e sono rilasciate sotto la licenza [b]CC BY-SA 4.0[/b].")
return
elif arg == "scores":
trivia_scores = await ru.asyncify(session.query(self.alchemy.get(TriviaScore)).all)
strings = ["🏆 [b]Trivia Leaderboards[/b]\n"]
for index, ts in enumerate(sorted(trivia_scores, key=lambda ts: -ts.score)):
if index > 3:
index = 3
strings.append(f"{self._medal_emojis[index]} {ts.user.username}: [b]{ts.score:.0f}p[/b]"
f" ({ts.correct_answers}/{ts.total_answers})")
await data.reply("\n".join(strings))
return
# if self._question_lock:
# raise rc.CommandError("C'è già un'altra domanda attiva!")
# self._question_lock = True
# Fetch the question
async with aiohttp.ClientSession() as ws:
async with ws.get("https://opentdb.com/api.php?amount=1") as response:
j = await response.json()
# Parse the question
if j["response_code"] != 0:
raise rc.CommandError(f"OpenTDB returned an error response_code ({j['response_code']}).")
question = j["results"][0]
text = f'❓ [b]{question["category"]}[/b]\n' \
f'{html.unescape(question["question"])}'
# Prepare answers
correct_answer: str = question["correct_answer"]
wrong_answers: List[str] = question["incorrect_answers"]
answers: List[str] = [correct_answer, *wrong_answers]
if question["type"] == "multiple":
random.shuffle(answers)
elif question["type"] == "boolean":
answers.sort(key=lambda a: a)
answers.reverse()
else:
keyboard.append(rc.KeyboardKey(interface=self.interface,
short=self._letter_emojis[index],
text=answers[index],
callback=wrong))
async with data.keyboard(text=text, keys=keyboard):
await asyncio.sleep(self._answer_time)
results = f"❗️ Tempo scaduto!\n" \
f"La risposta corretta era [b]{answers[correct_index]}[/b]!\n\n"
for answerer_id in self._answerers[question_id]:
answerer = data.session.query(self.alchemy.get(rbt.users.User)).get(answerer_id)
if answerer.trivia_score is None:
ts = self.interface.alchemy.get(TriviaScore)(user=answerer)
data.session.add(ts)
await ru.asyncify(data.session.commit)
previous_score = answerer.trivia_score.score
if self._answerers[question_id][answerer_id]:
results += self._correct_emoji
answerer.trivia_score.correct_answers += 1
raise NotImplementedError("Unknown question type")
# Find the correct index
for index, answer in enumerate(answers):
if answer == correct_answer:
correct_index = index
break
else:
results += self._wrong_emoji
answerer.trivia_score.wrong_answers += 1
current_score = answerer.trivia_score.score
score_difference = current_score - previous_score
results += f" {answerer}: [b]{current_score:.0f}p[/b] ({score_difference:+.0f}p)\n"
await data.reply(results)
del self._answerers[question_id]
await ru.asyncify(data.session.commit)
# self._question_lock = False
raise ValueError("correct_index not found")
# Add emojis
for index, answer in enumerate(answers):
answers[index] = f"{self._letter_emojis[index]} {html.unescape(answers[index])}"
# Create the question id
question_id = uuid.uuid4()
self._answerers[question_id] = {}
# Create the correct and wrong functions
async def correct(data: rc.CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self._answerers[question_id][answerer_.uid] = True
except KeyError:
raise rc.UserError("Tempo scaduto!")
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
async def wrong(data: rc.CommandData):
answerer_ = await data.get_author(error_if_none=True)
try:
self._answerers[question_id][answerer_.uid] = False
except KeyError:
raise rc.UserError("Tempo scaduto!")
await data.reply("🆗 Hai risposto alla domanda. Ora aspetta un attimo per i risultati!")
# Add question
keyboard: List[rc.KeyboardKey] = []
for index, answer in enumerate(answers):
if index == correct_index:
keyboard.append(rc.KeyboardKey(short=self._letter_emojis[index],
text=answers[index],
callback=correct))
else:
keyboard.append(rc.KeyboardKey(short=self._letter_emojis[index],
text=answers[index],
callback=wrong))
async with data.keyboard(text=text, keys=keyboard):
await asyncio.sleep(self._answer_time)
results = f"❗️ Tempo scaduto!\n" \
f"La risposta corretta era [b]{answers[correct_index]}[/b]!\n\n"
for answerer_id in self._answerers[question_id]:
answerer = session.query(self.alchemy.get(rbt.users.User)).get(answerer_id)
if answerer.trivia_score is None:
ts = self.alchemy.get(TriviaScore)(user=answerer)
session.add(ts)
await ru.asyncify(session.commit)
previous_score = answerer.trivia_score.score
if self._answerers[question_id][answerer_id]:
results += self._correct_emoji
answerer.trivia_score.correct_answers += 1
else:
results += self._wrong_emoji
answerer.trivia_score.wrong_answers += 1
current_score = answerer.trivia_score.score
score_difference = current_score - previous_score
results += f" {answerer}: [b]{current_score:.0f}p[/b] ({score_difference:+.0f}p)\n"
await data.reply(results)
del self._answerers[question_id]
await ru.asyncify(session.commit)
# self._question_lock = False

View file

@ -1,74 +0,0 @@
from typing import *
import royalnet.commands as rc
import royalnet.backpack.tables as rbt
class UserinfoCommand(rc.Command):
name: str = "userinfo"
aliases = ["uinfo", "ui", "useri"]
description: str = "Visualizza informazioni su un utente."
syntax = "[username]"
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None:
username = args.optional(0)
if username is None:
user: rbt.User = await data.get_author(error_if_none=True)
else:
found: Optional[rbt.User] = await rbt.User.find(self.alchemy, data.session, username)
if not found:
raise rc.InvalidInputError("Utente non trovato.")
else:
user = found
r = [
f" [url=https://ryg.steffo.eu/#/user/{user.uid}]{user.username}[/url]",
f"{', '.join(user.roles)}",
]
if user.email:
r.append(f"{user.email}")
r.append("")
# Bios are a bit too long
# if user.bio:
# r.append(f"{user.bio}")
for account in user.telegram:
r.append(f"{account}")
for account in user.discord:
r.append(f"{account}")
for account in user.steam:
r.append(f"{account}")
if account.dota is not None:
r.append(f"{account.dota}")
if account.brawlhalla is not None:
r.append(f"{account.brawlhalla}")
for account in user.leagueoflegends:
r.append(f"{account}")
r.append("")
r.append(f"Ha creato [b]{len(user.diario_created)}[/b] righe di "
f"[url=https://ryg.steffo.eu/#/diario]Diario[/url], e vi compare in"
f" [b]{len(user.diario_quoted)}[/b] righe.")
r.append("")
if user.trivia_score:
r.append(f"Ha [b]{user.trivia_score.score:.0f}[/b] punti Trivia, avendo risposto correttamente a"
f" [b]{user.trivia_score.correct_answers}[/b] domande su"
f" [b]{user.trivia_score.total_answers}[/b].")
r.append("")
if user.fiorygi:
r.append(f"Ha [b]{user.fiorygi}[/b].")
r.append("")
await data.reply("\n".join(r))

View file

@ -4,6 +4,7 @@ import datetime
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declared_attr
import royalnet.utils as ru
from .fiorygi import Fiorygi
@ -48,35 +49,38 @@ class FiorygiTransaction:
@classmethod
async def spawn_fiorygi(cls, data: "CommandData", user, qty: int, reason: str):
if user.fiorygi is None:
data.session.add(data._interface.alchemy.get(Fiorygi)(
async with data.session_acm() as session:
session.add(data.alchemy.get(Fiorygi)(
user_id=user.uid,
fiorygi=0
))
await ru.asyncify(session.commit)
async with data.session_acm() as session:
transaction = data.alchemy.get(FiorygiTransaction)(
user_id=user.uid,
fiorygi=0
))
await data.session_commit()
change=qty,
reason=reason,
timestamp=datetime.datetime.now()
)
session.add(transaction)
transaction = data._interface.alchemy.get(FiorygiTransaction)(
user_id=user.uid,
change=qty,
reason=reason,
timestamp=datetime.datetime.now()
)
data.session.add(transaction)
user.fiorygi.fiorygi += qty
await ru.asyncify(session.commit)
user.fiorygi.fiorygi += qty
await data.session_commit()
if len(user.telegram) > 0:
user_str = user.telegram[0].mention()
else:
user_str = user.username
if len(user.telegram) > 0:
user_str = user.telegram[0].mention()
else:
user_str = user.username
if qty > 0:
msg = f"💰 [b]{user_str}[/b] ha ottenuto [b]{qty}[/b] fioryg{'i' if qty != 1 else ''} per [i]{reason}[/i]!"
elif qty == 0:
msg = f"❓ [b]{user_str}[/b] ha mantenuto i suoi fiorygi attuali per [i]{reason}[/i].\nWait, cosa?"
else:
msg = f"💸 [b]{user_str}[/b] ha perso [b]{-qty}[/b] fioryg{'i' if qty != -1 else ''} per [i]{reason}[/i]."
if qty > 0:
msg = f"💰 [b]{user_str}[/b] ha ottenuto [b]{qty}[/b] fioryg{'i' if qty != 1 else ''} per [i]{reason}[/i]!"
elif qty == 0:
msg = f"❓ [b]{user_str}[/b] ha mantenuto i suoi fiorygi attuali per [i]{reason}[/i].\nWait, cosa?"
else:
msg = f"💸 [b]{user_str}[/b] ha perso [b]{-qty}[/b] fioryg{'i' if qty != -1 else ''} per [i]{reason}[/i]."
await data._interface.call_herald_event("telegram", "telegram_message",
chat_id=data._interface.config["Telegram"]["main_group_id"],
text=msg)
await data.command.serf.call_herald_event(
"telegram", "telegram_message",
chat_id=data.command.config["Telegram"]["main_group_id"],
text=msg)