discord-thread/discord-thread.py

222 lines
6.1 KiB
Python

#!/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()