From 202e33bdf0527fb0cfc77ba507e55826e263d6ad Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi <ste.pigozzi@gmail.com> Date: Wed, 3 Apr 2019 19:31:20 +0200 Subject: [PATCH] SO STUFF MUCH DOGE --- royalgames.py | 7 +++++-- royalnet/bots/telegram.py | 21 ++++++++++++++++++-- royalnet/commands/debug_author.py | 16 +++++++++++++++ royalnet/commands/diario.py | 18 +++++++++++++++-- royalnet/commands/sync.py | 7 ++++--- royalnet/database/__init__.py | 3 ++- royalnet/database/relationshiplinkchain.py | 22 +++++++++++++++++++++ royalnet/database/tables/__init__.py | 3 ++- royalnet/database/tables/aliases.py | 23 ++++++++++++++++++++++ royalnet/database/tables/diario.py | 8 ++++---- royalnet/database/tables/telegram.py | 1 - royalnet/utils/call.py | 5 +++++ 12 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 royalnet/commands/debug_author.py create mode 100644 royalnet/database/relationshiplinkchain.py create mode 100644 royalnet/database/tables/aliases.py diff --git a/royalgames.py b/royalgames.py index 3aa357ec..c198b58d 100644 --- a/royalgames.py +++ b/royalgames.py @@ -3,14 +3,17 @@ import asyncio from royalnet.bots import TelegramBot from royalnet.commands import PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, SyncCommand from royalnet.commands.debug_create import DebugCreateCommand +from royalnet.commands.debug_author import DebugAuthorCommand from royalnet.network import RoyalnetServer +from royalnet.database.tables import Royal, Telegram loop = asyncio.get_event_loop() -commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand] +commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand, + DebugAuthorCommand] master = RoyalnetServer("localhost", 1234, "sas") -tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, os.environ["DB_PATH"]) +tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Telegram, "tg_id") loop.create_task(master.run()) loop.create_task(tg_bot.run()) print("Starting loop...") diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index e7542094..dc69d438 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -5,8 +5,7 @@ import logging as _logging from ..commands import NullCommand from ..utils import asyncify, Call, Command from ..network import RoyalnetLink, Message -from ..database import Alchemy - +from ..database import Alchemy, relationshiplinkchain loop = asyncio.get_event_loop() log = _logging.getLogger(__name__) @@ -24,6 +23,9 @@ class TelegramBot: master_server_secret: str, commands: typing.List[typing.Type[Command]], database_uri: str, + master_table, + identity_table, + identity_column_name: str, missing_command: Command = NullCommand, error_command: Command = NullCommand): self.bot: telegram.Bot = telegram.Bot(api_key) @@ -40,6 +42,10 @@ class TelegramBot: required_tables = required_tables.union(command.require_alchemy_tables) # Generate the Alchemy database self.alchemy = Alchemy(database_uri, required_tables) + self.master_table = self.alchemy.__getattribute__(master_table.__name__) + self.identity_table = self.alchemy.__getattribute__(identity_table.__name__) + self.identity_column = self.identity_table.__getattribute__(self.identity_table, identity_column_name) + self.identity_chain = relationshiplinkchain(self.master_table, self.identity_table) # noinspection PyMethodParameters class TelegramCall(Call): @@ -54,6 +60,17 @@ class TelegramBot: response = await self.network.request(message, destination) return response + async def get_author(call): + update: telegram.Update = call.kwargs["update"] + user: telegram.User = update.effective_user + if user is None: + return None + query = call.session.query(self.master_table) + for link in self.identity_chain: + query = query.join(link.mapper.class_) + query = query.filter(self.identity_column == user.id) + return await asyncify(query.one_or_none) + self.Call = TelegramCall async def run(self): diff --git a/royalnet/commands/debug_author.py b/royalnet/commands/debug_author.py new file mode 100644 index 00000000..0e9799ff --- /dev/null +++ b/royalnet/commands/debug_author.py @@ -0,0 +1,16 @@ +from ..utils import Command, CommandArgs, Call +from ..database.tables import Royal, Telegram + + +class DebugAuthorCommand(Command): + + command_name = "debug_author" + command_title = "Ottieni informazioni sull'autore di questa chiamata." + + require_alchemy_tables = {Royal, Telegram} + + async def common(self, call: Call, args: CommandArgs): + author = await call.get_author() + if author is None: + await call.reply(f"โ๏ธ L'autore di questa chiamata รจ sconosciuto.") + await call.reply(f"๐ <code>{str(author)}</code> รจ l'autore di questa chiamata.") diff --git a/royalnet/commands/diario.py b/royalnet/commands/diario.py index f6a65867..97f3bb6c 100644 --- a/royalnet/commands/diario.py +++ b/royalnet/commands/diario.py @@ -1,5 +1,7 @@ -from ..utils import Command, CommandArgs, Call import re +import datetime +from ..utils import Command, CommandArgs, Call, InvalidInputError +from ..database.tables import Royal, Diario, Alias class DiarioCommand(Command): @@ -7,9 +9,21 @@ class DiarioCommand(Command): command_name = "diario" command_title = "Aggiungi una citazione al Diario." + require_alchemy_tables = {Royal, Diario, Alias} + async def common(self, call: Call, args: CommandArgs): # Recreate the full sentence text = " ".join(args) # Pass the sentence through the diario regex match = re.match(r'["ยซโโโโโโใ๏ผ`]([^"]+)["ยปโโโโใ๏ผ`] *(?:(?:-{1,2}|โ) *(\w+))?(?:,? *([^ ].*))?', text) - # TODO \ No newline at end of file + # Find the corresponding matches + if match is None: + raise InvalidInputError("No match found.") + text = match.group(1) + quoted = match.group(2) + context = match.group(3) + timestamp = datetime.datetime.now() + # Find if there's a Royalnet account associated with the quoted name + quoted_alias = call.session.query(call.alchemy.Alias).filter_by(alias=quoted).one_or_none() + quoted_account = quoted_alias.royal if quoted_alias is not None else None + # Find the creator of the quote \ No newline at end of file diff --git a/royalnet/commands/sync.py b/royalnet/commands/sync.py index aece8edb..89dcf63a 100644 --- a/royalnet/commands/sync.py +++ b/royalnet/commands/sync.py @@ -1,6 +1,6 @@ import typing from telegram import Update, User -from ..utils import Command, CommandArgs, Call, asyncify +from ..utils import Command, CommandArgs, Call, asyncify, UnsupportedError from ..database.tables import Royal, Telegram @@ -9,10 +9,10 @@ class SyncCommand(Command): command_name = "sync" command_title = "Connect your current account to Royalnet" - require_alchemy_tables = [Royal, Telegram] + require_alchemy_tables = {Royal, Telegram} async def common(self, call: Call, args: CommandArgs): - raise NotImplementedError() + raise UnsupportedError() async def telegram(self, call: Call, args: CommandArgs): update: Update = args.kwargs["update"] @@ -24,6 +24,7 @@ class SyncCommand(Command): royal = await asyncify(call.session.query(call.alchemy.Royal).filter_by(username=args[0]).one_or_none) if royal is None: await call.reply("โ ๏ธ Non esiste alcun account Royalnet con quel nome.") + return # Find if the user is already synced telegram = await asyncify(call.session.query(call.alchemy.Telegram).filter_by(tg_id=user.id).one_or_none) if telegram is None: diff --git a/royalnet/database/__init__.py b/royalnet/database/__init__.py index 9c7956f4..70a54417 100644 --- a/royalnet/database/__init__.py +++ b/royalnet/database/__init__.py @@ -1,3 +1,4 @@ from .alchemy import Alchemy +from .relationshiplinkchain import relationshiplinkchain -__all__ = ["Alchemy"] +__all__ = ["Alchemy", "relationshiplinkchain"] diff --git a/royalnet/database/relationshiplinkchain.py b/royalnet/database/relationshiplinkchain.py new file mode 100644 index 00000000..2ab72934 --- /dev/null +++ b/royalnet/database/relationshiplinkchain.py @@ -0,0 +1,22 @@ +import typing +from sqlalchemy.inspection import inspect + + +def relationshiplinkchain(starting_class, ending_class) -> typing.Optional[tuple]: + """Find the path to follow to get from the starting table to the ending table.""" + inspected = set() + + def search(_mapper, chain): + inspected.add(_mapper) + if _mapper.class_ == ending_class: + return chain + relationships = _mapper.relationships + for _relationship in set(relationships): + if _relationship.mapper in inspected: + continue + result = search(_relationship.mapper, chain + (_relationship,)) + if len(result) == 0: + return result + return () + + return search(inspect(starting_class), tuple()) diff --git a/royalnet/database/tables/__init__.py b/royalnet/database/tables/__init__.py index 15a9634c..dc48894b 100644 --- a/royalnet/database/tables/__init__.py +++ b/royalnet/database/tables/__init__.py @@ -1,5 +1,6 @@ from .royals import Royal from .telegram import Telegram from .diario import Diario +from .aliases import Alias -__all__ = ["Royal", "Telegram", "Diario"] +__all__ = ["Royal", "Telegram", "Diario", "Alias"] diff --git a/royalnet/database/tables/aliases.py b/royalnet/database/tables/aliases.py new file mode 100644 index 00000000..7f86ffa6 --- /dev/null +++ b/royalnet/database/tables/aliases.py @@ -0,0 +1,23 @@ +from sqlalchemy import Column, \ + Integer, \ + String, \ + BigInteger, \ + LargeBinary, \ + ForeignKey +from sqlalchemy.orm import relationship +from .royals import Royal + + +class Alias: + __tablename__ = "aliases" + + royal_id = Column(Integer, ForeignKey("royals.uid")) + alias = Column(String, primary_key=True) + + royal = relationship("Royal", backref="aliases") + + def __repr__(self): + return f"<Alias {str(self)}>" + + def __str__(self): + return f"{self.alias}->{self.royal_id}" diff --git a/royalnet/database/tables/diario.py b/royalnet/database/tables/diario.py index 8335c458..c3418cf9 100644 --- a/royalnet/database/tables/diario.py +++ b/royalnet/database/tables/diario.py @@ -14,9 +14,9 @@ class Diario: diario_id = Column(Integer, primary_key=True) - creator_id = Column(Integer, ForeignKey("royals.id")) - quoted_id = Column(Integer, ForeignKey("royals.id")) - quoted_name = Column(String) + creator_id = Column(Integer, ForeignKey("royals.uid")) + quoted_account_id = Column(Integer, ForeignKey("royals.uid")) + quoted = Column(String) text = Column(Text, nullable=False) context = Column(Text) timestamp = Column(DateTime, nullable=False) @@ -24,7 +24,7 @@ class Diario: spoiler = Column(Boolean, default=False) creator = relationship("Royal", foreign_keys=creator_id, backref="diario_created") - quoted = relationship("Royal", foreign_keys=quoted_id, backref="diario_quoted") + quoted_account = relationship("Royal", foreign_keys=quoted_account_id, backref="diario_quoted") def __repr__(self): return f"<Diario {self.diario_id}>" diff --git a/royalnet/database/tables/telegram.py b/royalnet/database/tables/telegram.py index 19a55c5d..e11c9f5f 100644 --- a/royalnet/database/tables/telegram.py +++ b/royalnet/database/tables/telegram.py @@ -17,7 +17,6 @@ class Telegram: tg_last_name = Column(String) tg_username = Column(String) tg_avatar = Column(LargeBinary) - # TODO: Add an index? https://www.citusdata.com/blog/2016/10/12/count-performance/ royal = relationship("Royal", backref="telegram") diff --git a/royalnet/utils/call.py b/royalnet/utils/call.py index c21c06a2..e722049a 100644 --- a/royalnet/utils/call.py +++ b/royalnet/utils/call.py @@ -26,6 +26,11 @@ class Call: The data must be pickleable.""" raise NotImplementedError() + async def get_author(self): + """Try to find the universal identifier of the user that sent the message. + That probably means, the database row identifying the user.""" + raise NotImplementedError() + # These parameters / methods should be left alone def __init__(self, channel, command: Command, *args, **kwargs): self.channel = channel