async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: async with data.session_acm() as session: author = await data.find_author(session=session, required=True) if "banker" not in author.roles: raise rc.UserError( "Non hai permessi sufficienti per eseguire questo comando." ) user_arg = args[0] qty_arg = args[1] reason_arg = " ".join(args[2:]) if user_arg is None: raise rc.InvalidInputError( "Non hai specificato un destinatario!") user = await rbt.User.find(self.alchemy, session, user_arg) if user is None: raise rc.InvalidInputError("L'utente specificato non esiste!") if qty_arg is None: raise rc.InvalidInputError("Non hai specificato una quantità!") try: qty = int(qty_arg) except ValueError: raise rc.InvalidInputError( "La quantità specificata non è un numero!") if reason_arg == "": raise rc.InvalidInputError("Non hai specificato un motivo!") await FiorygiTransaction.spawn_fiorygi(user, qty, reason_arg, data=data, session=session)
def _parse_args(args) -> Tuple[Optional[datetime.datetime], str, str]: """Parse command arguments, either using the standard syntax or the Proto syntax.""" try: timestring, title, description = args.match( r"(?:\[\s*([^]]+)\s*]\s*)?([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL) except rc.InvalidInputError: timestring, title, description = args.match( r"(?:\s*(.+?)\s*\n\s*)?([^\n]+)\s*\n?\s*(.+)?\s*", re.DOTALL) if timestring is not None: try: dt: typing.Optional[datetime.datetime] = dateparser.parse( timestring, settings={"PREFER_DATES_FROM": "future"}) except OverflowError: dt = None if dt is None: raise rc.InvalidInputError( "La data che hai specificato non è valida.") if dt <= datetime.datetime.now(): raise rc.InvalidInputError( "La data che hai specificato è nel passato.") if dt - datetime.datetime.now() >= datetime.timedelta(days=366): raise rc.InvalidInputError( "Hai specificato una data tra più di un anno!\n" "Se volevi scrivere un'orario, ricordati che le ore sono separate da " "due punti (:) e non da punto semplice!") else: dt = None return dt, title, description
async def call_herald_event(ci, destination: str, event_name: str, **kwargs) -> Dict: """Send a :class:`rh.Request` to a specific destination, and wait for a :class:`rh.Response`.""" if self.herald is None: raise rc.UnsupportedError("`royalherald` is not enabled on this Constellation.") request: rh.Request = rh.Request(handler=event_name, data=kwargs) response: rh.Response = await self.herald.request(destination=destination, request=request) if isinstance(response, rh.ResponseFailure): if response.name == "no_event": raise rc.CommandError(f"There is no event named {event_name} in {destination}.") elif response.name == "exception_in_event": # TODO: pretty sure there's a better way to do this if response.extra_info["type"] == "CommandError": raise rc.CommandError(response.extra_info["message"]) elif response.extra_info["type"] == "UserError": raise rc.UserError(response.extra_info["message"]) elif response.extra_info["type"] == "InvalidInputError": raise rc.InvalidInputError(response.extra_info["message"]) elif response.extra_info["type"] == "UnsupportedError": raise rc.UnsupportedError(response.extra_info["message"]) elif response.extra_info["type"] == "ConfigurationError": raise rc.ConfigurationError(response.extra_info["message"]) elif response.extra_info["type"] == "ExternalError": raise rc.ExternalError(response.extra_info["message"]) else: raise TypeError(f"Herald action call returned invalid error:\n" f"[p]{response}[/p]") elif isinstance(response, rh.ResponseSuccess): return response.data else: raise TypeError(f"Other Herald Link returned unknown response:\n" f"[p]{response}[/p]")
async def create(self, session, user: rbt.User, args: rc.CommandArgs, data: Optional[rc.CommandData] = None) -> Optional[Steam]: url = args.joined() steamid64 = await self._call(steam.steamid.steam64_from_url, url) if steamid64 is None: raise rc.InvalidInputError( "Quel link non è associato ad alcun account Steam.") response = await self._call(self._api.ISteamUser.GetPlayerSummaries_v2, steamids=steamid64) r = response["response"]["players"][0] steam_account = self.alchemy.get(Steam)( user=user, _steamid=int(steamid64), persona_name=r["personaname"], profile_url=r["profileurl"], avatar=r["avatarfull"], primary_clan_id=r["primaryclanid"], account_creation_date=datetime.datetime.fromtimestamp( r["timecreated"])) await FiorygiTransaction.spawn_fiorygi( user=user, qty=1, reason="aver collegato a Royalnet il proprio account di Steam", data=data, session=session, ) session.add(steam_account) return steam_account
async def create( self, session, user: rbt.User, args: rc.CommandArgs, data: Optional[rc.CommandData] = None) -> Optional[Brawlhalla]: raise rc.InvalidInputError( "Brawlhalla accounts are automatically linked from Steam.")
async def _change(self, unit: DndBattleUnit, args: List[str]): health = unit.health if args[0][0] == "s": health.deathsave_success() elif args[0][0] == "f": health.deathsave_failure() else: raise rc.InvalidInputError( f"Unknown result type [c]{args[0][0]}[/c].") unit.health = health
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: faction = Faction[args[0].upper()] name = args[1] initiative = int(args[2]) health = args[3] armor_class = int(args[4]) DndBattleUnitT = self.alchemy.get(DndBattleUnit) async with data.session_acm() as session: active_battle = await get_active_battle(session=session, data=data) if active_battle is None: raise rc.CommandError("No battle is active in this chat.") units_with_same_name = await ru.asyncify( session.query(DndBattleUnitT).filter_by( name=name, battle=active_battle.battle).all) if len(units_with_same_name) != 0: raise rc.InvalidInputError( "A unit with the same name already exists.") try: health = Health.from_text(health) except ValueError: raise rc.InvalidInputError("Invalid health string.") dbu = DndBattleUnitT(linked_character_id=None, initiative=initiative, faction=faction, name=name, health_string=health, armor_class=armor_class, battle=active_battle.battle) session.add(dbu) await ru.asyncify(session.commit) await data.reply(f"{dbu}\n" f"joins the battle!") if dbu.health.hidden: await data.delete_invoking()
async def call_herald_event(self, destination: str, event_name: str, **kwargs) -> Dict: """Send a :class:`royalherald.Request` to a specific destination, and wait for a :class:`royalherald.Response`.""" if self.herald is None: raise rc.UnsupportedError( "`royalherald` is not enabled on this serf.") request: rh.Request = rh.Request(handler=event_name, data=kwargs) response: rh.Response = await self.herald.request( destination=destination, request=request) if isinstance(response, rh.ResponseFailure): if response.name == "no_event": raise rc.ProgramError( f"There is no event named {event_name} in {destination}.") elif response.name == "error_in_event": if response.extra_info["type"] == "CommandError": raise rc.CommandError(response.extra_info["message"]) elif response.extra_info["type"] == "UserError": raise rc.UserError(response.extra_info["message"]) elif response.extra_info["type"] == "InvalidInputError": raise rc.InvalidInputError(response.extra_info["message"]) elif response.extra_info["type"] == "UnsupportedError": raise rc.UnsupportedError(response.extra_info["message"]) elif response.extra_info["type"] == "ConfigurationError": raise rc.ConfigurationError(response.extra_info["message"]) elif response.extra_info["type"] == "ExternalError": raise rc.ExternalError(response.extra_info["message"]) else: raise rc.ProgramError( f"Invalid error in Herald event '{event_name}':\n" f"[b]{response.extra_info['type']}[/b]\n" f"{response.extra_info['message']}") elif response.name == "unhandled_exception_in_event": raise rc.ProgramError( f"Unhandled exception in Herald event '{event_name}':\n" f"[b]{response.extra_info['type']}[/b]\n" f"{response.extra_info['message']}") else: raise rc.ProgramError( f"Unknown response in Herald event '{event_name}':\n" f"[b]{response.name}[/b]" f"[p]{response}[/p]") elif isinstance(response, rh.ResponseSuccess): return response.data else: raise rc.ProgramError( f"Other Herald Link returned unknown response:\n" f"[p]{response}[/p]")
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: faction = Faction[args[0].upper()] initiative_mod = int(args.optional(1, default="0")) async with data.session_acm() as session: DndBattleUnitT = self.alchemy.get(DndBattleUnit) active_battle = await get_active_battle(data=data, session=session) if active_battle is None: raise rc.CommandError("No battle is active in this chat.") active_character = await get_active_character(data=data, session=session) if active_character is None: raise rc.CommandError("You don't have an active character.") char: DndCharacter = active_character.character units_with_same_name = await ru.asyncify(session.query(DndBattleUnitT).filter_by( name=char.name, battle=active_battle.battle ).all) if len(units_with_same_name) != 0: raise rc.InvalidInputError("A unit with the same name already exists.") roll = random.randrange(1, 21) modifier = char.initiative + initiative_mod modifier_str = f"{modifier:+d}" if modifier != 0 else "" initiative = roll + modifier dbu = DndBattleUnitT( linked_character=char, initiative=initiative, faction=faction, name=char.name, health_string=f"{char.current_hp}/{char.max_hp}", armor_class=char.armor_class, battle=active_battle.battle ) session.add(dbu) await ru.asyncify(session.commit) await data.reply(f"{dbu}\n" f"joins the battle!\n" f"\n" f"🎲 1d20{modifier_str} = {roll}{modifier_str} = {initiative}")
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: async with data.session_acm() as session: author = await data.find_author(session=session, required=True) user_arg = args[0] qty_arg = args[1] if user_arg is None: raise rc.InvalidInputError( "Non hai specificato un destinatario!") user = await rbt.User.find(alchemy=self.alchemy, session=session, identifier=user_arg) if user is None: raise rc.InvalidInputError("L'utente specificato non esiste!") if user.uid == author.uid: raise rc.InvalidInputError( "Non puoi inviare fiorygi a te stesso!") if qty_arg is None: raise rc.InvalidInputError("Non hai specificato una quantità!") try: qty = int(qty_arg) except ValueError: raise rc.InvalidInputError( "La quantità specificata non è un numero!") if qty <= 0: raise rc.InvalidInputError( "La quantità specificata deve essere almeno 1!") if author.fiorygi.fiorygi < qty: raise rc.InvalidInputError( "Non hai abbastanza fiorygi per effettuare la transazione!" ) await FiorygiTransaction.spawn_fiorygi( author, -qty, f"aver ceduto fiorygi a {user}", data=data, session=session) await FiorygiTransaction.spawn_fiorygi( user, qty, f"aver ricevuto fiorygi da {author}", data=data, session=session)
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: target = args[0] async with data.session_acm() as session: units = await get_targets(target, data=data, session=session) if len(units) == 0: raise rc.InvalidInputError( f"No targets found matching [c]{target}[/c].") for unit in units: await self._change(unit, args[1:]) await session.commit() message = [] for unit in units: message.append(f"{unit}") await data.reply("\n\n".join(message))
async def run(self): log.debug(f"Running task for: {self.mmid}") # Create a new session for the MMTask self._session = self.command.alchemy.Session() self._EventT = self.command.alchemy.get(MMEvent) self._ResponseT = self.command.alchemy.get(MMResponse) self._mmevent: MMEvent = self._session.query(self._EventT).get( self.mmid) if self._mmevent is None: raise rc.InvalidInputError( f"No event exists with the mmid {self.mmid}.") if self._mmevent.interface != "telegram": raise rc.UnsupportedError( "Currently only the Telegram interface is supported.") async with self.telegram_channel_message(): self.command.serf.tasks.add(self.wait_until_due()) # Sleep until something interrupts the task interrupt = await self.queue.get() # Mark the event as interrupted self._mmevent.interrupted = True self._session.commit() # Send a group notification if the MMEvent wasn't deleted if interrupt != Interrupts.MANUAL_DELETE: await self.telegram_group_message_start() else: await self.telegram_group_message_delete() # Close the database session await ru.asyncify(self._session.close)
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: if len(args) == 0: message = ["ℹ️ Comandi disponibili:"] for command in sorted(list(set(self.serf.commands.values())), key=lambda c: c.name): message.append(f"- [c]{self.serf.prefix}{command.name}[/c]") await data.reply("\n".join(message)) else: name: str = args[0].lstrip(self.serf.prefix) try: 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.serf.prefix}{command.name} {command.syntax}[/c]", "", f"{command.description}" ] await data.reply("\n".join(message))
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: async with data.session_acm() as session: author = await data.find_author(session=session, required=False) if author is not None: raise rc.UserError(f"This account is already connected to {author}!") username = args[0] password = "******".join(args[1:]) user = await data.find_user(session=session, identifier=username) if user is None: raise rc.UserError("No such user.") try: successful = user.test_password(password) except ValueError: raise rc.UserError(f"User {user} has no password set!") if not successful: raise rc.InvalidInputError(f"Invalid password!") if rst is not None and isinstance(self.serf, rst.TelegramSerf): import telegram message: telegram.Message = data.message from_user: telegram.User = message.from_user TelegramT = self.alchemy.get(Telegram) tg_user: Telegram = await ru.asyncify( session.query(TelegramT).filter_by(tg_id=from_user.id).one_or_none ) if tg_user is None: # Create tg_user = TelegramT( user=user, tg_id=from_user.id, first_name=from_user.first_name, last_name=from_user.last_name, username=from_user.username ) session.add(tg_user) else: # Edit tg_user.first_name = from_user.first_name tg_user.last_name = from_user.last_name tg_user.username = from_user.username await ru.asyncify(session.commit) await data.reply(f"↔️ Account {tg_user} synced to {user}!") elif rsd is not None and isinstance(self.serf, rsd.DiscordSerf): import discord message: discord.Message = data.message ds_author: discord.User = message.author DiscordT = self.alchemy.get(Discord) ds_user: Discord = await ru.asyncify( session.query(DiscordT).filter_by(discord_id=ds_author.id).one_or_none ) if ds_user is None: # Create # noinspection PyProtectedMember ds_user = DiscordT( user=user, discord_id=ds_author.id, username=ds_author.name, discriminator=ds_author.discriminator, avatar_url=ds_author.avatar_url._url ) session.add(ds_user) else: # Edit ds_user.username = ds_author.name ds_user.discriminator = ds_author.discriminator ds_user.avatar_url = ds_author.avatar_url await ru.asyncify(session.commit) await data.reply(f"↔️ Account {ds_user} synced to {ds_author}!") else: raise rc.UnsupportedError(f"Unknown interface: {self.serf.__class__.__qualname__}")
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: 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.find_author(session=session, required=True) # 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: 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 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.find_author(session=session, required=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_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)}")
async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: username = args[0] password = "******".join(args[1:]) author = await data.get_author(error_if_none=True) user = await data.find_user(username) try: successful = user.test_password(password) except ValueError: raise rc.UserError(f"User {user} has no password set!") if not successful: raise rc.InvalidInputError(f"Invalid password!") if self.interface.name == "telegram": import telegram message: telegram.Message = data.message from_user: telegram.User = message.from_user TelegramT = self.alchemy.get(Telegram) tg_user: Telegram = await ru.asyncify( data.session.query(TelegramT).filter_by( tg_id=from_user.id).one_or_none) if tg_user is None: # Create tg_user = TelegramT(user=author, tg_id=from_user.id, first_name=from_user.first_name, last_name=from_user.last_name, username=from_user.username) data.session.add(tg_user) else: # Edit tg_user.first_name = from_user.first_name tg_user.last_name = from_user.last_name tg_user.username = from_user.username await data.session_commit() await data.reply(f"↔️ Account {tg_user} synced to {author}!") elif self.interface.name == "discord": import discord message: discord.Message = data.message author: discord.User = message.author DiscordT = self.alchemy.get(Discord) ds_user: Discord = await ru.asyncify( data.session.query(DiscordT).filter_by( discord_id=author.id).one_or_none) if ds_user is None: # Create ds_user = DiscordT(user=author, discord_id=author.id, username=author.name, discriminator=author.discriminator, avatar_url=author.avatar_url) data.session.add(ds_user) else: # Edit ds_user.username = author.name ds_user.discriminator = author.discriminator ds_user.avatar_url = author.avatar_url await data.session_commit() await data.reply(f"↔️ Account {ds_user} synced to {author}!") elif self.interface.name == "matrix": raise rc.UnsupportedError( f"{self} hasn't been implemented for Matrix yet") else: raise rc.UnsupportedError( f"Unknown interface: {self.interface.name}")