#!/usr/bin/env python3 import sys oldexcepthook = sys.excepthook def newexcepthook(type,value,traceback): oldexcepthook(type,value,traceback) #input("Press ENTER to quit.") sys.excepthook = newexcepthook import os p = os.path.join pUp = os.path.dirname s = False if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): s = os.path.realpath(sys.executable) else: s = os.path.realpath(__file__) sp = pUp(s) # script start import configparser import threading import queue import datetime import json import logging #logging.basicConfig(level=logging.INFO) import asyncio import discord from discord.ext import tasks def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) userFolder = p(sp,"userdata") inQueue = [] inQueueLock = threading.Lock() outQueue = queue.Queue() fileLock = threading.Lock() thread = False fetchMessageLock = threading.Lock() def datetimeToString(dt): return json.dumps([dt.year,dt.month,dt.day,dt.hour,dt.minute,dt.second,dt.microsecond]) def datetimeFromString(js): ls = json.loads(js) return datetime.datetime(*ls) def stringToBool(st): if st.lower() in ["0","false","no"]: return False if st.lower() in ["1","true","yes"]: return True raise Exception("syntax","could not convert " +str(st)+ " to bool") def readConfig(): config = configparser.ConfigParser() config.read(s.rsplit(".",1)[0] + ".ini") global cTrackMessages cTrackMessages = stringToBool(config["lostMessageTracker"]["trackMessages"]) global cAcquireLostMessagesOnReconnect cAcquireLostMessagesOnReconnect = stringToBool(config["lostMessageTracker"]["acquireAllOnReconnect"]) global cObserveServerChannels cObserveServerChannels = stringToBool(config["default"]["observeServerChannels"]) global cObserveGroupChannels cObserveGroupChannels = stringToBool(config["default"]["observeGroupChannels"]) global cObserveDirectMessageChannels cObserveDirectMessageChannels = stringToBool(config["default"]["observeDirectMessageChannels"]) readConfig() def shouldIgnoreChannel(channel): if type(channel) == discord.TextChannel: if cObserveServerChannels: return False return True if type(channel) == discord.GroupChannel: if cObserveGroupChannels: return False return True if type(channel) == discord.DMChannel: if cObserveDirectMessageChannels: return False return True return True class discordClient(threading.Thread): self = False def __init__(self,token): threading.Thread.__init__(self) self.token = token self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.client = discord.Client() self.defineEvents() def defineEvents(self): @self.client.event async def on_ready(*args,**kwargs): await self.on_ready(*args,**kwargs) @self.client.event async def on_resumed(*args,**kwargs): await self.on_resumed(*args,**kwargs) @self.client.event async def on_message(*args,**kwargs): await self.on_message(*args,**kwargs) def getChannelFolder(self,channel): return p(self.clientFolder,"channels",str(channel.id)) def getChannelMessageIdFile(self,channel): return p(self.getChannelFolder(channel),"messageIDs.txt") def readChannelMessageIdFile(self,channel): cfile = self.getChannelMessageIdFile(channel) if not os.path.isfile(cfile): return [] data = None with open(cfile,"r") as fhandler: data = fhandler.read() data = data.split("\n") data.pop(-1) return data def addToChannelMessageIdFile(self,message): cfile = self.getChannelMessageIdFile(message.channel) if not os.path.isfile(cfile): cfolder = self.getChannelFolder(message.channel) if not os.path.isdir(cfolder): os.makedirs(cfolder) with open(cfile,"w") as fhandler: pass with open(cfile,"a") as fhandler: fhandler.write(str(message.id) + "\n") def trackMessage(self,message): with fileLock: messageIDs = self.readChannelMessageIdFile(message.channel) if not message.id in messageIDs: self.addToChannelMessageIdFile(message) return True return False def getTrackedChannels(self): for guild in self.client.guilds: for channel in guild.channels: if not shouldIgnoreChannel(channel): yield channel for channel in self.client.private_channels: if not shouldIgnoreChannel(channel): yield channel async def acquireAllLostMessages(self): eprint("Acquiring lost messages...") with fetchMessageLock: for channel in self.getTrackedChannels(): messageIDs = False with fileLock: messageIDs = self.readChannelMessageIdFile(channel) messages = [] lim = 999999 if messageIDs == []: lim = 100 async for message in channel.history(limit=lim): if str(message.id) in messageIDs: break messages.append(message) messages.reverse() messagesFiltered = [] for message in messages: if not str(message.id) in messageIDs: messagesFiltered.append(message) with fileLock: messageIDs = self.readChannelMessageIdFile(channel) for message in messagesFiltered: if not str(message.id) in messageIDs: self.addToChannelMessageIdFile(message) await self.on_message(message, tracked = True) eprint("Done acquiring lost messages.") def run(self): self.discordTask = self.loop.create_task(self.client.start(self.token,bot = False)) self.loop.run_forever() async def on_ready(self): eprint("on_ready") self.clientFolder = p(userFolder,str(self.client.user.id)) if cAcquireLostMessagesOnReconnect: await self.acquireAllLostMessages() async def on_resumed(self): eprint("on_resumed") #if cAcquireLostMessagesOnReconnect: # await self.acquireAllLostMessages() async def on_message(self,message,tracked = False): if shouldIgnoreChannel(message.channel): return if tracked == False: with fetchMessageLock, fileLock: messageIDs = self.readChannelMessageIdFile(message.channel) if not str(message.id) in messageIDs: self.addToChannelMessageIdFile(message) outQueue.put(["message",message,tracked]) async def stop(self): eprint("Waiting for client to close...") await self.client.close() eprint("Stopping loop.") self.loop.stop() def run(token): global thread thread = discordClient(token) thread.start()