diff --git a/grandbot.py b/grandbot.py index 39bf042d..cddf3bf2 100644 --- a/grandbot.py +++ b/grandbot.py @@ -3,7 +3,7 @@ import json import random import aiohttp import async_timeout -import extra_discord +import royaldiscord import markovify import database import royalbotconfig @@ -12,17 +12,26 @@ import datetime loop = asyncio.get_event_loop() b = telegram.Bot(royalbotconfig.telegram_token) -d = extra_discord.ExtraClient(royalbotconfig.discord_token) +d = royaldiscord.ExtraClient(royalbotconfig.discord_token) -def currently_logged_in(update): +def currently_logged_in(thing): """Trova l'utente connesso all'account di Telegram che ha mandato l'update.""" + # Create a new database session session = database.Session() - user = session.query(database.User).filter_by(telegram_id=update.message.sent_from.user_id).first() + # Check if thing is a Telegram update + if isinstance(thing, telegram.Update): + user = session.query(database.User).filter_by(telegram_id=thing.message.sent_from.user_id).first() + # Check if thing is a Discord message + elif isinstance(thing, royaldiscord.discord.Message): + user = session.query(database.User).filter_by(discord_id=thing.author.id).first() + # I don't know what thing is. + else: + raise TypeError("thing must be either a telegram.Update or a discord.Message") return user -async def start(bot, update, arguments): +async def start_telegram(bot, update, arguments): user = currently_logged_in(update) if user is None: await update.message.reply(bot, f"Ciao!\n_Non hai eseguito l'accesso al RYGdb._", parse_mode="Markdown") @@ -32,7 +41,7 @@ async def start(bot, update, arguments): await update.message.reply(bot, f"Ciao!\nHai eseguito l'accesso come `{user}`.\nAttualmente hai {user.coins} ℝ.\n\n*Account collegati:*\n{telegram_status} Telegram\n{discord_status} Discord", parse_mode="Markdown") -async def diario(bot, update, arguments): +async def diario_telegram(bot, update, arguments): """Aggiungi una frase al diario Royal Games. Devi essere un Royal per poter eseguire questo comando. @@ -69,7 +78,44 @@ Sintassi: `/diario `""" await update.message.reply(bot, "✅ Aggiunto al diario!") -async def leggi(bot, update, arguments): +async def diario_discord(bot, message, arguments): + """Aggiungi una frase al diario Royal Games. + +Devi essere un Royal per poter eseguire questo comando. + +Sintassi: `!diario `""" + # Check if the user is logged in + if not currently_logged_in(message): + bot.send_message(message.channel, "⚠ Non hai ancora eseguito l'accesso! Usa `!sync`.") + return + # Check if the currently logged in user is a Royal Games member + if not currently_logged_in(message).royal: + bot.send_message(message.channel, "⚠ Non sei autorizzato a eseguire questo comando.") + return + # Check the command syntax + if len(arguments) == 0: + bot.send_message(message.channel, "⚠ Sintassi del comando non valida.\n`!diario `") + return + # Check for non-ASCII characters + entry = " ".join(arguments) + if not entry.isprintable(): + bot.send_message(message.channel, "⚠ La frase che stai provando ad aggiungere contiene caratteri non ASCII, quindi non è stata aggiunta.\nToglili e riprova!") + return + # Remove endlines + entry = entry.replace("\n", " ") + # TODO: check if a end-of-file character can be sent on Discord + # Generate a timestamp + time = message.timestamp + # Write on the diario file + file = open("diario.txt", "a", encoding="utf8") + file.write(f"{int(time)}|{entry}\n") + file.close() + del file + # Answer on Telegram + bot.send_message(message.channel, "✅ Aggiunto al diario!") + + +async def leggi_telegram(bot, update, arguments): """Leggi una frase dal diario Royal Games. Puoi visualizzare il diario [qui](https://royal.steffo.me/diario.htm), leggere una frase casuale scrivendo `/leggi random` o leggere una frase specifica scrivendo `/leggi `. @@ -79,7 +125,7 @@ Sintassi: `/leggi `""" await update.message.reply(bot, "⚠ Sintassi del comando non valida.\n`/leggi `", parse_mode="Markdown") return # Open the file - file = open("diario.txt", "r") + file = open("diario.txt", "r", encoding="utf8") # Split the data in lines entries = file.read().split("\n") file.close() @@ -89,11 +135,13 @@ Sintassi: `/leggi `""" entry_number = random.randrange(len(entries)) else: # ...or a specific one - entry_number = arguments[0] + # TODO: check if the entry actually exists + # TODO: check if the first argument is a number + entry_number = int(arguments[0]) # Split the timestamp from the text entry = entries[entry_number].split("|", 1) # Parse the timestamp - date = datetime.datetime.fromtimestamp(entry[0]).isoformat() + date = datetime.datetime.fromtimestamp(int(entry[0])).isoformat() # Get the text text = entry[1] # Sanitize the text to prevent TelegramErrors @@ -101,7 +149,41 @@ Sintassi: `/leggi `""" await update.message.reply(bot, f"Frase #{entry_number} | {date}\n{text}", parse_mode="Markdown") -async def markov(bot, update, arguments): +async def leggi_discord(bot, message, arguments): + """Leggi una frase dal diario Royal Games. + + Puoi visualizzare il diario [qui](https://royal.steffo.me/diario.htm), leggere una frase casuale scrivendo `/leggi random` o leggere una frase specifica scrivendo `/leggi `. + + Sintassi: `!leggi `""" + if len(arguments) == 0 or len(arguments) > 1: + await bot.send_message(message.channel, "⚠ Sintassi del comando non valida.\n`!leggi `") + return + # Open the file + file = open("diario.txt", "r", encoding="utf8") + # Split the data in lines + entries = file.read().split("\n") + file.close() + # Choose an entry + if arguments[0] == "random": + # either randomly... + entry_number = random.randrange(len(entries)) + else: + # ...or a specific one + # TODO: check if the entry actually exists + # TODO: check if the first argument is a number + entry_number = int(arguments[0]) + # Split the timestamp from the text + entry = entries[entry_number].split("|", 1) + # Parse the timestamp + date = datetime.datetime.fromtimestamp(int(entry[0])).isoformat() + # Get the text + text = entry[1] + # Sanitize the text to prevent TelegramErrors + text = text.replace("_", "\_").replace("*", "\*").replace("`", "\`").replace("[", "\[") + await bot.send_message(message.channel, f"Frase #{entry_number} | {date}\n{text}") + + +async def markov_telegram(bot, update, arguments): """Genera una frase del diario utilizzando le catene di Markov. Puoi specificare con che parole (massimo 2) deve iniziare la frase generata. @@ -139,7 +221,7 @@ Sintassi: `/markov [inizio]`""" await update.message.reply(bot, f"⚠ Il bot non è riuscito a generare una nuova frase.\nSe è la prima volta che vedi questo errore, riprova, altrimenti prova a cambiare configurazione.") -async def help_cmd(bot, update, arguments): +async def help_telegram(bot, update, arguments): """Visualizza la descrizione di un comando. Sintassi: `/help [comando]`""" @@ -149,12 +231,27 @@ Sintassi: `/help [comando]`""" await update.message.reply(bot, "⚠ Sintassi del comando non valida.\n`/help [comando]`", parse_mode="Markdown") else: if arguments[0] in b.commands: - await update.message.reply(bot, b.commands[arguments[0]].__doc__, parse_mode="Markdown") + await update.message.reply(bot, b.commands[arguments[0] + "_telegram"].__doc__, parse_mode="Markdown") else: await update.message.reply(bot, "⚠ Il comando specificato non esiste.") -async def discord(bot, update, arguments): +async def help_discord(bot, message, arguments): + """Visualizza la descrizione di un comando. + +Sintassi: `!help [comando]`""" + if len(arguments) == 0: + bot.send_message(message.channel, help.__doc__) + elif len(arguments) > 1: + bot.send_message(message.channel, "⚠ Sintassi del comando non valida.\n`!help [comando]`") + else: + if arguments[0] in b.commands: + bot.send_message(message.channel, b.commands[arguments[0] + "_discord"].__doc__) + else: + bot.send_message(message.channel, "⚠ Il comando specificato non esiste.") + + +async def discord_telegram(bot, update, arguments): """Manda un messaggio a #chat di Discord. Sintassi: `/discord `""" @@ -256,7 +353,7 @@ Sintassi: `!sync `""" await bot.send_message(message.channel, "⚠ Username o password non validi.") -async def changepassword(bot, update, arguments): +async def changepassword_telegram(bot, update, arguments): """Cambia la tua password del Database Royal Games. Sintassi: `/changepassword `""" @@ -274,7 +371,7 @@ Sintassi: `/changepassword `""" await update.message.reply(bot, "⚠ Username o password non validi.", parse_mode="Markdown") -async def cv(bot, update, arguments): +async def cv_telegram(bot, update, arguments): """Visualizza lo stato attuale della chat vocale Discord. Sintassi: `/cv`""" @@ -354,7 +451,7 @@ Sintassi: `/cv`""" await update.message.reply(bot, to_send, parse_mode="Markdown", disable_web_page_preview=1) -async def buycoins(bot, update, arguments): +async def buycoins_telegram(bot, update, arguments): """Compra nuovi ℝ! Sintassi: /buycoins""" @@ -392,20 +489,50 @@ Sintassi: /buycoins""" f"NOTA LEGALE: @Steffo non si assume responsabilità per il contenuto delle app sponsorizzate. Fate attenzione!", parse_mode="Markdown") +async def roll_telegram(bot, update, arguments): + """Lancia un dado a N facce. + +Sintassi: `/roll `""" + # Check the command syntax + if len(arguments) != 1: + await update.message.reply(bot, "⚠ Sintassi del comando non valida.\n`/roll `", + parse_mode="Markdown") + return + # Roll the dice! + await update.message.reply(bot, f"*Numero generato:* {random.randrange(0, int(arguments[0])) + 1}") + + +async def roll_discord(bot, message, arguments): + """Lancia un dado a N facce. + +Sintassi: `!roll `""" + # Check the command syntax + if len(arguments) != 1: + await bot.send_message(message.channel, "⚠ Sintassi del comando non valida.\n`!roll `") + return + # Roll the dice! + await bot.send_message(message.channel, f"*Numero generato:* {random.randrange(0, int(arguments[0])) + 1}") + + if __name__ == "__main__": # Init Telegram bot commands - b.commands["start"] = start - b.commands["leggi"] = leggi - b.commands["diario"] = diario - b.commands["discord"] = discord + b.commands["start"] = start_telegram + b.commands["leggi"] = leggi_telegram + b.commands["diario"] = diario_telegram + b.commands["discord"] = discord_telegram b.commands["sync"] = sync_telegram - b.commands["changepassword"] = changepassword - b.commands["help"] = help_cmd - b.commands["markov"] = markov - b.commands["cv"] = cv - b.commands["buycoins"] = buycoins + b.commands["changepassword"] = changepassword_telegram + b.commands["help"] = help_telegram + b.commands["markov"] = markov_telegram + b.commands["cv"] = cv_telegram + b.commands["roll"] = roll_telegram + b.commands["buycoins"] = buycoins_telegram # Init Discord bot commands d.commands["sync"] = sync_discord + d.commands["roll"] = roll_discord + d.commands["help"] = help_discord + d.commands["leggi"] = leggi_discord + d.commands["diario"] = diario_discord # Init Telegram bot loop.create_task(b.run()) print("Telegram bot start scheduled!") diff --git a/extra_discord.py b/royaldiscord.py similarity index 100% rename from extra_discord.py rename to royaldiscord.py diff --git a/telegram.py b/telegram.py index 5a5a641c..e2ac9cda 100644 --- a/telegram.py +++ b/telegram.py @@ -139,11 +139,13 @@ class Bot: # Send the request to the Telegram API token = self.token async with session.request("GET", f"https://api.telegram.org/bot{token}/{endpoint}", params=params) as response: - # Check for errors in the request - if response.status != 200: - raise TelegramAPIError(f"Request returned {response.status} {response.reason}\n{response.text()}") # Parse the json data as soon it's ready data = await response.json() + # Check for errors in the request + if "description" in data: + error = data["description"] + if response.status != 200: + raise TelegramAPIError(f"Request returned {response.status} {response.reason}") # Check for errors in the response if not data["ok"]: error = data["description"] @@ -229,7 +231,11 @@ class Chat: # TODO: This could give problems if a class inherits Bot if not isinstance(bot, Bot): raise TypeError("bot is not an instance of Bot.") - await bot.api_request("sendMessage", text=text, chat_id=self.chat_id, **params) + # Catch TelegramAPI exceptions + try: + await bot.api_request("sendMessage", text=text, chat_id=self.chat_id, **params) + except TelegramAPIError as e: + print(f"[Telegram] sendMessage failed: {e.args[0]}") class User: