From 49382b9bc10f9ce352fb433ff0585672fccdcb8b Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Fri, 12 Apr 2019 00:55:35 +0200 Subject: [PATCH] Working discord bot implementation! :O --- royalgames.py | 1 + royalnet/bots/discord.py | 42 +++++++++++++++--- royalnet/bots/telegram.py | 1 + royalnet/commands/error_handler.py | 6 +-- royalnet/database/alchemy.py | 2 +- royalnet/database/tables/activekvgroup.py | 21 +++++++-- royalnet/database/tables/aliases.py | 14 ++++-- royalnet/database/tables/diario.py | 54 ++++++++++++++++++----- royalnet/database/tables/discord.py | 29 +++++++++--- royalnet/database/tables/keygroup.py | 5 ++- royalnet/database/tables/keyvalue.py | 19 ++++++-- royalnet/database/tables/royals.py | 25 ++++++++--- royalnet/database/tables/telegram.py | 29 +++++++++--- royalnet/utils/call.py | 1 + 14 files changed, 197 insertions(+), 52 deletions(-) diff --git a/royalgames.py b/royalgames.py index 0d9856cb..7dafd123 100644 --- a/royalgames.py +++ b/royalgames.py @@ -18,6 +18,7 @@ tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, os. ds_bot = DiscordBot(os.environ["DS_AK"], "localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Discord, "discord_id", error_command=ErrorHandlerCommand) loop.create_task(master.run()) loop.create_task(tg_bot.run()) +loop.create_task(ds_bot.run()) print("Commands enabled:") print(tg_bot.generate_botfather_command_string()) print("Starting loop...") diff --git a/royalnet/bots/discord.py b/royalnet/bots/discord.py index 43dd7985..348272e6 100644 --- a/royalnet/bots/discord.py +++ b/royalnet/bots/discord.py @@ -2,6 +2,7 @@ import discord import asyncio import typing import logging as _logging +import sys from ..commands import NullCommand from ..utils import asyncify, Call, Command, UnregisteredError from ..network import RoyalnetLink, Message @@ -16,10 +17,6 @@ async def todo(message: Message): class DiscordBot: - # noinspection PyMethodParameters - class DiscordClient(discord.Client): - pass - def __init__(self, token: str, master_server_uri: str, @@ -32,13 +29,14 @@ class DiscordBot: missing_command: typing.Type[Command] = NullCommand, error_command: typing.Type[Command] = NullCommand): self.token = token - self.bot = self.DiscordClient() + self.missing_command = missing_command + self.error_command = error_command self.network: RoyalnetLink = RoyalnetLink(master_server_uri, master_server_secret, "discord", todo) # Generate commands self.commands = {} required_tables = set() for command in commands: - self.commands[f"/{command.command_name}"] = command + self.commands[f"!{command.command_name}"] = command required_tables = required_tables.union(command.require_alchemy_tables) # Generate the Alchemy database self.alchemy = Alchemy(database_uri, required_tables) @@ -51,6 +49,7 @@ class DiscordBot: class DiscordCall(Call): interface_name = "discord" interface_obj = self + interface_prefix = "!" alchemy = self.alchemy async def reply(call, text: str): @@ -85,6 +84,37 @@ class DiscordBot: self.DiscordCall = DiscordCall + # noinspection PyMethodParameters + class DiscordClient(discord.Client): + async def on_message(cli, message: discord.Message): + text = message.content + # Skip non-text messages + if not text: + return + # Find and clean parameters + command_text, *parameters = text.split(" ") + # Find the function + try: + selected_command = self.commands[command_text] + except KeyError: + # Skip inexistent commands + selected_command = self.missing_command + # Call the command + try: + return await self.DiscordCall(message.channel, selected_command, parameters, log, + message=message).run() + except Exception as exc: + try: + return await self.DiscordCall(message.channel, self.error_command, parameters, log, + message=message, + exception_info=sys.exc_info(), + previous_command=selected_command).run() + except Exception as exc2: + log.error(f"Exception in error handler command: {exc2}") + + self.DiscordClient = DiscordClient + self.bot = self.DiscordClient() + async def run(self): await self.bot.login(self.token) await self.bot.connect() diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index 236fd9bc..2c53e8fa 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -51,6 +51,7 @@ class TelegramBot: class TelegramCall(Call): interface_name = "telegram" interface_obj = self + interface_prefix = "/" alchemy = self.alchemy async def reply(call, text: str): diff --git a/royalnet/commands/error_handler.py b/royalnet/commands/error_handler.py index 70c6a521..de90fb5c 100644 --- a/royalnet/commands/error_handler.py +++ b/royalnet/commands/error_handler.py @@ -11,10 +11,6 @@ class ErrorHandlerCommand(Command): @classmethod async def common(cls, call: Call): - raise UnsupportedError() - - @classmethod - async def telegram(cls, call: Call): try: e_type, e_value, e_tb = call.kwargs["exception_info"] except InvalidInputError: @@ -22,7 +18,7 @@ class ErrorHandlerCommand(Command): return if e_type == InvalidInputError: command = call.kwargs["previous_command"] - await call.reply(f"⚠️ Sintassi non valida.\nSintassi corretta: [c]/{command.command_name} {command.command_syntax}[/c]") + await call.reply(f"⚠️ Sintassi non valida.\nSintassi corretta: [c]{call.interface_prefix}{command.command_name} {command.command_syntax}[/c]") return if e_type == UnregisteredError: await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!") diff --git a/royalnet/database/alchemy.py b/royalnet/database/alchemy.py index c9cdcbe9..ac1ce1a8 100644 --- a/royalnet/database/alchemy.py +++ b/royalnet/database/alchemy.py @@ -26,7 +26,7 @@ class Alchemy: except AttributeError: # Actually the intended result # TODO: here is the problem! - self.__setattr__(name, type(name, (self.Base,), cdj(table))) + self.__setattr__(name, type(name, (self.Base, table), {})) else: raise NameError(f"{name} is a reserved name and can't be used as a table name") self.Base.metadata.create_all() diff --git a/royalnet/database/tables/activekvgroup.py b/royalnet/database/tables/activekvgroup.py index 1974c247..d8918bc4 100644 --- a/royalnet/database/tables/activekvgroup.py +++ b/royalnet/database/tables/activekvgroup.py @@ -3,16 +3,29 @@ from sqlalchemy import Column, \ Integer, \ ForeignKey from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr +from .royals import Royal +from .keygroup import Keygroup class ActiveKvGroup: __tablename__ = "activekvgroups" - royal_id = Column(Integer, ForeignKey("royals.uid"), primary_key=True) - group_name = Column(String, ForeignKey("keygroups.group_name"), nullable=False) + @declared_attr + def royal_id(self): + return Column(Integer, ForeignKey("royals.uid"), primary_key=True) - royal = relationship("Royal", backref="active_kv_group") - group = relationship("Keygroup", backref="users_with_this_active") + @declared_attr + def group_name(self): + return Column(String, ForeignKey("keygroups.group_name"), nullable=False) + + @declared_attr + def royal(self): + return relationship("Royal", backref="active_kv_group") + + @declared_attr + def group(self): + return relationship("Keygroup", backref="users_with_this_active") def __repr__(self): return f"" diff --git a/royalnet/database/tables/aliases.py b/royalnet/database/tables/aliases.py index f88f79d7..d250596f 100644 --- a/royalnet/database/tables/aliases.py +++ b/royalnet/database/tables/aliases.py @@ -3,16 +3,24 @@ from sqlalchemy import Column, \ String, \ ForeignKey from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr from .royals import Royal class Alias: __tablename__ = "aliases" - royal_id = Column(Integer, ForeignKey("royals.uid")) - alias = Column(String, primary_key=True) + @declared_attr + def royal_id(self): + return Column(Integer, ForeignKey("royals.uid")) - royal = relationship("Royal", backref="aliases") + @declared_attr + def alias(self): + return Column(String, primary_key=True) + + @declared_attr + def royal(self): + return relationship("Royal", backref="aliases") def __repr__(self): return f"" diff --git a/royalnet/database/tables/diario.py b/royalnet/database/tables/diario.py index 69be1013..12067595 100644 --- a/royalnet/database/tables/diario.py +++ b/royalnet/database/tables/diario.py @@ -7,24 +7,56 @@ from sqlalchemy import Column, \ ForeignKey, \ String from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr from .royals import Royal class Diario: __tablename__ = "diario" - diario_id = Column(Integer, primary_key=True) - creator_id = Column(Integer, ForeignKey("royals.uid"), nullable=False) - quoted_account_id = Column(Integer, ForeignKey("royals.uid")) - quoted = Column(String) - text = Column(Text) - context = Column(Text) - timestamp = Column(DateTime, nullable=False) - media_url = Column(String) - spoiler = Column(Boolean, default=False) + @declared_attr + def diario_id(self): + return Column(Integer, primary_key=True) - creator = relationship("Royal", foreign_keys=creator_id, backref="diario_created") - quoted_account = relationship("Royal", foreign_keys=quoted_account_id, backref="diario_quoted") + @declared_attr + def creator_id(self): + return Column(Integer, ForeignKey("royals.uid"), nullable=False) + + @declared_attr + def quoted_account_id(self): + return Column(Integer, ForeignKey("royals.uid")) + + @declared_attr + def quoted(self): + return Column(String) + + @declared_attr + def text(self): + return Column(Text) + + @declared_attr + def context(self): + return Column(Text) + + @declared_attr + def timestamp(self): + return Column(DateTime, nullable=False) + + @declared_attr + def media_url(self): + return Column(String) + + @declared_attr + def spoiler(self): + return Column(Boolean, default=False) + + @declared_attr + def creator(self): + return relationship("Royal", foreign_keys=self.creator_id, backref="diario_created") + + @declared_attr + def quoted_account(self): + return relationship("Royal", foreign_keys=self.quoted_account_id, backref="diario_quoted") def __repr__(self): return f"" diff --git a/royalnet/database/tables/discord.py b/royalnet/database/tables/discord.py index 2acba58e..b259ea05 100644 --- a/royalnet/database/tables/discord.py +++ b/royalnet/database/tables/discord.py @@ -4,19 +4,36 @@ from sqlalchemy import Column, \ BigInteger, \ ForeignKey from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr from .royals import Royal class Discord: __tablename__ = "discord" - royal_id = Column(Integer, ForeignKey("royals.uid")) - discord_id = Column(BigInteger, primary_key=True) - username = Column(String) - discriminator = Column(String) - avatar_hash = Column(String) + @declared_attr + def royal_id(self): + return Column(Integer, ForeignKey("royals.uid")) - royal = relationship("Royal", backref="discord") + @declared_attr + def discord_id(self): + return Column(BigInteger, primary_key=True) + + @declared_attr + def username(self): + return Column(String) + + @declared_attr + def discriminator(self): + return Column(String) + + @declared_attr + def avatar_hash(self): + return Column(String) + + @declared_attr + def royal(self): + return relationship("Royal", backref="discord") def __repr__(self): return f"" diff --git a/royalnet/database/tables/keygroup.py b/royalnet/database/tables/keygroup.py index ed5557ce..a1dbf9c8 100644 --- a/royalnet/database/tables/keygroup.py +++ b/royalnet/database/tables/keygroup.py @@ -1,12 +1,15 @@ from sqlalchemy import Column, \ String, \ ForeignKey +from sqlalchemy.ext.declarative import declared_attr class Keygroup: __tablename__ = "keygroups" - group_name = Column(String, ForeignKey("keygroups.group_name"), primary_key=True) + @declared_attr + def group_name(self): + return Column(String, ForeignKey("keygroups.group_name"), primary_key=True) def __repr__(self): return f"" diff --git a/royalnet/database/tables/keyvalue.py b/royalnet/database/tables/keyvalue.py index d09e63c8..4d4859ae 100644 --- a/royalnet/database/tables/keyvalue.py +++ b/royalnet/database/tables/keyvalue.py @@ -2,17 +2,28 @@ from sqlalchemy import Column, \ String, \ ForeignKey from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr from .keygroup import Keygroup class Keyvalue: __tablename__ = "keyvalues" - group_name = Column(String, ForeignKey("keygroups.group_name"), primary_key=True) - key = Column(String, primary_key=True) - value = Column(String, nullable=False) + @declared_attr + def group_name(self): + return Column(String, ForeignKey("keygroups.group_name"), primary_key=True) - group = relationship("Keygroup") + @declared_attr + def key(self): + return Column(String, primary_key=True) + + @declared_attr + def value(self): + return Column(String, nullable=False) + + @declared_attr + def group(self): + return relationship("Keygroup") def __repr__(self): return f"" diff --git a/royalnet/database/tables/royals.py b/royalnet/database/tables/royals.py index 63a85f1b..71292c1b 100644 --- a/royalnet/database/tables/royals.py +++ b/royalnet/database/tables/royals.py @@ -2,16 +2,31 @@ from sqlalchemy import Column, \ Integer, \ String, \ LargeBinary +from sqlalchemy.ext.declarative import declared_attr class Royal: __tablename__ = "royals" - uid = Column(Integer, unique=True, primary_key=True) - username = Column(String, unique=True, nullable=False) - password = Column(LargeBinary) - role = Column(String, nullable=False) - avatar = Column(LargeBinary) + @declared_attr + def uid(self): + return Column(Integer, unique=True, primary_key=True) + + @declared_attr + def username(self): + return Column(String, unique=True, nullable=False) + + @declared_attr + def password(self): + return Column(LargeBinary) + + @declared_attr + def role(self): + return Column(String, nullable=False) + + @declared_attr + def avatar(self): + return Column(LargeBinary) def __repr__(self): return f"" diff --git a/royalnet/database/tables/telegram.py b/royalnet/database/tables/telegram.py index 79c79b57..5a8ba600 100644 --- a/royalnet/database/tables/telegram.py +++ b/royalnet/database/tables/telegram.py @@ -5,19 +5,36 @@ from sqlalchemy import Column, \ LargeBinary, \ ForeignKey from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr from .royals import Royal class Telegram: __tablename__ = "telegram" - royal_id = Column(Integer, ForeignKey("royals.uid")) - tg_id = Column(BigInteger, primary_key=True) - first_name = Column(String) - last_name = Column(String) - username = Column(String) + @declared_attr + def royal_id(self): + return Column(Integer, ForeignKey("royals.uid")) - royal = relationship("Royal", backref="telegram") + @declared_attr + def tg_id(self): + return Column(BigInteger, primary_key=True) + + @declared_attr + def first_name(self): + return Column(String) + + @declared_attr + def last_name(self): + return Column(String) + + @declared_attr + def username(self): + return Column(String) + + @declared_attr + def royal(self): + return relationship("Royal", backref="telegram") def __repr__(self): return f"" diff --git a/royalnet/utils/call.py b/royalnet/utils/call.py index 9d0afba2..a3e15f6a 100644 --- a/royalnet/utils/call.py +++ b/royalnet/utils/call.py @@ -20,6 +20,7 @@ class Call: # These parameters / methods should be overridden interface_name = NotImplemented interface_obj = NotImplemented + interface_prefix = NotImplemented alchemy: "Alchemy" = NotImplemented async def reply(self, text: str):