2017-05-04 05:22:27 +02:00
from config import config
2017-05-04 07:16:49 +02:00
from titanembeds . database import DatabaseInterface
2017-05-20 09:50:29 +02:00
from titanembeds . commands import Commands
2017-08-20 21:56:54 +02:00
from titanembeds . socketio import SocketIOInterface
2017-05-04 05:22:27 +02:00
import discord
2017-05-04 07:16:49 +02:00
import aiohttp
import asyncio
2017-05-14 23:05:51 +02:00
import sys
2017-05-15 00:09:38 +02:00
import logging
2017-09-06 06:58:46 +02:00
import json
2017-05-15 00:19:15 +02:00
logging . basicConfig ( filename = ' titanbot.log ' , level = logging . INFO , format = ' %(asctime)s %(message)s ' , datefmt = ' % m/ %d / % Y % I: % M: % S % p ' )
2017-09-07 06:00:09 +02:00
handler = logging . FileHandler ( config . get ( " logging-location " , " titanbot.log " ) )
2017-05-15 00:09:38 +02:00
logging . getLogger ( ' TitanBot ' )
logging . getLogger ( ' sqlalchemy ' )
2017-05-04 05:22:27 +02:00
2017-05-04 07:16:49 +02:00
class Titan ( discord . Client ) :
def __init__ ( self ) :
2017-09-06 06:58:46 +02:00
super ( ) . __init__ ( max_messages = 20000 )
2017-05-04 07:16:49 +02:00
self . aiosession = aiohttp . ClientSession ( loop = self . loop )
self . http . user_agent + = ' TitanEmbeds-Bot '
self . database = DatabaseInterface ( self )
2017-05-20 09:50:29 +02:00
self . command = Commands ( self , self . database )
2017-08-20 21:56:54 +02:00
self . socketio = SocketIOInterface ( self , config [ " redis-uri " ] )
2017-06-13 03:39:49 +02:00
self . database_connected = False
self . loop . create_task ( self . send_webserver_heartbeat ( ) )
2017-05-04 05:22:27 +02:00
2017-05-04 07:16:49 +02:00
def _cleanup ( self ) :
try :
self . loop . run_until_complete ( self . logout ( ) )
except : # Can be ignored
pass
pending = asyncio . Task . all_tasks ( )
gathered = asyncio . gather ( * pending )
try :
gathered . cancel ( )
self . loop . run_until_complete ( gathered )
gathered . exception ( )
except : # Can be ignored
pass
2017-06-13 03:39:49 +02:00
2017-06-13 04:29:39 +02:00
async def wait_until_dbonline ( self ) :
2017-06-13 03:39:49 +02:00
while not self . database_connected :
await asyncio . sleep ( 1 ) # Wait until db is connected
2017-06-13 04:29:39 +02:00
async def send_webserver_heartbeat ( self ) :
await self . wait_until_ready ( )
await self . wait_until_dbonline ( )
2017-06-13 04:16:45 +02:00
last_db_conn_status = False
2017-06-13 03:39:49 +02:00
while not self . is_closed :
2017-06-13 04:16:45 +02:00
try :
await self . database . send_webserver_heartbeat ( )
self . database_connected = True
except :
self . database_connected = False
if last_db_conn_status != self . database_connected and config . get ( " errorreporting-channelid " ) :
error_channel = self . get_channel ( config [ " errorreporting-channelid " ] )
if self . database_connected :
await self . send_message ( error_channel , " Titan has obtained connection to the database! " )
else :
await self . send_message ( error_channel , " Titan has lost connection to the database! Don ' t panic!! We ' ll sort this out... hopefully soon. " )
last_db_conn_status = self . database_connected
2017-06-13 04:36:38 +02:00
await asyncio . sleep ( 60 )
2017-05-04 05:22:27 +02:00
2017-05-04 07:16:49 +02:00
def run ( self ) :
try :
self . loop . run_until_complete ( self . start ( config [ " bot-token " ] ) )
except discord . errors . LoginFailure :
print ( " Invalid bot token in config! " )
finally :
try :
self . _cleanup ( )
except Exception as e :
print ( " Error in cleanup: " , e )
self . loop . close ( )
async def on_ready ( self ) :
print ( ' Titan [DiscordBot] ' )
print ( ' Logged in as the following user: ' )
print ( self . user . name )
print ( self . user . id )
print ( ' ------ ' )
await self . change_presence (
2017-09-01 06:45:35 +02:00
game = discord . Game ( name = " Embed your Discord server! Visit https://TitanEmbeds.com/ " ) , status = discord . Status . online
2017-05-04 07:16:49 +02:00
)
try :
2017-09-05 08:54:54 +02:00
await self . database . connect ( config [ " database-uri " ] )
2017-06-13 03:39:49 +02:00
self . database_connected = True
2017-05-04 07:16:49 +02:00
except Exception :
self . logger . error ( " Unable to connect to specified database! " )
traceback . print_exc ( )
await self . logout ( )
return
2017-05-27 19:50:52 +02:00
2017-05-14 23:05:51 +02:00
if " no-init " not in sys . argv :
for server in self . servers :
await self . database . update_guild ( server )
if server . large :
2017-05-27 21:18:52 +02:00
await self . request_offline_members ( server )
2017-09-07 02:59:22 +02:00
if server . me . server_permissions . ban_members :
server_bans = await self . get_bans ( server )
else :
server_bans = [ ]
2017-05-14 23:05:51 +02:00
for member in server . members :
banned = member . id in [ u . id for u in server_bans ]
await self . database . update_guild_member (
member ,
True ,
banned
)
await self . database . flag_unactive_guild_members ( server . id , server . members )
await self . database . flag_unactive_bans ( server . id , server_bans )
await self . database . remove_unused_guilds ( self . servers )
else :
print ( " Skipping indexing server due to no-init flag " )
2017-05-07 00:27:07 +02:00
2017-05-06 10:03:52 +02:00
async def on_message ( self , message ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-06 10:03:52 +02:00
await self . database . push_message ( message )
2017-08-20 21:56:54 +02:00
await self . socketio . on_message ( message )
2017-05-27 19:50:52 +02:00
2017-05-20 09:50:29 +02:00
msg_arr = message . content . split ( ) # split the message
if len ( message . content . split ( ) ) > 1 and message . server : #making sure there is actually stuff in the message and have arguments and check if it is sent in server (not PM)
if msg_arr [ 0 ] == " <@ {} > " . format ( self . user . id ) : #make sure it is mention
msg_cmd = msg_arr [ 1 ] . lower ( ) # get command
cmd = getattr ( self . command , msg_cmd , None ) #check if cmd exist, if not its none
if cmd : # if cmd is not none...
await self . send_typing ( message . channel ) #this looks nice
await getattr ( self . command , msg_cmd ) ( message ) #actually run cmd, passing in msg obj
2017-05-06 10:03:52 +02:00
async def on_message_edit ( self , message_before , message_after ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-06 10:03:52 +02:00
await self . database . update_message ( message_after )
2017-08-22 08:57:30 +02:00
await self . socketio . on_message_update ( message_after )
2017-05-07 00:27:07 +02:00
async def on_message_delete ( self , message ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . delete_message ( message )
2017-08-22 08:57:30 +02:00
await self . socketio . on_message_delete ( message )
2017-05-07 00:27:07 +02:00
async def on_server_join ( self , guild ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( guild )
2017-05-09 05:19:45 +02:00
for channel in guild . channels :
2017-09-07 02:59:22 +02:00
if not channel . permissions_for ( channel . server . me ) . read_messages :
continue
2017-05-09 05:19:45 +02:00
async for message in self . logs_from ( channel , limit = 50 , reverse = True ) :
await self . database . push_message ( message )
2017-05-09 05:49:37 +02:00
for member in guild . members :
2017-05-20 03:45:38 +02:00
await self . database . update_guild_member ( member , True , False )
2017-09-07 02:59:22 +02:00
if guild . me . server_permissions . ban_members :
banned = await self . get_bans ( guild )
for ban in banned :
await self . database . update_guild_member ( ban , False , True )
2017-05-07 00:27:07 +02:00
async def on_server_remove ( self , guild ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . remove_guild ( guild )
async def on_server_update ( self , guildbefore , guildafter ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( guildafter )
2017-08-25 08:37:14 +02:00
await self . socketio . on_guild_update ( guildafter )
2017-05-07 00:27:07 +02:00
async def on_server_role_create ( self , role ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
if role . name == self . user . name and role . managed :
await asyncio . sleep ( 2 )
await self . database . update_guild ( role . server )
2017-08-25 08:37:14 +02:00
await self . socketio . on_guild_role_create ( role )
2017-05-07 00:27:07 +02:00
async def on_server_role_delete ( self , role ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
if role . server . me not in role . server . members :
return
await self . database . update_guild ( role . server )
2017-08-25 08:37:14 +02:00
await self . socketio . on_guild_role_delete ( role )
2017-05-07 00:27:07 +02:00
async def on_server_role_update ( self , rolebefore , roleafter ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( roleafter . server )
2017-09-05 09:37:04 +02:00
await self . socketio . on_guild_role_update ( roleafter )
2017-05-07 00:27:07 +02:00
async def on_channel_delete ( self , channel ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( channel . server )
2017-08-25 08:37:14 +02:00
await self . socketio . on_channel_delete ( channel )
2017-05-07 00:27:07 +02:00
async def on_channel_create ( self , channel ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( channel . server )
2017-08-25 08:37:14 +02:00
await self . socketio . on_channel_create ( channel )
2017-05-07 00:27:07 +02:00
async def on_channel_update ( self , channelbefore , channelafter ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild ( channelafter . server )
2017-08-25 08:37:14 +02:00
await self . socketio . on_channel_update ( channelafter )
2017-05-07 00:27:07 +02:00
async def on_member_join ( self , member ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild_member ( member , active = True , banned = False )
2017-08-22 08:57:30 +02:00
await self . socketio . on_guild_member_add ( member )
2017-05-07 00:27:07 +02:00
async def on_member_remove ( self , member ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild_member ( member , active = False , banned = False )
2017-08-22 08:57:30 +02:00
await self . socketio . on_guild_member_remove ( member )
2017-05-07 00:27:07 +02:00
async def on_member_update ( self , memberbefore , memberafter ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-07 00:27:07 +02:00
await self . database . update_guild_member ( memberafter )
2017-08-22 08:57:30 +02:00
await self . socketio . on_guild_member_update ( memberafter )
2017-05-07 00:27:07 +02:00
async def on_member_ban ( self , member ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-14 23:05:51 +02:00
if self . user . id == member . id :
2017-05-07 00:27:07 +02:00
return
await self . database . update_guild_member ( member , active = False , banned = True )
2017-05-14 23:05:51 +02:00
async def on_member_unban ( self , server , user ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-27 19:50:52 +02:00
await self . database . unban_server_user ( user , server )
2017-05-30 00:07:32 +02:00
async def on_server_emojis_update ( self , before , after ) :
2017-06-13 04:29:39 +02:00
await self . wait_until_dbonline ( )
2017-05-30 00:07:32 +02:00
if len ( after ) == 0 :
await self . database . update_guild ( before [ 0 ] . server )
2017-08-22 09:53:41 +02:00
await self . socketio . on_guild_emojis_update ( before )
2017-05-30 00:07:32 +02:00
else :
await self . database . update_guild ( after [ 0 ] . server )
2017-08-22 09:53:41 +02:00
await self . socketio . on_guild_emojis_update ( after )
2017-07-01 08:52:21 +02:00
async def on_webhooks_update ( self , server ) :
await self . wait_until_dbonline ( )
await self . database . update_guild ( server )
2017-09-06 06:58:46 +02:00
async def on_socket_raw_receive ( self , msg ) :
if type ( msg ) is not str :
return
msg = json . loads ( msg )
if msg [ " op " ] != 0 :
return
action = msg [ " t " ]
await asyncio . sleep ( 1 )
if action == " MESSAGE_UPDATE " :
if not self . in_messages_cache ( msg [ " d " ] [ " id " ] ) :
channel = self . get_channel ( msg [ " d " ] [ " channel_id " ] )
message = await self . get_message ( channel , msg [ " d " ] [ " id " ] )
await self . on_message_edit ( None , message )
if action == " MESSAGE_DELETE " :
if not self . in_messages_cache ( msg [ " d " ] [ " id " ] ) :
await self . process_raw_message_delete ( msg [ " d " ] [ " id " ] , msg [ " d " ] [ " channel_id " ] )
if action == " MESSAGE_DELETE_BULK " :
for msgid in msg [ " d " ] [ " ids " ] :
if not self . in_messages_cache ( msgid ) :
await self . process_raw_message_delete ( msgid , msg [ " d " ] [ " channel_id " ] )
async def process_raw_message_delete ( self , msg_id , channel_id ) :
channel = self . get_channel ( channel_id )
msg = discord . Message ( channel = channel , reactions = [ ] , id = msg_id , type = 0 , timestamp = " 2017-01-15T02:59:58 " , content = " What fun is there in making sense? " ) # Procreate a fake message object
await self . on_message_delete ( msg )
def in_messages_cache ( self , msg_id ) :
for msg in self . messages :
if msg . id == msg_id :
return True
2017-09-07 06:00:09 +02:00
return False