2017-02-27 22:16:42 +00:00
import asyncio
2017-03-05 17:14:45 +00:00
import datetime
2017-03-09 11:51:45 +00:00
import json
2017-03-22 17:04:21 +00:00
import random
import aiohttp
import async_timeout
2017-03-22 18:13:22 +00:00
import extra_discord
2017-03-22 17:04:21 +00:00
import markovify
import database
import royalbotconfig
import telegram
2017-03-05 17:14:45 +00:00
2017-03-22 16:23:17 +00:00
loop = asyncio . get_event_loop ( )
2017-03-05 18:12:02 +00:00
b = telegram . Bot ( royalbotconfig . telegram_token )
2017-03-22 18:13:22 +00:00
d = extra_discord . ExtraClient ( royalbotconfig . discord_token )
2017-02-27 22:16:42 +00:00
2017-03-22 16:23:17 +00:00
2017-03-10 10:52:20 +00:00
def currently_logged_in ( update ) :
2017-03-10 14:52:26 +00:00
""" Trova l ' utente connesso all ' account di Telegram che ha mandato l ' update. """
2017-03-10 10:52:20 +00:00
session = database . Session ( )
user = session . query ( database . User ) . filter_by ( telegram_id = update . message . sent_from . user_id ) . first ( )
2017-03-10 11:00:37 +00:00
return user
2017-03-10 10:52:20 +00:00
2017-03-22 16:23:17 +00:00
async def start ( bot , update , arguments ) :
user = currently_logged_in ( update )
if user is None :
2017-03-22 18:13:22 +00:00
await update . message . reply ( bot , f " Ciao! \n _Non hai eseguito l ' accesso al RYGdb._ " , parse_mode = " Markdown " )
2017-03-22 16:23:17 +00:00
else :
2017-03-22 18:13:22 +00:00
telegram_status = " 🔵 " if user . telegram_id is not None else " ⚪ "
discord_status = " 🔵 " if user . discord_id is not None else " ⚪ "
await update . message . reply ( bot , f " Ciao! \n Hai eseguito l ' accesso come ` { user } `. \n \n *Account collegati:* \n { telegram_status } Telegram \n { discord_status } Discord " , parse_mode = " Markdown " )
2017-03-22 16:23:17 +00:00
2017-02-27 22:52:47 +00:00
async def diario ( bot , update , arguments ) :
2017-03-05 17:14:45 +00:00
""" Aggiungi una frase al diario Royal Games.
2017-03-10 11:00:37 +00:00
Devi essere un Royal per poter eseguire questo comando .
2017-03-05 17:14:45 +00:00
Sintassi : ` / diario < frase > ` """
2017-03-23 18:43:26 +00:00
# Check if the user is logged in
if not currently_logged_in ( update ) :
await update . message . reply ( bot , " ⚠ Non hai ancora eseguito l ' accesso! Usa `/sync`. " , parse_mode = " Markdown " )
return
# Check if the currently logged in user is a Royal Games member
2017-03-10 11:00:37 +00:00
if not currently_logged_in ( update ) . royal :
2017-03-23 18:43:26 +00:00
await update . message . reply ( bot , " ⚠ Non sei autorizzato a eseguire questo comando. " )
2017-03-10 11:00:37 +00:00
return
2017-03-23 18:43:26 +00:00
# Check the command syntax
2017-03-05 17:14:45 +00:00
if len ( arguments ) == 0 :
2017-03-13 14:32:51 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/diario <random | markov | numerofrase>` " , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
return
2017-03-23 18:43:26 +00:00
# Check for non-ASCII characters
2017-03-05 17:14:45 +00:00
entry = " " . join ( arguments )
if not entry . isprintable ( ) :
2017-03-23 18:43:26 +00:00
await update . message . reply ( bot , " ⚠ La frase che stai provando ad aggiungere contiene caratteri non ASCII, quindi non è stata aggiunta. \n Toglili e riprova! " )
2017-03-05 17:14:45 +00:00
return
2017-03-23 18:43:26 +00:00
# Remove endlines
2017-03-05 17:14:45 +00:00
entry = entry . replace ( " \n " , " " )
2017-03-23 18:43:26 +00:00
# TODO: check if a end-of-file character can be sent on Telegram
# Generate a timestamp
2017-03-05 17:14:45 +00:00
time = update . message . date . timestamp ( )
2017-03-23 18:43:26 +00:00
# Write on the diario file
2017-03-13 11:52:00 +00:00
file = open ( " diario.txt " , " a " , encoding = " utf8 " )
2017-03-05 17:14:45 +00:00
file . write ( f " { int ( time ) } | { entry } \n " )
file . close ( )
del file
2017-03-23 18:43:26 +00:00
# Answer on Telegram
await update . message . reply ( bot , " ✅ Aggiunto al diario! " )
2017-03-05 17:14:45 +00:00
async def leggi ( 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 < numero > ` .
2017-03-13 18:13:42 +00:00
Sintassi : ` / leggi < random | numerofrase > ` """
2017-03-05 17:14:45 +00:00
if len ( arguments ) == 0 or len ( arguments ) > 1 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/leggi <random | numerofrase>` " , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
return
2017-03-23 18:43:26 +00:00
# Open the file
2017-03-13 18:13:42 +00:00
file = open ( " diario.txt " , " r " )
2017-03-23 18:43:26 +00:00
# Split the data in lines
2017-03-13 18:13:42 +00:00
entries = file . read ( ) . split ( " \n " )
2017-03-05 17:14:45 +00:00
file . close ( )
2017-03-23 18:43:26 +00:00
# Choose an entry
2017-03-13 18:13:42 +00:00
if arguments [ 0 ] == " random " :
2017-03-23 18:43:26 +00:00
# either randomly...
2017-03-13 18:13:42 +00:00
entry_number = random . randrange ( len ( entries ) )
2017-03-05 17:14:45 +00:00
else :
2017-03-23 18:43:26 +00:00
# ...or a specific one
2017-03-13 18:13:42 +00:00
entry_number = arguments [ 0 ]
2017-03-23 18:43:26 +00:00
# Split the timestamp from the text
2017-03-13 18:13:42 +00:00
entry = entries [ entry_number ] . split ( " | " , 1 )
2017-03-23 18:43:26 +00:00
# Parse the timestamp
2017-03-13 18:13:42 +00:00
date = datetime . datetime . fromtimestamp ( entry [ 0 ] ) . isoformat ( )
2017-03-23 18:43:26 +00:00
# Get the text
2017-03-05 17:14:45 +00:00
text = entry [ 1 ]
2017-03-23 18:43:26 +00:00
# Sanitize the text to prevent TelegramErrors
text = text . replace ( " _ " , " \ _ " ) . replace ( " * " , " \ * " ) . replace ( " ` " , " \ ` " ) . replace ( " [ " , " \ [ " )
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , f " Frase # { entry_number } | { date } \n { text } " , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
2017-03-13 18:13:42 +00:00
async def markov ( bot , update , arguments ) :
""" Genera una frase del diario utilizzando le catene di Markov.
2017-03-22 17:02:59 +00:00
Puoi specificare con che parole ( massimo 2 ) deve iniziare la frase generata .
Se non vengono specificate , verrà scelta una parola a caso .
2017-03-13 18:13:42 +00:00
Sintassi : ` / markov [ inizio ] ` """
2017-03-22 17:02:59 +00:00
if len ( arguments ) > 2 :
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/markov [inizio]` " )
2017-03-13 18:13:42 +00:00
file = open ( " diario.txt " , " r " , encoding = " utf8 " )
# Clean the diario
clean_diario = str ( )
# Remove the timestamps in each row
for row in file :
2017-03-18 20:32:34 +00:00
clean_diario + = row . split ( " | " , 1 ) [ 1 ] . lower ( )
2017-03-13 18:13:42 +00:00
# The text is split by newlines
generator = markovify . NewlineText ( clean_diario )
file . close ( )
if len ( arguments ) == 0 :
# Generate a sentence with a random start
2017-03-14 13:02:10 +00:00
text = generator . make_sentence ( tries = 50 )
2017-03-13 18:13:42 +00:00
else :
# Generate a sentence with a specific start
2017-03-22 17:02:59 +00:00
start_with = " " . join ( arguments )
2017-03-14 13:02:10 +00:00
try :
2017-03-22 17:02:59 +00:00
text = generator . make_sentence_with_start ( start_with , tries = 100 )
2017-03-14 13:02:10 +00:00
# No entry can start in that word.
except KeyError :
await update . message . reply ( bot , f " ⚠ Non sono state trovate corrispondenze nel diario dell ' inizio che hai specificato. " , parse_mode = " Markdown " )
return
if text is not None :
2017-03-23 18:43:26 +00:00
# Sanitize the text to prevent TelegramErrors
text = text . replace ( " _ " , " \ _ " ) . replace ( " * " , " \ * " ) . replace ( " ` " , " \ ` " ) . replace ( " [ " , " \ [ " )
2017-03-14 13:02:10 +00:00
await update . message . reply ( bot , f " *Frase generata:* \n { text } " , parse_mode = " Markdown " )
else :
await update . message . reply ( bot , f " ⚠ Il bot non è riuscito a generare una nuova frase. \n Se è la prima volta che vedi questo errore, riprova, altrimenti prova a cambiare configurazione. " )
2017-03-13 18:13:42 +00:00
2017-03-14 13:02:10 +00:00
async def help_cmd ( bot , update , arguments ) :
2017-03-05 17:14:45 +00:00
""" Visualizza la descrizione di un comando.
Sintassi : ` / help [ comando ] ` """
if len ( arguments ) == 0 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , help . __doc__ , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
elif len ( arguments ) > 1 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/help [comando]` " , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
else :
if arguments [ 0 ] in b . commands :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , b . commands [ arguments [ 0 ] ] . __doc__ , parse_mode = " Markdown " )
2017-03-05 17:14:45 +00:00
else :
2017-03-23 18:43:26 +00:00
await update . message . reply ( bot , " ⚠ Il comando specificato non esiste. " )
2017-03-05 17:14:45 +00:00
2017-02-27 22:16:42 +00:00
2017-03-05 18:12:02 +00:00
async def discord ( bot , update , arguments ) :
""" Manda un messaggio a #chat di Discord.
Sintassi : ` / discord < messaggio > ` """
2017-03-23 18:43:26 +00:00
# Try to login
logged_user = currently_logged_in ( update )
# Check if the user is logged in
if not logged_user :
await update . message . reply ( bot , " ⚠ Non hai ancora eseguito l ' accesso! Usa `/sync`. " , parse_mode = " Markdown " )
return
# Check if the currently logged in user is a Royal Games member
if not logged_user . royal :
await update . message . reply ( bot , " ⚠ Non sei autorizzato a eseguire questo comando. " )
return
# Check the command syntax
2017-03-05 18:12:02 +00:00
if len ( arguments ) == 0 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/discord <messaggio>` " , parse_mode = " Markdown " )
2017-03-05 18:12:02 +00:00
return
message = " " . join ( arguments )
2017-03-23 18:43:26 +00:00
# Find the message sender's Discord username
users = list ( d . client . get_all_members ( ) )
for user in users :
if user . id == logged_user . discord_id :
username = user . name
break
else :
# Use the telegram username
username = f " { update . message . sent_from } "
2017-03-05 18:12:02 +00:00
# Parameters to send
params = {
2017-03-23 18:43:26 +00:00
" username " : username ,
" content " : f " { message } "
2017-03-05 18:12:02 +00:00
}
# Headers to send
headers = {
" Content-Type " : " application/json "
}
# Request timeout is 10 seconds.
with async_timeout . timeout ( 10 ) :
# Create a new session for each request.
async with aiohttp . ClientSession ( ) as session :
# Send the request to the Discord webhook
async with session . request ( " POST " , royalbotconfig . discord_webhook , data = json . dumps ( params ) , headers = headers ) as response :
# Check if the request was successful
if response . status != 204 :
# Request failed
# Answer on Telegram
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ L ' invio del messaggio è fallito. Oops! " , parse_mode = " Markdown " )
2017-03-05 18:12:02 +00:00
# TODO: handle Discord webhooks errors
raise Exception ( " Qualcosa è andato storto durante l ' invio del messaggio a Discord. " )
# Answer on Telegram
2017-03-23 18:43:26 +00:00
await update . message . reply ( bot , " ✅ Richiesta inviata. " , parse_mode = " Markdown " )
2017-03-05 18:12:02 +00:00
2017-03-22 18:13:22 +00:00
async def sync_telegram ( bot , update , arguments ) :
2017-03-10 09:11:06 +00:00
""" Connetti il tuo account Telegram al Database Royal Games.
2017-03-09 14:52:02 +00:00
2017-03-10 09:11:06 +00:00
Sintassi : ` / sync < username > < password > ` """
2017-03-09 14:52:02 +00:00
if len ( arguments ) != 2 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/sync <username> <password>` " , parse_mode = " Markdown " )
2017-03-09 14:52:02 +00:00
return
2017-03-10 09:11:06 +00:00
# Try to login
session , logged_user = database . login ( arguments [ 0 ] , arguments [ 1 ] )
# Check if the login is successful
2017-03-09 14:52:02 +00:00
if logged_user is not None :
2017-03-10 09:11:06 +00:00
# Add the telegram_id to the user if it's missing
if logged_user . telegram_id is None :
logged_user . telegram_id = update . message . sent_from . user_id
session . commit ( )
2017-03-22 18:13:22 +00:00
print ( f " { logged_user } ha sincronizzato l ' account di Telegram. " )
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , f " Sincronizzazione riuscita! \n Sei loggato come ` { logged_user } `. " , parse_mode = " Markdown " )
2017-03-10 09:11:06 +00:00
else :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ L ' account è già stato sincronizzato. " , parse_mode = " Markdown " )
2017-03-09 14:52:02 +00:00
else :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Username o password non validi. " , parse_mode = " Markdown " )
2017-03-09 14:52:02 +00:00
2017-03-22 18:13:22 +00:00
async def sync_discord ( bot , message , arguments ) :
""" Connetti il tuo account Discord al Database Royal Games.
Sintassi : ` ! sync < username > < password > ` """
if len ( arguments ) != 2 :
await bot . send_message ( message . channel , " ⚠ Sintassi del comando non valida. \n `!sync <username> <password>` " )
return
# Try to login
session , logged_user = database . login ( arguments [ 0 ] , arguments [ 1 ] )
# Check if the login is successful
if logged_user is not None :
# Add the discord_id to the user if it's missing
if logged_user . discord_id is None :
logged_user . discord_id = int ( message . author . id )
session . commit ( )
print ( f " { logged_user } ha sincronizzato l ' account di Discord. " )
await bot . send_message ( message . channel , f " Sincronizzazione riuscita! \n Sei loggato come ` { logged_user } `. " )
else :
await bot . send_message ( message . channel , " ⚠ L ' account è già stato sincronizzato. " )
else :
await bot . send_message ( message . channel , " ⚠ Username o password non validi. " )
2017-03-10 09:34:27 +00:00
async def changepassword ( bot , update , arguments ) :
""" Cambia la tua password del Database Royal Games.
2017-03-10 10:52:20 +00:00
Sintassi : ` / changepassword < newpassword > ` """
if len ( arguments ) != 2 :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/changepassword <oldpassword> <newpassword>` " , parse_mode = " Markdown " )
2017-03-10 09:34:27 +00:00
return
2017-03-10 10:52:20 +00:00
# TODO: this can be improved, maybe?
2017-03-10 11:00:37 +00:00
logged_user = currently_logged_in ( update )
2017-03-10 09:34:27 +00:00
# Check if the login is successful
if logged_user is not None :
# Change the password
2017-03-10 10:52:20 +00:00
database . change_password ( logged_user . username , arguments [ 1 ] )
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , f " Il cambio password è riuscito! \n \n _Info per smanettoni: la tua password è hashata nel database come_ ` { logged_user . password } `. " , parse_mode = " Markdown " )
2017-03-10 09:34:27 +00:00
else :
2017-03-13 11:57:04 +00:00
await update . message . reply ( bot , " ⚠ Username o password non validi. " , parse_mode = " Markdown " )
2017-03-10 09:34:27 +00:00
2017-03-18 20:32:34 +00:00
async def cv ( bot , update , arguments ) :
""" Visualizza lo stato attuale della chat vocale Discord.
Sintassi : ` / cv ` """
if len ( arguments ) != 0 :
await update . message . reply ( bot , " ⚠ Sintassi del comando non valida. \n `/cv` " , parse_mode = " Markdown " )
return
# Wait for the Discord bot to login
2017-03-22 18:13:22 +00:00
while not d . client . is_logged_in :
2017-03-18 20:32:34 +00:00
await asyncio . sleep ( 1 )
# Find all the users in the server
# Change this if the bot is logged in more than one server at once?
2017-03-22 18:13:22 +00:00
users = list ( d . client . get_all_members ( ) )
2017-03-18 20:32:34 +00:00
# Find all the channels
channels = dict ( )
for user in users :
if user . voice_channel is not None :
if user . voice_channel . name not in channels :
channels [ user . voice_channel . name ] = list ( )
channels [ user . voice_channel . name ] . append ( user )
# Create the string to send to Telegram
to_send = str ( )
for channel in channels :
# Channel header
to_send + = f " * { channel } :* \n "
# Users in channel
for user in channels [ channel ] :
# Online status
if user . status . name == " online " :
2017-03-18 21:11:28 +00:00
# Online
2017-03-18 20:32:34 +00:00
status = " 🔵 "
2017-03-22 18:34:47 +00:00
elif user . status . name == " dnd " or ( user . game is not None and user . game . type == 1 ) :
2017-03-18 21:11:28 +00:00
# Do not disturb or streaming
2017-03-18 20:39:59 +00:00
status = " 🔴 "
2017-03-18 20:32:34 +00:00
elif user . status . name == " idle " :
2017-03-18 21:11:28 +00:00
# Idle
2017-03-18 20:32:34 +00:00
status = " ⚫ "
elif user . status . name == " offline " :
2017-03-18 21:11:28 +00:00
# Invisible
2017-03-18 20:32:34 +00:00
status = " ⚪ "
else :
2017-03-18 21:11:28 +00:00
# Unknown
2017-03-18 20:32:34 +00:00
status = " ❓ "
# Voice status
if user . bot :
2017-03-18 21:11:28 +00:00
# Music bot
2017-03-18 20:32:34 +00:00
volume = " 🎵 "
elif user . voice . deaf or user . voice . self_deaf :
2017-03-18 21:11:28 +00:00
# Deafened
2017-03-18 20:32:34 +00:00
volume = " 🔇 "
elif user . voice . mute or user . voice . self_mute :
2017-03-18 21:11:28 +00:00
# Muted
2017-03-18 20:32:34 +00:00
volume = " 🔈 "
else :
2017-03-18 21:11:28 +00:00
# Speaking
2017-03-18 20:32:34 +00:00
volume = " 🔊 "
# Game, is formatted
if user . game is not None :
2017-03-18 21:11:28 +00:00
# Playing
if user . game . type == 0 :
# Game name
game = f " - * { user . game . name } * "
# Streaming
elif user . game . type == 1 :
# Stream name and url
game = f " - [ { user . game . name } ]( { user . game . url } ) "
2017-03-18 20:32:34 +00:00
else :
game = " "
2017-03-18 21:11:28 +00:00
# Nickname if available, otherwise use the username
2017-03-18 20:32:34 +00:00
if user . nick is not None :
name = user . nick
else :
name = user . name
# Add the user
to_send + = f " { volume } { status } { name } { game } \n "
# Channel footer
to_send + = " \n "
2017-03-18 21:11:28 +00:00
await update . message . reply ( bot , to_send , parse_mode = " Markdown " , disable_web_page_preview = 1 )
2017-03-18 20:32:34 +00:00
2017-03-22 18:13:22 +00:00
2017-03-09 11:51:45 +00:00
if __name__ == " __main__ " :
2017-03-18 20:32:34 +00:00
# Init Telegram bot commands
2017-03-22 16:23:17 +00:00
b . commands [ " start " ] = start
2017-03-09 11:51:45 +00:00
b . commands [ " leggi " ] = leggi
b . commands [ " diario " ] = diario
b . commands [ " discord " ] = discord
2017-03-22 18:13:22 +00:00
b . commands [ " sync " ] = sync_telegram
2017-03-10 09:34:27 +00:00
b . commands [ " changepassword " ] = changepassword
2017-03-14 13:02:10 +00:00
b . commands [ " help " ] = help_cmd
2017-03-13 18:13:42 +00:00
b . commands [ " markov " ] = markov
2017-03-18 20:32:34 +00:00
b . commands [ " cv " ] = cv
2017-03-22 18:13:22 +00:00
# Init Discord bot commands
d . commands [ " sync " ] = sync_discord
2017-03-18 20:32:34 +00:00
# Init Telegram bot
loop . create_task ( b . run ( ) )
print ( " Telegram bot start scheduled! " )
# Init Discord bot
2017-03-22 18:13:22 +00:00
loop . create_task ( d . run ( ) )
2017-03-18 20:32:34 +00:00
print ( " Discord bot start scheduled! " )
# Run everything!
loop . run_forever ( )