1050 lines
29 KiB
Python
1050 lines
29 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.") # - this causes issues with Qt's input hook
|
|
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)
|
|
os.chdir(sp)
|
|
|
|
# script start
|
|
import qtpy
|
|
from qtpy.QtGui import *
|
|
from qtpy.QtWidgets import *
|
|
from qtpy.QtCore import *
|
|
from qtpy.QtMultimedia import QSound
|
|
import urllib.parse
|
|
import html
|
|
import threading
|
|
import asyncio
|
|
import logging
|
|
import configparser
|
|
import datetime
|
|
import json
|
|
import traceback
|
|
distro = "Discord but fast"
|
|
version = (0,2,0)
|
|
versionString = ".".join(map(str,version))
|
|
pathLogins = p(sp,"logins")
|
|
guiLock = threading.Condition()
|
|
openGuis = []
|
|
guiTasks = []
|
|
ready = False
|
|
online = True
|
|
unreadChannels = []
|
|
oldUnreadChannels = []
|
|
|
|
def doNothing():
|
|
pass
|
|
|
|
def listMove(list,ind,newInd):
|
|
if newInd > ind: newInd = newInd - 1
|
|
list.insert(newInd,list.pop(ind))
|
|
|
|
def addGuiTask(gui,func,args,kwargs,condition = None):
|
|
guiTasks.append((gui,func,args,kwargs,condition))
|
|
|
|
def getTitle(text = False):
|
|
title = ""
|
|
if text == False:
|
|
title = distro + " " + versionString
|
|
else:
|
|
title = text + " - " + distro + " " + versionString
|
|
return title
|
|
|
|
def getChannelDisplayName(channel):
|
|
channelName = "Unknown"
|
|
if type(channel) == discord.DMChannel:
|
|
channelName = channel.recipient.name + "#" +channel.recipient.discriminator
|
|
elif type(channel) == discord.GroupChannel:
|
|
if channel.name:
|
|
channelName = "Group: " +channel.name
|
|
else:
|
|
channelName = "Group: [No name]"
|
|
|
|
if channel.id in unreadChannels:
|
|
channelName = "* " +channelName
|
|
|
|
return channelName
|
|
|
|
def playNotificationSound():
|
|
if notificationSound: notificationSound.play()
|
|
|
|
class dbfApp:
|
|
def __init__(self):
|
|
self.app = QApplication(sys.argv)
|
|
self.uiTimer = QTimer(self.app)
|
|
self.uiTimer.setInterval(round(float(config["performance"]["uiTickTime"])*1000))
|
|
self.uiTimer.timeout.connect(self.update)
|
|
self.uiTimer.start()
|
|
|
|
def update(self):
|
|
guiLock.acquire()
|
|
|
|
index = 0
|
|
length = len(openGuis)
|
|
while index < length:
|
|
if openGuis[index].closed == True:
|
|
del openGuis[index]
|
|
length -= 1
|
|
continue
|
|
|
|
index += 1
|
|
|
|
if len(unreadChannels) > 0:
|
|
activeWindow = app.app.activeWindow()
|
|
if activeWindow != None:
|
|
index = 0
|
|
while index < length:
|
|
gui = openGuis[index]
|
|
if gui.window == activeWindow:
|
|
activeWindow = gui
|
|
break
|
|
index += 1
|
|
|
|
if activeWindow.type == "channel":
|
|
if activeWindow.channel.id in unreadChannels:
|
|
if activeWindow.messageLog.scrollbar.value() == activeWindow.messageLog.scrollbar.maximum():
|
|
del unreadChannels[unreadChannels.index(activeWindow.channel.id)]
|
|
|
|
index = 0
|
|
while index < length:
|
|
gui = openGuis[index]
|
|
if gui.type == "channel":
|
|
if gui.channel.id in unreadChannels:
|
|
if gui.read == False: index += 1; continue
|
|
gui.title = getChannelDisplayName(gui.channel)
|
|
gui.window.setWindowTitle(getTitle(gui.title))
|
|
gui.read = False
|
|
else:
|
|
if gui.read == True: index += 1; continue
|
|
gui.title = getChannelDisplayName(gui.channel)
|
|
gui.window.setWindowTitle(getTitle(gui.title))
|
|
gui.read = True
|
|
index += 1
|
|
|
|
global oldUnreadChannels
|
|
unreadChannelsLength = len(unreadChannels)
|
|
if oldUnreadChannels != unreadChannels:
|
|
for gui in openGuis:
|
|
if gui.type == "channels":
|
|
if unreadChannelsLength > 0:
|
|
gui.title = "* Channels"
|
|
gui.window.setWindowTitle(getTitle(gui.title))
|
|
else:
|
|
gui.title = "Channels"
|
|
gui.window.setWindowTitle(getTitle(gui.title))
|
|
gui.repopulateChannels("conversations")
|
|
oldUnreadChannels = unreadChannels.copy()
|
|
fh = open(p(loginPath,"unreadChannels.json"),"w")
|
|
fh.write(json.dumps(unreadChannels))
|
|
fh.close()
|
|
|
|
conditions = []
|
|
length = len(guiTasks)
|
|
while length > 0:
|
|
task = guiTasks.pop(0)
|
|
gui = task[0]
|
|
length -= 1
|
|
|
|
condition = task[4]
|
|
if condition: conditions.append(condition)
|
|
|
|
if gui == None:
|
|
task[1](*task[2],**task[3])
|
|
continue
|
|
|
|
if gui == "open":
|
|
openGuis.append(task[1](*task[2],**task[3]))
|
|
continue
|
|
|
|
if gui.closed == True: continue
|
|
task[1](*task[2],**task[3])
|
|
guiLock.notifyAll()
|
|
guiLock.release()
|
|
for condition in conditions:
|
|
condition.acquire()
|
|
condition.notifyAll()
|
|
condition.release()
|
|
|
|
class dbfMainWindow(QMainWindow):
|
|
def __init__(self,*args,customEventFilter = None,**kwargs):
|
|
super(dbfMainWindow,self).__init__(*args,**kwargs)
|
|
if customEventFilter != None:
|
|
self.customEventFilter = customEventFilter
|
|
self.installEventFilter(self)
|
|
|
|
def eventFilter(self,source,event):
|
|
rtn = self.customEventFilter[1](self.customEventFilter[0],source,event)
|
|
if rtn == None: rtn = False
|
|
return rtn
|
|
|
|
class dbfTextBrowser:
|
|
def __init__(self,*args,**kwargs):
|
|
self.widget = QTextBrowser(*args,**kwargs)
|
|
self.widget.setOpenLinks(False)
|
|
self.widget.anchorClicked.connect(self.anchorClicked)
|
|
self.scrollbar = self.widget.verticalScrollBar()
|
|
self.links = []
|
|
|
|
def getHtml(self,texts):
|
|
htmlOut = ""
|
|
for text in texts:
|
|
processedText = ""
|
|
raw = False
|
|
#print(str(text))
|
|
if "raw" in text: raw = text["raw"]
|
|
if raw:
|
|
processedText = text["text"]
|
|
else:
|
|
processedText = html.escape(text["text"]).replace("\n","<br>")
|
|
|
|
if "link" in text:
|
|
index = len(self.links)
|
|
self.links.append(text["link"])
|
|
processedText = '<a href="' +html.escape(str(index))+ '">' +processedText+ '</a>'
|
|
|
|
if "style" in text:
|
|
processedText = '<span style="' +html.escape(text["style"])+ '">' +processedText+ '</span>'
|
|
|
|
htmlOut += processedText
|
|
return htmlOut
|
|
|
|
def set(self,texts):
|
|
oldValue = self.scrollbar.value()
|
|
isMaxScroll = (oldValue == self.scrollbar.maximum())
|
|
self.links = []
|
|
self.widget.clear()
|
|
self.widget.insertHtml(self.getHtml(texts))
|
|
if isMaxScroll:
|
|
self.scrollbar.setValue(self.scrollbar.maximum())
|
|
else:
|
|
self.scrollbar.setValue(oldValue)
|
|
|
|
def append(self,texts):
|
|
oldValue = self.scrollbar.value()
|
|
isMaxScroll = (oldValue == self.scrollbar.maximum())
|
|
self.widget.insertHtml(self.getHtml(texts))
|
|
if isMaxScroll:
|
|
self.scrollbar.setValue(self.scrollbar.maximum())
|
|
else:
|
|
self.scrollbar.setValue(oldValue)
|
|
|
|
def anchorClicked(self,url):
|
|
urlFunc = self.links[int(urllib.parse.unquote(url.toString()))]
|
|
urlFunc[0](*urlFunc[1],**urlFunc[2])
|
|
|
|
|
|
class guiLoginChooser:
|
|
def __init__(self):
|
|
self.closed = False
|
|
self.type = "loginChooser"
|
|
self.title = "Login"
|
|
self.width = 300
|
|
self.height = 78
|
|
self.window = QWidget()
|
|
global style
|
|
self.window.setStyleSheet(style)
|
|
self.window.setWindowTitle(getTitle(self.title))
|
|
self.window.resize(self.width,self.height)
|
|
|
|
self.window.resizeEvent = self.resizeEvent
|
|
self.window.closeEvent = self.closeEvent
|
|
self.createElements()
|
|
|
|
def createElements(self):
|
|
self.label = QLabel("Choose an account:",self.window)
|
|
self.label.setAlignment(Qt.AlignCenter)
|
|
self.dropdown = QComboBox(self.window)
|
|
self.buttonNew = QPushButton("New",self.window)
|
|
self.buttonNew.clicked.connect(self.newLogin)
|
|
self.buttonLogin = QPushButton("Login",self.window)
|
|
self.buttonLogin.clicked.connect(self.login)
|
|
|
|
for root,dirs,files in os.walk(pathLogins):
|
|
for file in dirs:
|
|
ffile = p(root,file)
|
|
lfile = ffile.rsplit(os.sep,1)[1]
|
|
self.dropdown.addItem(lfile)
|
|
break
|
|
self.resizeElements()
|
|
self.window.show()
|
|
|
|
def resizeElements(self):
|
|
self.label.move(5,5)
|
|
self.label.resize(self.width - 10,12)
|
|
self.dropdown.move(40,20)
|
|
self.dropdown.resize(self.width - 80,22)
|
|
self.buttonLogin.move(self.width - 64 - 5,self.height - 24 - 5)
|
|
self.buttonLogin.resize(64,24)
|
|
self.buttonNew.move(5,self.height - 24 - 5)
|
|
self.buttonNew.resize(64,24)
|
|
|
|
def resizeEvent(self, event):
|
|
self.width = self.window.width()
|
|
self.height = self.window.height()
|
|
self.resizeElements()
|
|
QWidget.resizeEvent(self.window, event)
|
|
|
|
def newLogin(self, event):
|
|
openGuis.append(guiNewLogin())
|
|
self.window.close()
|
|
|
|
def login(self, event):
|
|
threading.Thread(target=init,args=(self.dropdown.currentText(),)).start()
|
|
|
|
def closeEvent(self, event):
|
|
self.closed = True
|
|
|
|
class guiNewLogin:
|
|
def __init__(self):
|
|
self.closed = False
|
|
self.type = "newLogin"
|
|
self.title = "Create new login"
|
|
self.width = 300
|
|
self.height = 84
|
|
self.window = QWidget()
|
|
global style
|
|
self.window.setStyleSheet(style)
|
|
self.window.setWindowTitle(getTitle(self.title))
|
|
self.window.resize(self.width,self.height)
|
|
|
|
self.window.resizeEvent = self.resizeEvent
|
|
self.window.closeEvent = self.closeEvent
|
|
self.createElements()
|
|
|
|
def createElements(self):
|
|
self.labelName = QLabel("Name:",self.window)
|
|
self.labelName.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
self.textName = QLineEdit(self.window)
|
|
self.labelToken = QLabel("Token:",self.window)
|
|
self.labelToken.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
self.textToken = QLineEdit(self.window)
|
|
|
|
self.buttonBack = QPushButton("< Back",self.window)
|
|
self.buttonBack.clicked.connect(self.login)
|
|
self.buttonAdd = QPushButton("Add",self.window)
|
|
self.buttonAdd.clicked.connect(self.createLogin)
|
|
|
|
self.resizeElements()
|
|
self.window.show()
|
|
|
|
def resizeElements(self):
|
|
self.labelName.move(5,5)
|
|
self.labelName.resize(62,22)
|
|
self.textName.move(69,5)
|
|
self.textName.resize(self.width - 69 - 15,22)
|
|
self.labelToken.move(5,27)
|
|
self.labelToken.resize(62,22)
|
|
self.textToken.move(69,27)
|
|
self.textToken.resize(self.width - 69 - 15,22)
|
|
self.buttonBack.move(5,self.height - 24 - 5)
|
|
self.buttonBack.resize(64,24)
|
|
self.buttonAdd.move(self.width - 64 - 5,self.height - 24 - 5)
|
|
self.buttonAdd.resize(64,24)
|
|
|
|
def resizeEvent(self, event):
|
|
self.width = self.window.width()
|
|
self.height = self.window.height()
|
|
self.resizeElements()
|
|
QWidget.resizeEvent(self.window, event)
|
|
|
|
def login(self, event):
|
|
openGuis.append(guiLoginChooser())
|
|
self.window.close()
|
|
|
|
def createLogin(self, event):
|
|
global pathLogins
|
|
os.makedirs(p(pathLogins,self.textName.text(),"messageTimes"))
|
|
tf = open(p(pathLogins,self.textName.text(),"token"),"w")
|
|
tf.write(self.textToken.text())
|
|
tf.close()
|
|
self.login(None)
|
|
|
|
def closeEvent(self, event):
|
|
self.closed = True
|
|
|
|
class guiLoginProgress:
|
|
def __init__(self):
|
|
self.closed = False
|
|
self.type = "loginProgress"
|
|
self.title = "Logging in..."
|
|
self.width = 320
|
|
self.height = 240
|
|
self.window = QWidget()
|
|
global style
|
|
self.window.setStyleSheet(style)
|
|
self.window.setWindowTitle(getTitle(self.title))
|
|
self.window.resize(self.width,self.height)
|
|
|
|
self.window.resizeEvent = self.resizeEvent
|
|
self.window.closeEvent = self.closeEvent
|
|
self.createElements()
|
|
|
|
def createElements(self):
|
|
self.log = dbfTextBrowser(self.window)
|
|
self.log.widget.setFont(fonts["monospace"])
|
|
|
|
self.logScroll = self.log.widget.verticalScrollBar()
|
|
self.resizeElements()
|
|
self.window.show()
|
|
|
|
def resizeElements(self):
|
|
self.log.widget.move(0,0)
|
|
self.log.widget.resize(self.width,self.height)
|
|
|
|
def resizeEvent(self, event):
|
|
self.width = self.window.width()
|
|
self.height = self.window.height()
|
|
self.resizeElements()
|
|
QWidget.resizeEvent(self.window, event)
|
|
|
|
def closeEvent(self, event):
|
|
self.closed = True
|
|
QWidget.closeEvent(self.window, event)
|
|
|
|
class guiChannels:
|
|
def __init__(self):
|
|
self.closed = False
|
|
self.type = "channels"
|
|
self.title = "Channels"
|
|
global unreadChannels
|
|
if len(unreadChannels) > 0: self.title = "* " +self.title
|
|
self.width = 200
|
|
self.height = 350
|
|
self.window = dbfMainWindow(customEventFilter = (self,self.eventFilter))
|
|
global style
|
|
self.window.setStyleSheet(style)
|
|
self.window.setWindowTitle(getTitle(self.title))
|
|
self.window.resize(self.width,self.height)
|
|
self.window.eventFilter = None
|
|
|
|
self.window.resizeEvent = self.resizeEvent
|
|
self.window.closeEvent = self.closeEvent
|
|
self.createElements()
|
|
|
|
def createElements(self):
|
|
self.menuBar = self.window.menuBar()
|
|
self.fileMenu = self.menuBar.addMenu("File")
|
|
|
|
self.conversations = []
|
|
self.friends = []
|
|
|
|
self.picture = QLabel(self.window)
|
|
self.picturePix = QPixmap("assets/images/windows.png").scaled(32,32,Qt.KeepAspectRatio)
|
|
self.picture.setPixmap(self.picturePix)
|
|
self.name = QLabel(self.window)
|
|
self.name.setStyleSheet("font-weight: bold")
|
|
self.name.setText(client.user.name + "#" +client.user.discriminator)
|
|
self.status = QLabel(self.window)
|
|
self.status.setText("A status")
|
|
|
|
self.tabs = QTabWidget(self.window)
|
|
|
|
self.conversationTab = QWidget(self.window)
|
|
self.tabs.addTab(self.conversationTab,"Conversations")
|
|
self.conversationList = QListWidget(self.conversationTab)
|
|
self.conversationList.itemDoubleClicked.connect(self.openChannel)
|
|
|
|
self.friendTab = QWidget(self.window)
|
|
self.tabs.addTab(self.friendTab,"Friends")
|
|
|
|
self.resizeElements()
|
|
self.window.show()
|
|
self.resizeElements() # Taking a hammer to solve the issue, here. Without calling resizeElements twice, the lists inside the tabs do not resize correctly when the window first opens.
|
|
|
|
self.tabs.currentChanged.connect(self.changedTab)
|
|
|
|
def resizeElements(self):
|
|
mb = self.menuBar.height()
|
|
self.picture.move(5,mb + 5)
|
|
self.picture.resize(32,32)
|
|
self.name.move(32+10, mb+5)
|
|
self.name.resize(self.width-32-15, 16)
|
|
self.status.move(32+10, mb+5+16)
|
|
self.status.resize(self.width-32-15, 16)
|
|
self.tabs.move(0, mb+32+10)
|
|
self.tabs.resize(self.width, self.height-mb-32-10)
|
|
self.conversationList.resize(self.conversationTab.width(),self.conversationTab.height())
|
|
|
|
def resizeEvent(self, event):
|
|
self.width = self.window.width()
|
|
self.height = self.window.height()
|
|
self.resizeElements()
|
|
QWidget.resizeEvent(self.window, event)
|
|
|
|
def closeEvent(self, event):
|
|
self.closed = True
|
|
QWidget.closeEvent(self.window, event)
|
|
|
|
def clearChannels(self,list):
|
|
channels = None
|
|
qlist = None
|
|
if list == "conversations":
|
|
channels = self.conversations
|
|
qlist = self.conversationList
|
|
|
|
channels.clear()
|
|
qlist.clear()
|
|
|
|
def repopulateChannels(self,list):
|
|
channels = None
|
|
qlist = None
|
|
if list == "conversations":
|
|
channels = self.conversations
|
|
qlist = self.conversationList
|
|
qlist.clear()
|
|
for channel in channels:
|
|
qlist.addItem(getChannelDisplayName(channel))
|
|
|
|
def moveChannel(self,list,channel,index):
|
|
channels = None
|
|
if list == "conversations":
|
|
channels = self.conversations
|
|
|
|
listMove(channels,channels.index(channel),index)
|
|
self.repopulateChannels(list)
|
|
|
|
def sortChannels(self,list):
|
|
channels = None
|
|
if list == "conversations":
|
|
channels = self.conversations
|
|
|
|
newChannels = []
|
|
|
|
while True:
|
|
index = 0
|
|
length = len(channels)
|
|
largestTime = None
|
|
largestElement = None
|
|
while index < length:
|
|
channel = channels[index]
|
|
time = getLastMessageTime(channel)
|
|
if not time: index += 1; continue
|
|
if not largestTime:
|
|
largestTime = time
|
|
largestElement = index
|
|
index += 1
|
|
continue
|
|
|
|
if time < largestTime:
|
|
largestTime = time
|
|
largestElement = index
|
|
index += 1
|
|
if largestElement == None: break
|
|
newChannels.append(channels.pop(largestElement))
|
|
|
|
newChannels.reverse()
|
|
newChannels.extend(channels)
|
|
channels.clear()
|
|
channels.extend(newChannels)
|
|
self.repopulateChannels(list)
|
|
|
|
def addChannel(self,list,channel):
|
|
channels = None
|
|
qlist = None
|
|
if list == "conversations":
|
|
channels = self.conversations
|
|
qlist = self.conversationList
|
|
|
|
if channel in channels: return
|
|
channels.append(channel)
|
|
qlist.addItem(getChannelDisplayName(channel))
|
|
|
|
def openChannel(self):
|
|
tab = self.tabs.currentWidget()
|
|
channel = None
|
|
if tab == self.conversationTab:
|
|
channel = self.conversations[self.conversationList.currentRow()]
|
|
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "channel":
|
|
if gui.channel == channel:
|
|
gui.window.setWindowState(gui.window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
|
|
gui.window.activateWindow()
|
|
guiLock.release()
|
|
return
|
|
openGuis.append(guiChannel(channel))
|
|
guiLock.release()
|
|
|
|
def eventFilter(_,self,source,event):
|
|
#print(source,event)
|
|
if event.type() == QEvent.KeyPress:
|
|
if event.key() in (Qt.Key_Return,Qt.Key_Enter):
|
|
if self.window.focusWidget() in (self.conversationList,):
|
|
self.openChannel()
|
|
return True
|
|
|
|
def changedTab(self,i):
|
|
self.resizeElements()
|
|
|
|
class guiChannel:
|
|
def __init__(self,channel):
|
|
self.closed = False
|
|
self.ready = False
|
|
self.type = "channel"
|
|
self.title = getChannelDisplayName(channel)
|
|
self.width = 320
|
|
self.height = 350
|
|
self.window = dbfMainWindow()
|
|
global style
|
|
self.window.setStyleSheet(style)
|
|
self.window.setWindowTitle(getTitle(self.title))
|
|
self.window.resize(self.width,self.height)
|
|
|
|
self.channel = channel
|
|
self.read = (not channel.id in unreadChannels)
|
|
self.messages = []
|
|
self.pendingMessages = []
|
|
|
|
self.window.resizeEvent = self.resizeEvent
|
|
self.window.closeEvent = self.closeEvent
|
|
self.createElements()
|
|
|
|
class chatTextBox(QPlainTextEdit):
|
|
def __init__(self,parent = None,exSelf = None):
|
|
super().__init__(parent = parent)
|
|
self.exSelf = exSelf
|
|
self.installEventFilter(self)
|
|
|
|
def eventFilter(self,source,event):
|
|
if event.type() == QEvent.KeyPress:
|
|
if event.key() in (Qt.Key_Return,Qt.Key_Enter):
|
|
modifiers = QApplication.keyboardModifiers()
|
|
if (modifiers & Qt.ShiftModifier): return False
|
|
if self.exSelf.window.focusWidget() in (self.exSelf.textbox,):
|
|
self.exSelf.sendMessage()
|
|
return True
|
|
return False
|
|
|
|
def createElements(self):
|
|
self.menuBar = self.window.menuBar()
|
|
self.fileMenu = self.menuBar.addMenu("File")
|
|
self.messageLog = dbfTextBrowser(self.window)
|
|
self.messageLogScroll = self.messageLog.widget.verticalScrollBar()
|
|
self.textbox = self.chatTextBox(self.window,self)
|
|
|
|
self.buttonSend = QPushButton("Send",self.window)
|
|
self.buttonSend.clicked.connect(self.sendMessage)
|
|
|
|
self.resizeElements()
|
|
self.window.show()
|
|
|
|
def resizeElements(self):
|
|
mb = self.menuBar.height()
|
|
self.messageLog.widget.move(0,mb)
|
|
self.messageLog.widget.resize(self.width,self.height-mb-48-24-10)
|
|
self.textbox.move(0,mb+self.height-mb-48-24-10)
|
|
self.textbox.resize(self.width,48)
|
|
self.buttonSend.move(self.width-64-5,self.height-24-5)
|
|
self.buttonSend.resize(64,24)
|
|
|
|
def resizeEvent(self, event):
|
|
self.width = self.window.width()
|
|
self.height = self.window.height()
|
|
self.resizeElements()
|
|
QWidget.resizeEvent(self.window, event)
|
|
|
|
def closeEvent(self, event):
|
|
self.closed = True
|
|
QWidget.closeEvent(self.window, event)
|
|
|
|
def confirmEvent(self, event):
|
|
pass
|
|
|
|
def addMessage(self, message):
|
|
global config
|
|
while len(self.messages) >= int(config["performance"]["maxMessagesListed"]): del self.messages[0]
|
|
self.messages.append(message)
|
|
self.messageLog.set({})
|
|
for message in self.messages:
|
|
texts = [
|
|
{"text":"\n" + message.author.name+ ": ","style":config["textStyle"]["userHighlight"]},
|
|
{"text":message.content}
|
|
]
|
|
|
|
for attachment in message.attachments:
|
|
texts.append(
|
|
{"text":"\n> Attach: ","style":"color: orange"}
|
|
)
|
|
|
|
texts.append(
|
|
{"text":attachment.url}
|
|
)
|
|
self.messageLog.append(texts)
|
|
|
|
def sendMessage(self):
|
|
self.pendingMessages.append(self.textbox.toPlainText())
|
|
self.textbox.setPlainText("")
|
|
|
|
def setReady(self):
|
|
self.ready = True
|
|
|
|
def dtToString(dt):
|
|
return json.dumps([dt.year,dt.month,dt.day,dt.hour,dt.minute,dt.second,dt.microsecond])
|
|
|
|
def dtFromString(js):
|
|
ls = json.loads(js)
|
|
return datetime.datetime(*ls)
|
|
|
|
def getMessageTimePath(channel):
|
|
return p(loginPath,"messageTimes",str(channel.id))
|
|
|
|
msgTimeLock = threading.Lock()
|
|
def getLastMessageTime(channel):
|
|
msgTimeLock.acquire()
|
|
mtpath = getMessageTimePath(channel)
|
|
if not os.path.isfile(mtpath): msgTimeLock.release(); return None
|
|
fh = open(mtpath,"r")
|
|
st = fh.read()
|
|
fh.close()
|
|
msgTimeLock.release()
|
|
return dtFromString(st)
|
|
|
|
def setLastMessageTime(channel,time):
|
|
msgTimeLock.acquire()
|
|
mtpath = getMessageTimePath(channel)
|
|
fh = open(mtpath,"w")
|
|
fh.write(dtToString(time))
|
|
fh.close()
|
|
msgTimeLock.release()
|
|
|
|
def discordClient(token):
|
|
logging.basicConfig(level=logging.INFO)
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
|
|
global client
|
|
client = discord.Client()
|
|
global channelFilter
|
|
channelFilter = (discord.DMChannel,discord.GroupChannel)
|
|
global loginPath
|
|
|
|
async def fetchLostMessages(channel):
|
|
newTime = None
|
|
while True:
|
|
try:
|
|
async for msg in channel.history(limit=1):
|
|
newTime = msg.created_at
|
|
break
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
print("! could not fetch lost messages for " +str(channel.id)+ ", retrying...")
|
|
if not newTime: return False
|
|
oldTime = getLastMessageTime(channel)
|
|
|
|
if not oldTime:
|
|
setLastMessageTime(channel,newTime)
|
|
return False
|
|
|
|
if newTime == oldTime:
|
|
return False
|
|
|
|
fetchMsgs = False
|
|
guiLock.acquire()
|
|
if not channel.id in unreadChannels: unreadChannels.append(channel.id)
|
|
for gui in openGuis:
|
|
if gui.type == "channel":
|
|
if channel != gui.channel: continue
|
|
fetchMsgs = True
|
|
break
|
|
guiLock.release()
|
|
|
|
if fetchMsgs == True:
|
|
newMsgs = None
|
|
while True:
|
|
try:
|
|
newMsgs = []
|
|
async for message in channel.history(limit=100,before=newTime,after=oldTime):
|
|
newMsgs.append(message)
|
|
break
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
print("! could not fetch lost messages for " +str(channel.id)+ ", retrying...")
|
|
|
|
guiLock.acquire()
|
|
for message in newMsgs:
|
|
for gui in openGuis:
|
|
if gui.type == "channel":
|
|
if channel != gui.channel: continue
|
|
addGuiTask(gui,gui.addMessage,(message,),{})
|
|
guiLock.release()
|
|
|
|
setLastMessageTime(channel,newTime)
|
|
if not channel.id in unreadChannels:
|
|
unreadChannels.append(channel.id)
|
|
return True
|
|
|
|
async def fetchAllLostMessages():
|
|
print("fetching all lost messages...")
|
|
newMessages = False
|
|
for channel in client.private_channels:
|
|
if not type(channel) in channelFilter: continue
|
|
r = await fetchLostMessages(channel)
|
|
if newMessages == False and r == True: newMessages = True
|
|
|
|
if newMessages == True:
|
|
guiLock.acquire()
|
|
addGuiTask(None,playNotificationSound,(),{})
|
|
guiLock.release()
|
|
|
|
print("done fetching.")
|
|
|
|
@client.event
|
|
async def on_ready():
|
|
print("on_ready")
|
|
global online
|
|
global channelFilter
|
|
global ready
|
|
if ready == False:
|
|
condition = threading.Condition()
|
|
condition.acquire()
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "loginProgress":
|
|
addGuiTask(gui,gui.window.close,(),{})
|
|
addGuiTask("open",guiChannels,(),{},condition)
|
|
|
|
guiLock.release()
|
|
condition.wait()
|
|
condition.release()
|
|
|
|
condition.acquire()
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "channels":
|
|
for channel in client.private_channels:
|
|
addGuiTask(gui,gui.addChannel,("conversations",channel,),{})
|
|
|
|
addGuiTask(gui,gui.sortChannels,("conversations",),{},condition)
|
|
guiLock.release()
|
|
condition.wait()
|
|
condition.release()
|
|
|
|
guiLoop.start()
|
|
ready = True
|
|
|
|
if config["messageTracking"]["trackMessages"] == "true" and config["messageTracking"]["recheckMessages"] == "true":
|
|
await fetchAllLostMessages()
|
|
|
|
if online == False:
|
|
online = True
|
|
|
|
@client.event
|
|
async def on_resumed():
|
|
print("on_resumed")
|
|
global online
|
|
global channelFilter
|
|
|
|
if config["messageTracking"]["trackMessages"] == "true" and config["messageTracking"]["recheckMessages"] == "true":
|
|
await fetchAllLostMessages()
|
|
|
|
if online == False:
|
|
online = True
|
|
|
|
@client.event
|
|
async def on_message(message):
|
|
messages = [message]
|
|
if type(message.channel) in channelFilter and config["messageTracking"]["trackMessages"] == "true":
|
|
oldTime = getLastMessageTime(message.channel)
|
|
|
|
if oldTime:
|
|
if config["messageTracking"]["paranoidMessages"] == "true":
|
|
newMsgs = None
|
|
while True:
|
|
try:
|
|
newMsgs = []
|
|
async for msg in message.channel.history(limit=100,before=message.created_at,after=oldTime):
|
|
newMsgs.append(msg)
|
|
break
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
print("! could not fetch lost messages for " +str(message.channel.id)+ ", retrying...")
|
|
|
|
if len(newMsgs) > 0:
|
|
print("paranoia found missed messages")
|
|
for msg in newMsgs:
|
|
messages.append(msg)
|
|
|
|
setLastMessageTime(message.channel,message.created_at)
|
|
|
|
guiLock.acquire()
|
|
|
|
if type(message.channel) in channelFilter:
|
|
addGuiTask(None,playNotificationSound,(),{})
|
|
if not message.channel.id in unreadChannels: unreadChannels.append(message.channel.id)
|
|
|
|
for gui in openGuis:
|
|
if gui.type == "channels":
|
|
if message.channel in gui.conversations:
|
|
addGuiTask(gui,gui.moveChannel,("conversations",message.channel,0),{})
|
|
|
|
if gui.type == "channel":
|
|
if message.channel != gui.channel: continue
|
|
for msg in messages:
|
|
addGuiTask(gui,gui.addMessage,(msg,),{})
|
|
guiLock.release()
|
|
|
|
@tasks.loop(seconds=float(config["performance"]["uiTickTime"]))
|
|
async def guiLoop():
|
|
guis = []
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
guis.append(gui)
|
|
if gui.type == "channel":
|
|
while len(gui.pendingMessages) > 0:
|
|
msg = gui.pendingMessages.pop(0)
|
|
try:
|
|
await gui.channel.send(msg)
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
print("! failed sending message: " +msg)
|
|
guiLock.release()
|
|
for gui in guis:
|
|
if gui.type == "channel":
|
|
if gui.ready == False:
|
|
channel = gui.channel
|
|
messages = []
|
|
try:
|
|
messages = await channel.history(limit=int(config["performance"]["historyFetch"])).flatten()
|
|
except Exception:
|
|
print(traceback.format_exc())
|
|
print("! failed fetching messages for channel " +str(channel.id))
|
|
messages = reversed(messages)
|
|
guiLock.acquire()
|
|
message = None
|
|
for message in messages:
|
|
addGuiTask(gui,gui.addMessage,(message,),{})
|
|
if message != None and config["messageTracking"]["trackMessages"] == "true": setLastMessageTime(channel,message.created_at)
|
|
condition = threading.Condition()
|
|
condition.acquire()
|
|
addGuiTask(gui,gui.setReady,(),{},condition)
|
|
guiLock.release()
|
|
condition.wait()
|
|
condition.release()
|
|
|
|
loop.create_task(client.start(token,bot = False))
|
|
loop.run_forever()
|
|
|
|
def init(nlogin):
|
|
condition = threading.Condition()
|
|
condition.acquire()
|
|
guiLock.acquire()
|
|
addGuiTask("open",guiLoginProgress,(),{},condition)
|
|
for gui in openGuis:
|
|
if gui.type == "loginChooser":
|
|
addGuiTask(gui,gui.window.close,(),{})
|
|
guiLock.release()
|
|
condition.wait()
|
|
condition.release()
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "loginProgress":
|
|
addGuiTask(gui,gui.log.append,([[
|
|
{"text":"fetching token..."}
|
|
]]),{})
|
|
guiLock.release()
|
|
|
|
global login
|
|
login = nlogin
|
|
global loginPath
|
|
loginPath = p(pathLogins,login)
|
|
token = ""
|
|
with open(p(loginPath,"token")) as tokenFile:
|
|
token = tokenFile.read()
|
|
|
|
if os.path.isfile(p(loginPath,"unreadChannels.json")):
|
|
global unreadChannels
|
|
fh = open(p(loginPath,"unreadChannels.json"))
|
|
unreadChannels = json.loads(fh.read())
|
|
fh.close()
|
|
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "loginProgress":
|
|
addGuiTask(gui,gui.log.append,([[
|
|
{"text":"\nloading discord.py..."}
|
|
]]),{})
|
|
guiLock.release()
|
|
global discord
|
|
import discord
|
|
global tasks
|
|
from discord.ext import tasks
|
|
guiLock.acquire()
|
|
for gui in openGuis:
|
|
if gui.type == "loginProgress":
|
|
addGuiTask(gui,gui.log.append,([[
|
|
{"text":"\nlogging in..."}
|
|
]]),{})
|
|
guiLock.release()
|
|
threading.Thread(target=discordClient,args=(token,)).start()
|
|
|
|
def loadStyle(config):
|
|
global fonts
|
|
fonts = {}
|
|
fonts["default"] = QFont()
|
|
fonts["monospace"] = QFont("monospace")
|
|
fonts["monospace"].setStyleHint(QFont.TypeWriter)
|
|
|
|
if config["font"]["defaultType"] != "default":
|
|
fonts["default"] = QFont(config["font"]["defaultType"])
|
|
|
|
if config["font"]["defaultSize"] != "default":
|
|
fonts["default"].setPointSize(int(config["font"]["defaultSize"]))
|
|
|
|
if config["font"]["monoType"] != "default":
|
|
fonts["monospace"] = QFont(config["font"]["monoType"])
|
|
|
|
if config["font"]["monoSize"] != "default":
|
|
fonts["monospace"].setPointSize(int(config["font"]["monoSize"]))
|
|
|
|
app.app.setFont(fonts["default"])
|
|
|
|
global style
|
|
style = ""
|
|
|
|
if config["style"]["baseStyle"] != "default":
|
|
app.app.setStyle(config["style"]["baseStyle"])
|
|
|
|
if config["style"]["backgroundColor"] != "default":
|
|
style += "background:" +config["style"]["backgroundColor"]+ ";"
|
|
|
|
if config["style"]["fontColor"] != "default":
|
|
style += "color:" +config["style"]["fontColor"]+ ";"
|
|
|
|
def main():
|
|
pyqtRemoveInputHook()
|
|
|
|
global config
|
|
config = configparser.ConfigParser()
|
|
config.read(p(sp,"default.ini"))
|
|
|
|
global app
|
|
app = dbfApp()
|
|
loadStyle(config)
|
|
|
|
global notificationSound
|
|
if config["sound"]["notification"].lower() != "none":
|
|
notificationSound = QSound(config["sound"]["notification"])
|
|
else:
|
|
notificationSound = None
|
|
|
|
loginGui = guiLoginChooser()
|
|
openGuis.append(loginGui)
|
|
sys.exit(app.app.exec_())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|