Beispiel #1
0
    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)
Beispiel #2
0
 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
Beispiel #3
0
 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]")
Beispiel #4
0
    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
Beispiel #5
0
 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.")
Beispiel #6
0
 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
Beispiel #7
0
    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()
Beispiel #8
0
 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]")
Beispiel #9
0
    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}")
Beispiel #10
0
    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)
Beispiel #11
0
    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))
Beispiel #12
0
    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)
Beispiel #13
0
    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))
Beispiel #14
0
    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__}")
Beispiel #15
0
 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)}")
Beispiel #16
0
    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}")