diff --git a/royalgames.py b/royalgames.py new file mode 100644 index 00000000..9ed768c7 --- /dev/null +++ b/royalgames.py @@ -0,0 +1,17 @@ +import os +import asyncio +from royalnet.bots import TelegramBot +from royalnet.commands import PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand +from royalnet.commands.debug_create import DebugCreateCommand +from royalnet.network import RoyalnetServer + +loop = asyncio.get_event_loop() + +commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand] + +master = RoyalnetServer("localhost", 1234, "sas") +tg_bot = TelegramBot(os.environ["TG_AK"], "localhost:1234", "sas", commands, "sqlite://") +loop.create_task(master.run()) +loop.create_task(tg_bot.run()) +print("Starting loop...") +loop.run_forever() diff --git a/royalnet/bots/telegram.py b/royalnet/bots/telegram.py index cdd91cb4..815b143a 100644 --- a/royalnet/bots/telegram.py +++ b/royalnet/bots/telegram.py @@ -34,10 +34,10 @@ class TelegramBot: self.network: RoyalnetLink = RoyalnetLink(master_server_uri, master_server_secret, "telegram", todo) # Generate commands self.commands = {} - required_tables = [] + required_tables = set() for command in commands: self.commands[f"/{command.command_name}"] = command - required_tables += command.require_alchemy_tables + required_tables = required_tables.union(command.require_alchemy_tables) # Generate the Alchemy database self.alchemy = Alchemy(database_uri, required_tables) @@ -45,7 +45,7 @@ class TelegramBot: class TelegramCall(Call): interface_name = "telegram" interface_obj = self - Session = self.alchemy.Session + interface_alchemy = self.alchemy async def reply(call, text: str): await asyncify(call.channel.send_message, text, parse_mode="HTML") diff --git a/royalnet/commands/__init__.py b/royalnet/commands/__init__.py index ff68ddf3..01f2db59 100644 --- a/royalnet/commands/__init__.py +++ b/royalnet/commands/__init__.py @@ -2,5 +2,8 @@ from .null import NullCommand from .ping import PingCommand from .ship import ShipCommand from .smecds import SmecdsCommand +from .ciaoruozi import CiaoruoziCommand +from .color import ColorCommand -__all__ = ["NullCommand", "PingCommand", "ShipCommand", "SmecdsCommand"] + +__all__ = ["NullCommand", "PingCommand", "ShipCommand", "SmecdsCommand", "CiaoruoziCommand", "ColorCommand"] diff --git a/royalnet/commands/color.py b/royalnet/commands/color.py index f97be778..76dbe2c2 100644 --- a/royalnet/commands/color.py +++ b/royalnet/commands/color.py @@ -1,7 +1,7 @@ from ..utils import Command, CommandArgs, Call -class PingCommand(Command): +class ColorCommand(Command): command_name = "color" command_title = "Invia un colore in chat...?" diff --git a/royalnet/commands/debug_create.py b/royalnet/commands/debug_create.py new file mode 100644 index 00000000..2bb7e7dd --- /dev/null +++ b/royalnet/commands/debug_create.py @@ -0,0 +1,16 @@ +from ..utils import Command, CommandArgs, Call +from ..database.tables import Royal + + +class DebugCreateCommand(Command): + + command_name = "debug_create" + command_title = "Create a new Royalnet user account" + + require_alchemy_tables = {Royal} + + async def common(self, call: Call, args: CommandArgs): + royal = call.interface_alchemy.Royal(username=args[0], role="Member") + call.session.add(royal) + call.session.commit() + await call.reply(f"✅ Utente {royal} creato!") diff --git a/royalnet/database/__init__.py b/royalnet/database/__init__.py index 8b2b75c2..50e0bcf4 100644 --- a/royalnet/database/__init__.py +++ b/royalnet/database/__init__.py @@ -1,4 +1,4 @@ from .alchemy import Alchemy -from .tables import Role, Royal +from .tables import Royal -__all__ = ["Alchemy", "Role", "Royal"] +__all__ = ["Alchemy", "Royal"] diff --git a/royalnet/database/alchemy.py b/royalnet/database/alchemy.py index 1372ff01..1c0af022 100644 --- a/royalnet/database/alchemy.py +++ b/royalnet/database/alchemy.py @@ -4,13 +4,13 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from contextlib import contextmanager, asynccontextmanager -from ..utils import classdictjanitor +from ..utils import cdj loop = asyncio.get_event_loop() class Alchemy: - def __init__(self, database_uri: str = "sqlite://", tables: typing.Optional[typing.List] = None): + def __init__(self, database_uri: str = "sqlite://", tables: typing.Optional[typing.Set] = None): self.engine = create_engine(database_uri) self.Base = declarative_base(bind=self.engine) self.Session = sessionmaker(bind=self.engine) @@ -23,7 +23,7 @@ class Alchemy: self.__getattribute__(name) except AttributeError: # Actually the intended result - self.__setattr__(name, type(name, (self.Base,), classdictjanitor(table))) + self.__setattr__(name, type(name, (self.Base,), cdj(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/__init__.py b/royalnet/database/tables/__init__.py index 9b85c1d9..947c88b2 100644 --- a/royalnet/database/tables/__init__.py +++ b/royalnet/database/tables/__init__.py @@ -1,3 +1,3 @@ -from .royals import Role, Royal +from .royals import Royal -__all__ = ["Role", "Royal"] +__all__ = ["Royal"] diff --git a/royalnet/database/tables/royals.py b/royalnet/database/tables/royals.py index 2d5220ec..7d71a1ca 100644 --- a/royalnet/database/tables/royals.py +++ b/royalnet/database/tables/royals.py @@ -1,16 +1,9 @@ -from enum import Enum from sqlalchemy import Column, \ Integer, \ String, \ LargeBinary -class Role(Enum): - Guest = "Guest" - Member = "Member" - Admin = "Admin" - - class Royal: __tablename__ = "royals" @@ -18,3 +11,10 @@ class Royal: username = Column(String, unique=True, nullable=False) password = Column(LargeBinary) role = Column(String, nullable=False) + avatar = Column(LargeBinary) + + def __repr__(self): + return f"" + + def __str__(self): + return self.username diff --git a/royalnet/database/tables/telegram.py b/royalnet/database/tables/telegram.py new file mode 100644 index 00000000..753abd89 --- /dev/null +++ b/royalnet/database/tables/telegram.py @@ -0,0 +1,28 @@ +from sqlalchemy import Column, \ + Integer, \ + String, \ + BigInteger, \ + LargeBinary + + +class Telegram: + __tablename__ = "telegram" + + royal_id = Column(Integer) + tg_id = Column(BigInteger, primary_key=True) + tg_first_name = Column(String) + tg_last_name = Column(String) + tg_username = Column(String) + tg_avatar = Column(LargeBinary) + + def __repr__(self): + return f"" + + def __str__(self): + if self.tg_username is not None: + return f"@{self.tg_username}" + elif self.tg_last_name is not None: + return f"{self.tg_first_name} {self.tg_last_name}" + else: + return self.tg_first_name + diff --git a/royalnet/utils/__init__.py b/royalnet/utils/__init__.py index 38afaa7c..2c7c9e93 100644 --- a/royalnet/utils/__init__.py +++ b/royalnet/utils/__init__.py @@ -2,7 +2,7 @@ from .asyncify import asyncify from .call import Call from .command import Command, CommandArgs, InvalidInputError, UnsupportedError from .safeformat import safeformat -from .classdictjanitor import classdictjanitor +from .classdictjanitor import cdj __all__ = ["asyncify", "Call", "Command", "safeformat", "InvalidInputError", "UnsupportedError", "CommandArgs", - "classdictjanitor"] + "cdj"] diff --git a/royalnet/utils/call.py b/royalnet/utils/call.py index cad150aa..5e3512d3 100644 --- a/royalnet/utils/call.py +++ b/royalnet/utils/call.py @@ -1,6 +1,11 @@ import typing +import asyncio from ..network.messages import Message from .command import Command, CommandArgs +from ..database import Alchemy + + +loop = asyncio.get_event_loop() class Call: @@ -9,7 +14,7 @@ class Call: # These parameters / methods should be overridden interface_name = NotImplemented interface_obj = NotImplemented - Session = NotImplemented + interface_alchemy: Alchemy = NotImplemented async def reply(self, text: str): """Send a text message to the channel the call was made.""" @@ -26,10 +31,26 @@ class Call: self.command = command self.args = args self.kwargs = kwargs + self.session = None + + async def session_init(self): + if not self.command.require_alchemy_tables: + return + self.session = await loop.run_in_executor(self.interface_alchemy.Session) + + async def session_end(self): + if not self.session: + return + self.session.close() async def run(self): + await self.session_init() try: coroutine = getattr(self.command, self.interface_name) except AttributeError: coroutine = getattr(self.command, "common") - return await coroutine(self.command, self, CommandArgs(*self.args, **self.kwargs)) + try: + result = await coroutine(self.command, self, CommandArgs(*self.args, **self.kwargs)) + finally: + self.session.close() + return result diff --git a/royalnet/utils/classdictjanitor.py b/royalnet/utils/classdictjanitor.py index cd16fe1c..52427bc2 100644 --- a/royalnet/utils/classdictjanitor.py +++ b/royalnet/utils/classdictjanitor.py @@ -1,4 +1,4 @@ -def classdictjanitor(class_) -> dict: +def cdj(class_) -> dict: """Return the cleaned class attributes in a dict.""" d = dict(class_.__dict__) del d["__module__"] diff --git a/royalnet/utils/command.py b/royalnet/utils/command.py index 09ecbc45..885e3472 100644 --- a/royalnet/utils/command.py +++ b/royalnet/utils/command.py @@ -39,7 +39,7 @@ class Command: command_name: str = NotImplemented command_title: str = NotImplemented - require_alchemy_tables: typing.List = [] + require_alchemy_tables: typing.Set = set() async def common(self, call: "Call", args: CommandArgs): raise NotImplementedError()