From 6b4f39428bf522b3563928d27957e011ea2f6f40 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 09:12:48 +0200 Subject: [PATCH 1/8] Remove unused imports --- requirements.txt | 1 + royalnet/bots/telegram.py | 1 - royalnet/commands/color.py | 2 +- royalnet/commands/debug_author.py | 2 +- royalnet/commands/debug_create.py | 2 +- royalnet/commands/null.py | 2 +- royalnet/commands/ping.py | 2 +- royalnet/commands/ship.py | 2 +- royalnet/commands/smecds.py | 2 +- royalnet/commands/sync.py | 2 +- royalnet/database/tables/aliases.py | 2 -- 11 files changed, 9 insertions(+), 11 deletions(-) diff --git a/requirements.txt b/requirements.txt index 47259f29..c5203d50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ websockets>=7.0 pytest>=4.3.1 psycopg2-binary>=2.8 aiohttp>=3.5.4 +Markdown>=3.1 diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index 2d32564d..c3bd336b 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -3,7 +3,6 @@ import asyncio import typing import logging as _logging import sys -import re from ..commands import NullCommand from ..utils import asyncify, Call, Command from ..network import RoyalnetLink, Message diff --git a/royalnet/commands/color.py b/royalnet/commands/color.py index 4c920486..ff9a458f 100644 --- a/royalnet/commands/color.py +++ b/royalnet/commands/color.py @@ -1,4 +1,4 @@ -from ..utils import Command, CommandArgs, Call +from ..utils import Command, Call class ColorCommand(Command): diff --git a/royalnet/commands/debug_author.py b/royalnet/commands/debug_author.py index 7400ee85..145b184b 100644 --- a/royalnet/commands/debug_author.py +++ b/royalnet/commands/debug_author.py @@ -1,4 +1,4 @@ -from ..utils import Command, CommandArgs, Call +from ..utils import Command, Call from ..database.tables import Royal, Telegram diff --git a/royalnet/commands/debug_create.py b/royalnet/commands/debug_create.py index faaedfb2..3b06e7c1 100644 --- a/royalnet/commands/debug_create.py +++ b/royalnet/commands/debug_create.py @@ -1,4 +1,4 @@ -from ..utils import Command, CommandArgs, Call, asyncify +from ..utils import Command, Call, asyncify from ..database.tables import Royal, Alias diff --git a/royalnet/commands/null.py b/royalnet/commands/null.py index 441d090b..f30db9fb 100644 --- a/royalnet/commands/null.py +++ b/royalnet/commands/null.py @@ -1,4 +1,4 @@ -from ..utils import Command, CommandArgs, Call +from ..utils import Command, Call class NullCommand(Command): diff --git a/royalnet/commands/ping.py b/royalnet/commands/ping.py index 04ea42bd..df280dd7 100644 --- a/royalnet/commands/ping.py +++ b/royalnet/commands/ping.py @@ -1,5 +1,5 @@ import asyncio -from ..utils import Command, CommandArgs, Call, InvalidInputError +from ..utils import Command, Call, InvalidInputError class PingCommand(Command): diff --git a/royalnet/commands/ship.py b/royalnet/commands/ship.py index c0b014af..8ec4e661 100644 --- a/royalnet/commands/ship.py +++ b/royalnet/commands/ship.py @@ -1,5 +1,5 @@ import re -from ..utils import Command, CommandArgs, Call, safeformat +from ..utils import Command, Call, safeformat SHIP_RESULT = "💕 {one} + {two} = [b]{result}[/b]" diff --git a/royalnet/commands/smecds.py b/royalnet/commands/smecds.py index 7e8bc21c..6d5616d4 100644 --- a/royalnet/commands/smecds.py +++ b/royalnet/commands/smecds.py @@ -1,5 +1,5 @@ import random -from ..utils import Command, CommandArgs, Call, safeformat +from ..utils import Command, Call, safeformat DS_LIST = ["della secca", "del seccatore", "del secchiello", "del secchio", "del secchione", "del secondino", diff --git a/royalnet/commands/sync.py b/royalnet/commands/sync.py index 2dc7dadd..288ce3c1 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, UnsupportedError +from ..utils import Command, Call, asyncify, UnsupportedError from ..database.tables import Royal, Telegram diff --git a/royalnet/database/tables/aliases.py b/royalnet/database/tables/aliases.py index 7f86ffa6..f88f79d7 100644 --- a/royalnet/database/tables/aliases.py +++ b/royalnet/database/tables/aliases.py @@ -1,8 +1,6 @@ from sqlalchemy import Column, \ Integer, \ String, \ - BigInteger, \ - LargeBinary, \ ForeignKey from sqlalchemy.orm import relationship from .royals import Royal From 094a961b5153cd0877e4d5a8eb1e953d4c9919b3 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 10:39:19 +0200 Subject: [PATCH 2/8] Create dateparser command --- requirements.txt | 1 + royalgames.py | 5 +++-- .../commands/{debug_author.py => author.py} | 4 ++-- royalnet/commands/dateparser.py | 21 +++++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) rename royalnet/commands/{debug_author.py => author.py} (88%) create mode 100644 royalnet/commands/dateparser.py diff --git a/requirements.txt b/requirements.txt index c5203d50..7e107c29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ pytest>=4.3.1 psycopg2-binary>=2.8 aiohttp>=3.5.4 Markdown>=3.1 +dateparser>=0.7.1 diff --git a/royalgames.py b/royalgames.py index 8589584e..b94ff602 100644 --- a/royalgames.py +++ b/royalgames.py @@ -3,7 +3,8 @@ import asyncio from royalnet.bots import TelegramBot from royalnet.commands import PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, SyncCommand, DiarioCommand, RageCommand from royalnet.commands.debug_create import DebugCreateCommand -from royalnet.commands.debug_author import DebugAuthorCommand +from royalnet.commands.author import AuthorCommand +from royalnet.commands.dateparser import DateparserCommand from royalnet.commands.error_handler import ErrorHandlerCommand from royalnet.network import RoyalnetServer from royalnet.database.tables import Royal, Telegram @@ -11,7 +12,7 @@ from royalnet.database.tables import Royal, Telegram loop = asyncio.get_event_loop() commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand, - DebugAuthorCommand, DiarioCommand, RageCommand] + AuthorCommand, DiarioCommand, RageCommand, DateparserCommand] master = RoyalnetServer("localhost", 1234, "sas") tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Telegram, "tg_id", error_command=ErrorHandlerCommand) diff --git a/royalnet/commands/debug_author.py b/royalnet/commands/author.py similarity index 88% rename from royalnet/commands/debug_author.py rename to royalnet/commands/author.py index 145b184b..188ec55f 100644 --- a/royalnet/commands/debug_author.py +++ b/royalnet/commands/author.py @@ -2,9 +2,9 @@ from ..utils import Command, Call from ..database.tables import Royal, Telegram -class DebugAuthorCommand(Command): +class AuthorCommand(Command): - command_name = "debug_author" + command_name = "author" command_description = "Ottieni informazioni sull'autore di questa chiamata." command_syntax = "" diff --git a/royalnet/commands/dateparser.py b/royalnet/commands/dateparser.py new file mode 100644 index 00000000..c2dc401f --- /dev/null +++ b/royalnet/commands/dateparser.py @@ -0,0 +1,21 @@ +import datetime +import dateparser +from ..utils import Command, Call, InvalidInputError + + +class DateparserCommand(Command): + + command_name = "dateparser" + command_description = "Legge e comprende la data inserita." + command_syntax = "(data)" + + @classmethod + async def common(cls, call: Call): + if len(call.args) == 0: + raise InvalidInputError("Missing arg") + text = " ".join(call.args) + date: datetime.datetime = dateparser.parse(text) + if date is None: + await call.reply("🕕 La data inserita non è valida.") + return + await call.reply(f"🕐 La data inserita è {date.isoformat()}") From 12805f5bc13d77bfd8dc2d3e1118422b1532b747 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 11:15:27 +0200 Subject: [PATCH 3/8] Create reminder command --- royalgames.py | 6 ++---- royalnet/commands/__init__.py | 5 ++++- royalnet/commands/reminder.py | 27 +++++++++++++++++++++++++++ royalnet/utils/__init__.py | 3 ++- royalnet/utils/command.py | 8 ++++++++ 5 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 royalnet/commands/reminder.py diff --git a/royalgames.py b/royalgames.py index b94ff602..14a079a7 100644 --- a/royalgames.py +++ b/royalgames.py @@ -1,10 +1,8 @@ import os import asyncio from royalnet.bots import TelegramBot -from royalnet.commands import PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, SyncCommand, DiarioCommand, RageCommand +from royalnet.commands import * from royalnet.commands.debug_create import DebugCreateCommand -from royalnet.commands.author import AuthorCommand -from royalnet.commands.dateparser import DateparserCommand from royalnet.commands.error_handler import ErrorHandlerCommand from royalnet.network import RoyalnetServer from royalnet.database.tables import Royal, Telegram @@ -12,7 +10,7 @@ from royalnet.database.tables import Royal, Telegram loop = asyncio.get_event_loop() commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand, - AuthorCommand, DiarioCommand, RageCommand, DateparserCommand] + AuthorCommand, DiarioCommand, RageCommand, DateparserCommand, ReminderCommand] master = RoyalnetServer("localhost", 1234, "sas") tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Telegram, "tg_id", error_command=ErrorHandlerCommand) diff --git a/royalnet/commands/__init__.py b/royalnet/commands/__init__.py index 78f7657f..d9670135 100644 --- a/royalnet/commands/__init__.py +++ b/royalnet/commands/__init__.py @@ -7,7 +7,10 @@ from .color import ColorCommand from .sync import SyncCommand from .diario import DiarioCommand from .rage import RageCommand +from .dateparser import DateparserCommand +from .author import AuthorCommand +from .reminder import ReminderCommand __all__ = ["NullCommand", "PingCommand", "ShipCommand", "SmecdsCommand", "CiaoruoziCommand", "ColorCommand", - "SyncCommand", "DiarioCommand", "RageCommand"] + "SyncCommand", "DiarioCommand", "RageCommand", "DateparserCommand", "AuthorCommand", "ReminderCommand"] diff --git a/royalnet/commands/reminder.py b/royalnet/commands/reminder.py new file mode 100644 index 00000000..9b3882c1 --- /dev/null +++ b/royalnet/commands/reminder.py @@ -0,0 +1,27 @@ +import datetime +import dateparser +import typing +from ..utils import Command, Call, sleep_until + + +class ReminderCommand(Command): + + command_name = "reminder" + command_description = "Ripete quello che gli avevi chiesto dopo un po' di tempo." + command_syntax = "[ (data) ] (testo)" + + @classmethod + async def common(cls, call: Call): + match = call.args.match(r"\[ *(.+?) *] *(.+?) *$") + date_str = match.group(1) + reminder_text = match.group(2) + date: typing.Optional[datetime.datetime] + try: + date = dateparser.parse(date_str) + except OverflowError: + date = None + if date is None: + await call.reply("⚠️ La data che hai inserito non è valida.") + await call.reply(f"✅ Promemoria impostato per [b]{date.strftime('%Y-%m-%d %H:%M:%S')}[/b]") + await sleep_until(date) + await call.reply(f"❗️ Promemoria: [b]{reminder_text}[/b]") diff --git a/royalnet/utils/__init__.py b/royalnet/utils/__init__.py index c75aaa79..7768a23b 100644 --- a/royalnet/utils/__init__.py +++ b/royalnet/utils/__init__.py @@ -3,6 +3,7 @@ from .call import Call from .command import Command, CommandArgs, InvalidInputError, UnsupportedError, InvalidConfigError, ExternalError from .safeformat import safeformat from .classdictjanitor import cdj +from .sleepuntil import sleep_until __all__ = ["asyncify", "Call", "Command", "safeformat", "InvalidInputError", "UnsupportedError", "CommandArgs", - "cdj", "InvalidConfigError", "ExternalError"] + "cdj", "InvalidConfigError", "ExternalError", "sleep_until"] diff --git a/royalnet/utils/command.py b/royalnet/utils/command.py index 098f78d0..6b548d2e 100644 --- a/royalnet/utils/command.py +++ b/royalnet/utils/command.py @@ -1,3 +1,4 @@ +import re import typing if typing.TYPE_CHECKING: from .call import Call @@ -35,6 +36,13 @@ class CommandArgs(list): raise InvalidInputError(f'Tried to get invalid [{item}] slice from CommandArgs') raise ValueError(f"Invalid type passed to CommandArgs.__getattr__: {type(item)}") + def match(self, pattern: typing.Pattern) -> typing.Match: + text = " ".join(self) + match = re.match(pattern, text) + if match is None: + raise InvalidInputError("Pattern didn't match") + return match + class Command: """A generic command, called from any source.""" From bbb89b33703352656e860afd831e326500b652a9 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 11:16:00 +0200 Subject: [PATCH 4/8] Add missing space --- royalnet/commands/error_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/royalnet/commands/error_handler.py b/royalnet/commands/error_handler.py index 13f2a299..744ff7c3 100644 --- a/royalnet/commands/error_handler.py +++ b/royalnet/commands/error_handler.py @@ -22,7 +22,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]/{command.command_name} {command.command_syntax}[/c]") return await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{e_type.__name__}[/b]\n{e_value}") formatted_tb: str = '\n'.join(traceback.format_tb(e_tb)) From 8d232fdc6b074e00d26d0c4158192b00d6146949 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 11:25:07 +0200 Subject: [PATCH 5/8] Forgot a return --- royalnet/commands/reminder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/royalnet/commands/reminder.py b/royalnet/commands/reminder.py index 9b3882c1..cae785f8 100644 --- a/royalnet/commands/reminder.py +++ b/royalnet/commands/reminder.py @@ -22,6 +22,7 @@ class ReminderCommand(Command): date = None if date is None: await call.reply("⚠️ La data che hai inserito non è valida.") + return await call.reply(f"✅ Promemoria impostato per [b]{date.strftime('%Y-%m-%d %H:%M:%S')}[/b]") await sleep_until(date) await call.reply(f"❗️ Promemoria: [b]{reminder_text}[/b]") From 4735d145b684547fa72acfea979e25ea284c3096 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 11:49:36 +0200 Subject: [PATCH 6/8] Add error_if_none parameter to call.get_author --- royalnet/bots/telegram.py | 6 ++++-- royalnet/commands/diario.py | 5 +---- royalnet/commands/error_handler.py | 5 ++++- royalnet/utils/__init__.py | 4 ++-- royalnet/utils/call.py | 9 +++++++-- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index c3bd336b..0aab1b40 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -4,7 +4,7 @@ import typing import logging as _logging import sys from ..commands import NullCommand -from ..utils import asyncify, Call, Command +from ..utils import asyncify, Call, Command, UnregisteredError from ..network import RoyalnetLink, Message from ..database import Alchemy, relationshiplinkchain @@ -71,10 +71,12 @@ class TelegramBot: response = await self.network.request(message, destination) return response - async def get_author(call): + async def get_author(call, error_if_none=False): update: telegram.Update = call.kwargs["update"] user: telegram.User = update.effective_user if user is None: + if error_if_none: + raise UnregisteredError("Author is not registered!") return None query = call.session.query(self.master_table) for link in self.identity_chain: diff --git a/royalnet/commands/diario.py b/royalnet/commands/diario.py index 7159bddf..c078cffc 100644 --- a/royalnet/commands/diario.py +++ b/royalnet/commands/diario.py @@ -45,10 +45,7 @@ class DiarioCommand(Command): @classmethod async def common(cls, call: Call): # Find the creator of the quotes - creator = await call.get_author() - if creator is None: - await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!") - return + creator = await call.get_author(error_if_none=True) # Recreate the full sentence raw_text = " ".join(call.args) # Pass the sentence through the diario regex diff --git a/royalnet/commands/error_handler.py b/royalnet/commands/error_handler.py index 744ff7c3..70c6a521 100644 --- a/royalnet/commands/error_handler.py +++ b/royalnet/commands/error_handler.py @@ -1,6 +1,6 @@ import traceback from logging import Logger -from ..utils import Command, CommandArgs, Call, InvalidInputError, UnsupportedError +from ..utils import Command, CommandArgs, Call, InvalidInputError, UnsupportedError, UnregisteredError class ErrorHandlerCommand(Command): @@ -24,6 +24,9 @@ class ErrorHandlerCommand(Command): command = call.kwargs["previous_command"] await call.reply(f"⚠️ Sintassi non valida.\nSintassi corretta: [c]/{command.command_name} {command.command_syntax}[/c]") return + if e_type == UnregisteredError: + await call.reply("⚠️ Devi essere registrato a Royalnet per usare questo comando!") + return await call.reply(f"❌ Eccezione non gestita durante l'esecuzione del comando:\n[b]{e_type.__name__}[/b]\n{e_value}") formatted_tb: str = '\n'.join(traceback.format_tb(e_tb)) call.logger.error(f"Unhandled exception - {e_type.__name__}: {e_value}\n{formatted_tb}") diff --git a/royalnet/utils/__init__.py b/royalnet/utils/__init__.py index 7768a23b..7060c955 100644 --- a/royalnet/utils/__init__.py +++ b/royalnet/utils/__init__.py @@ -1,9 +1,9 @@ from .asyncify import asyncify -from .call import Call +from .call import Call, UnregisteredError from .command import Command, CommandArgs, InvalidInputError, UnsupportedError, InvalidConfigError, ExternalError from .safeformat import safeformat from .classdictjanitor import cdj from .sleepuntil import sleep_until __all__ = ["asyncify", "Call", "Command", "safeformat", "InvalidInputError", "UnsupportedError", "CommandArgs", - "cdj", "InvalidConfigError", "ExternalError", "sleep_until"] + "cdj", "InvalidConfigError", "ExternalError", "sleep_until", "UnregisteredError"] diff --git a/royalnet/utils/call.py b/royalnet/utils/call.py index ab31c6bc..9d0afba2 100644 --- a/royalnet/utils/call.py +++ b/royalnet/utils/call.py @@ -10,6 +10,10 @@ if typing.TYPE_CHECKING: loop = asyncio.get_event_loop() +class UnregisteredError(Exception): + pass + + class Call: """A command call. Still an abstract class, subbots should create a new call from this.""" @@ -27,9 +31,10 @@ class Call: The data must be pickleable.""" raise NotImplementedError() - async def get_author(self): + async def get_author(self, error_if_none=False): """Try to find the universal identifier of the user that sent the message. - That probably means, the database row identifying the user.""" + That probably means, the database row identifying the user. + Raise a UnregisteredError if error_if_none is set to True and no author is found.""" raise NotImplementedError() # These parameters / methods should be left alone From 8f7fe39c27c18d7ae71839baf563c43e1420a597 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 12:17:05 +0200 Subject: [PATCH 7/8] Add optional to CommandArgs --- royalnet/utils/command.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/royalnet/utils/command.py b/royalnet/utils/command.py index 6b548d2e..84a26f8e 100644 --- a/royalnet/utils/command.py +++ b/royalnet/utils/command.py @@ -43,6 +43,11 @@ class CommandArgs(list): raise InvalidInputError("Pattern didn't match") return match + def optional(self, index: int) -> typing.Optional: + try: + return self[index] + except IndexError: + return None class Command: """A generic command, called from any source.""" From 85c0d717dabb10659e36ff8157677867bfedc2df Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Tue, 9 Apr 2019 12:41:42 +0200 Subject: [PATCH 8/8] Bugfix in call.get_author for telegram bots --- royalnet/bots/telegram.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index 0aab1b40..2f3d026d 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -82,7 +82,10 @@ class TelegramBot: 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) + result = await asyncify(query.one_or_none) + if result is None and error_if_none: + raise UnregisteredError("Author is not registered!") + return result self.TelegramCall = TelegramCall