diff --git a/poetry.lock b/poetry.lock index 032fa3cc..defd91fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -303,7 +303,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "royalnet" -version = "6.5.0" +version = "6.5.4" description = "A multipurpose bot framework" category = "main" optional = false @@ -512,7 +512,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "d09a25572708f3d976eec40c086da014232918d5d1cd689866c831bc324b598d" +content-hash = "c30fdf09f35a2430d64998b966b2648d1bed68a9a1e58e45339f5a1a53895263" [metadata.files] aiohttp = [ @@ -894,8 +894,8 @@ requests = [ {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] royalnet = [ - {file = "royalnet-6.5.0-py3-none-any.whl", hash = "sha256:1c1f0ddb4891d1951bd8fe2e9a4139cc5b3a039d9b2318c0cabd489dc2253ee3"}, - {file = "royalnet-6.5.0.tar.gz", hash = "sha256:dc4e5a284e8e2f36f259b5fe88e36a5947984c78600fd22e695e62277c787951"}, + {file = "royalnet-6.5.4-py3-none-any.whl", hash = "sha256:68d30fdd00653a5e79eab2b22a8c5bb16103e08e8674ec8f4b13ff1bdf1d33d2"}, + {file = "royalnet-6.5.4.tar.gz", hash = "sha256:fbab17b911c2ee200c0f7bfd5d2bcc4d1d3f9771b41ab64a848a08cdd464e3d8"}, ] royalnet-console = [ {file = "royalnet-console-2.0.4.tar.gz", hash = "sha256:049948a558a2ee7359f808cf61faef08789ce9e3969a0dfb94e65bd912ffcffc"}, diff --git a/pyproject.toml b/pyproject.toml index e7bc9dcd..df7a474a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ python = "^3.8" coloredlogs = "^15.0" aiohttp = "^3.7.4" psycopg2 = "^2.8.6" -royalnet = "~6.5.0" +royalnet = "~6.5.3" royalnet_telethon = "^2.0.0" royalnet_console = "^2.0.4" alembic = "^1.5.8" diff --git a/royalpack/__main__.py b/royalpack/__main__.py index ffb7b654..da8d29b1 100644 --- a/royalpack/__main__.py +++ b/royalpack/__main__.py @@ -55,6 +55,7 @@ register_telegram(commands.pmots, ["pmots"]) register_telegram(commands.spell, ["spell"], "(?P.+)") register_telegram(commands.smecds, ["smecds"]) register_telegram(commands.man, ["man", "help"], "(?P[A-Za-z]+)") +register_telegram(commands.login, ["login"]) pda.implementations["telethon.1"].register_conversation(r) diff --git a/royalpack/commands/__init__.py b/royalpack/commands/__init__.py index 7d6c42b3..60f1b3b0 100644 --- a/royalpack/commands/__init__.py +++ b/royalpack/commands/__init__.py @@ -12,3 +12,4 @@ from .pmots import * from .spell import * from .smecds import * from .man import * +from .login import * diff --git a/royalpack/commands/ahnonlosoio.py b/royalpack/commands/ahnonlosoio.py index f5bea4f4..a25af863 100644 --- a/royalpack/commands/ahnonlosoio.py +++ b/royalpack/commands/ahnonlosoio.py @@ -2,7 +2,7 @@ import royalnet.engineer as engi @engi.TeleportingConversation -async def ahnonlosoio(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def ahnonlosoio(*, _msg: engi.Message, **__): """ Ah, non lo so io! """ diff --git a/royalpack/commands/answer.py b/royalpack/commands/answer.py index fbc9331b..e36eeb7f 100644 --- a/royalpack/commands/answer.py +++ b/royalpack/commands/answer.py @@ -74,7 +74,7 @@ ANSWERS = [ @engi.TeleportingConversation -async def answer(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def answer(*, _msg: engi.Message, **__): """ Fai una domanda al bot, che possa essere risposta con un sì o un no: lui ti risponderà! """ diff --git a/royalpack/commands/cat.py b/royalpack/commands/cat.py index 0688d1b3..dbb98501 100644 --- a/royalpack/commands/cat.py +++ b/royalpack/commands/cat.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__) @engi.TeleportingConversation -async def cat(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def cat(*, _msg: engi.Message, **__): """ Invia un gatto in chat! 🐈 """ diff --git a/royalpack/commands/ciaoruozi.py b/royalpack/commands/ciaoruozi.py index b5229e7b..4ba6292e 100644 --- a/royalpack/commands/ciaoruozi.py +++ b/royalpack/commands/ciaoruozi.py @@ -5,7 +5,7 @@ import royalnet_telethon.bullet.contents @engi.TeleportingConversation -async def ciaoruozi(*, _sentry: engi.Sentry, _msg: engi.Message, _imp, **__): +async def ciaoruozi(*, _msg: engi.Message, _imp, **__): """ Saluta Ruozi, una creatura leggendaria che potrebbe esistere o non esistere in Royal Games. """ diff --git a/royalpack/commands/color.py b/royalpack/commands/color.py index 58b8203d..4dfd27c6 100644 --- a/royalpack/commands/color.py +++ b/royalpack/commands/color.py @@ -3,7 +3,7 @@ import royalnet.engineer.conversation as c @engi.TeleportingConversation -async def color(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def color(*, _msg: engi.Message, **__): """ Invia un colore in chat...? """ diff --git a/royalpack/commands/emojify.py b/royalpack/commands/emojify.py index 2d053507..cfeae2dc 100644 --- a/royalpack/commands/emojify.py +++ b/royalpack/commands/emojify.py @@ -80,7 +80,7 @@ _emojis = { @engi.TeleportingConversation -async def emojify(*, _msg: engi.Message, message: str, **__): +async def emojify(*, message: str, **__): """ Converti un messaggio in emoji. """ diff --git a/royalpack/commands/fortune.py b/royalpack/commands/fortune.py index e34e8c7b..287fb4df 100644 --- a/royalpack/commands/fortune.py +++ b/royalpack/commands/fortune.py @@ -56,7 +56,7 @@ _fortunes = [ @engi.TeleportingConversation -async def fortune(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def fortune(*, _msg: engi.Message, **__): """ Come sarà la giornata di oggi? """ diff --git a/royalpack/commands/login.py b/royalpack/commands/login.py new file mode 100644 index 00000000..f2aca537 --- /dev/null +++ b/royalpack/commands/login.py @@ -0,0 +1,142 @@ +import royalnet.engineer as engi +import sqlalchemy.sql as ss +import sqlalchemy.orm as so +import royalpack.database as db +import royalpack.config as cfg +import royalnet_telethon +import royalnet_telethon.bullet.contents +import aiohttp +import asyncio +import logging +import arrow +import datetime + +log = logging.getLogger(__name__) + +# FIXME: Properly handle errors in this function! + +@engi.use_database(db.lazy_session_class) +@engi.TeleportingConversation +async def login(*, _msg: engi.Message, _session: so.Session, _imp, **__): + """ + Fai il login al tuo account Royalnet. + """ + log.debug("Evaluating config...") + config = cfg.lazy_config.evaluate() + + log.debug("Sliding into DMs...") + sender: engi.User = await _msg.sender + current: engi.Channel = await _msg.channel + private: engi.Channel = await sender.slide() + if hash(current) != hash(private): + await _msg.reply(text="👤 Ti ho inviato un messaggio in chat privata contenente le istruzioni per il login!") + + async with aiohttp.ClientSession() as http_session: + + log.debug("Generating device code...") + async with http_session.post(config["auth.url.device"], data={ + "client_id": config["auth.client.id"], + "scope": "profile email openid", + "prompt": "consent", + }) as request: + response = await request.json() + start = arrow.now() + + log.debug("Asking user to login...") + await private.send_message( + text=f"🌍 Effettua il RYGlogin al seguente URL, poi premi Confirm:\n" + f"{response['verification_uri_complete']}\n" + f"\n" + f"(Codice: {response['user_code']})" + ) + + expiration = start + datetime.timedelta(seconds=response["expires_in"]) + while arrow.now() < expiration: + log.debug("Sleeping for 10 seconds...") + await asyncio.sleep(10) + + async with http_session.post(config["auth.url.token"], data={ + "client_id": config["auth.client.id"], + "grant_type": "urn:ietf:params:oauth:grant-type:device_code", + "device_code": response["device_code"], + }) as request: + response = await request.json() + if "error" in response: + log.debug(f"Response returned error {response['error']!r}, retrying...") + continue + elif "access_token" in response: + log.debug(f"Obtained access token...") + break + else: + log.error(f"Didn't get an access token, but didn't get an error either?!") + continue + else: + log.debug("Login request expired.") + await private.send_message(text="🕒 La tua richiesta di login è scaduta. " + "Riinvia il comando per ricominciare!") + return + + async with http_session.post(config["auth.url.userinfo"], headers={ + "Authorization": f"{response['token_type']} {response['access_token']}" + }) as request: + response = await request.json() + + log.debug("Checking if the user already exists...") + user: db.User = _session.execute( + ss.select(db.User).where(db.User.sub == response["sub"]) + ).scalar() + + log.debug("Creating user dict...") + user_dict = { + "sub": response['sub'], + "last_update": arrow.now(), + "name": response['name'], + "nickname": response['nickname'], + "avatar": response['picture'], + "email": response['email'], + } + + if user is None: + log.info(f"Creating new user: {response['sub']}") + user = db.User(**user_dict) + _session.add(user) + else: + log.debug(f"Updating existing user: {response['sub']}") + user.update(**user_dict) + + if isinstance(_imp, royalnet_telethon.TelethonPDAImplementation): + log.debug("Found out I'm running on Telethon...") + + sender: royalnet_telethon.bullet.contents.TelegramUser + + log.debug("Checking if the TelegramAccount already exists...") + tg: db.TelegramAccount = _session.execute( + ss.select(db.TelegramAccount).where(db.TelegramAccount.id == sender._user.id) + ).scalar() + + log.debug("Creating tg_dict...") + tg_dict = { + "user_fk": response["sub"], + "id": sender._user.id, + "first_name": sender._user.first_name, + "last_name": sender._user.last_name, + "username": sender._user.username, + "avatar_url": None, # TODO: avatars + } + + if tg is None: + log.info(f"Creating new TelegramAccount: {sender._user.id}") + tg = db.TelegramAccount(**tg_dict) + _session.add(tg) + else: + log.debug(f"Updating existing TelegramAccount: {sender._user.id}") + tg.update(**tg_dict) + + log.debug(f"Committing session...") + _session.commit() + + log.debug(f"Done, notifying the user...") + await private.send_message(text=f"✅ Login riuscito! Sei loggato come {response['name']}!") + + +__all__ = ("login",) diff --git a/royalpack/commands/ping.py b/royalpack/commands/ping.py index 57dad3d0..0e07ed56 100644 --- a/royalpack/commands/ping.py +++ b/royalpack/commands/ping.py @@ -2,7 +2,7 @@ import royalnet.engineer as engi @engi.TeleportingConversation -async def ping(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def ping(*, _msg: engi.Message, **__): """ Gioca a ping pong con il bot. 🏓 """ diff --git a/royalpack/commands/pmots.py b/royalpack/commands/pmots.py index 3cebaf06..f979881b 100644 --- a/royalpack/commands/pmots.py +++ b/royalpack/commands/pmots.py @@ -2,7 +2,7 @@ import royalnet.engineer as engi @engi.TeleportingConversation -async def pmots(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def pmots(*, _msg: engi.Message, **__): """ Riprenditi da uno stomp colossale! diff --git a/royalpack/commands/ship.py b/royalpack/commands/ship.py index 2954b488..ec97d9be 100644 --- a/royalpack/commands/ship.py +++ b/royalpack/commands/ship.py @@ -6,7 +6,7 @@ log = logging.getLogger(__name__) @engi.TeleportingConversation -async def ship(*, _sentry: engi.Sentry, _msg: engi.Message, first: str, second: str, **__): +async def ship(*, _msg: engi.Message, first: str, second: str, **__): """ Shippa insieme due persone! 💞 """ diff --git a/royalpack/commands/smecds.py b/royalpack/commands/smecds.py index 05333b48..e3a0fc45 100644 --- a/royalpack/commands/smecds.py +++ b/royalpack/commands/smecds.py @@ -55,7 +55,7 @@ _ds_list = ["della secca", "del seccatore", "del secchiello", "del secchio", "de @engi.TeleportingConversation -async def smecds(*, _sentry: engi.Sentry, _msg: engi.Message, **__): +async def smecds(*, _msg: engi.Message, **__): """ Secondo me, è colpa dello stagista... """ diff --git a/royalpack/database/__init__.py b/royalpack/database/__init__.py new file mode 100644 index 00000000..fa79969a --- /dev/null +++ b/royalpack/database/__init__.py @@ -0,0 +1,2 @@ +from .base import * +from .engine import * diff --git a/royalpack/database/engine.py b/royalpack/database/engine.py index eed1202d..b233aa2c 100644 --- a/royalpack/database/engine.py +++ b/royalpack/database/engine.py @@ -9,6 +9,13 @@ lazy_engine = royalnet.lazy.Lazy(lambda c: sqlalchemy.create_engine(c["database. The uninitialized sqlalchemy engine. """ +lazy_session_class = royalnet.lazy.Lazy(lambda e: sqlalchemy.orm.sessionmaker(bind=e), e=lazy_engine) +""" +The uninitialized sqlalchemy session class. +""" + + __all__ = ( "lazy_engine", + "lazy_session_class", )