2020-11-17 15:59:42 +00:00
#!/usr/bin/env python3
import sys
oldexcepthook = sys . excepthook
def newexcepthook ( type , value , traceback ) :
oldexcepthook ( type , value , traceback )
2020-11-24 00:40:39 +00:00
input ( " Press ENTER to quit. " ) # - this causes issues with Qt's input hook
2020-11-17 15:59:42 +00:00
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 )
2020-11-24 23:13:16 +00:00
os . chdir ( sp )
2020-11-17 15:59:42 +00:00
# script start
2020-11-24 00:40:39 +00:00
import qtpy
from qtpy . QtGui import *
from qtpy . QtWidgets import *
from qtpy . QtCore import *
2020-11-24 23:13:16 +00:00
from qtpy . QtMultimedia import QSound
2020-11-24 00:40:39 +00:00
import urllib . parse
import html
2020-11-17 15:59:42 +00:00
import threading
2020-11-24 00:40:39 +00:00
import asyncio
2020-11-17 15:59:42 +00:00
import logging
import configparser
2020-11-24 14:41:05 +00:00
import datetime
2020-11-24 15:45:29 +00:00
import json
2020-11-26 02:55:42 +00:00
import traceback
2020-11-17 15:59:42 +00:00
distro = " Discord but fast "
2020-11-26 04:20:25 +00:00
version = ( 0 , 2 , 0 )
2020-11-17 15:59:42 +00:00
versionString = " . " . join ( map ( str , version ) )
pathLogins = p ( sp , " logins " )
2020-11-24 00:40:39 +00:00
guiLock = threading . Condition ( )
openGuis = [ ]
2020-11-20 13:30:01 +00:00
guiTasks = [ ]
2020-11-24 00:40:39 +00:00
ready = False
online = True
2020-11-25 02:29:08 +00:00
unreadChannels = [ ]
oldUnreadChannels = [ ]
2020-11-20 13:30:01 +00:00
2020-11-24 00:40:39 +00:00
def doNothing ( ) :
pass
2020-11-25 01:08:41 +00:00
def listMove ( list , ind , newInd ) :
if newInd > ind : newInd = newInd - 1
list . insert ( newInd , list . pop ( ind ) )
2020-11-24 00:40:39 +00:00
def addGuiTask ( gui , func , args , kwargs , condition = None ) :
guiTasks . append ( ( gui , func , args , kwargs , condition ) )
2020-11-20 13:30:01 +00:00
2020-11-17 15:59:42 +00:00
def getTitle ( text = False ) :
title = " "
if text == False :
title = distro + " " + versionString
else :
title = text + " - " + distro + " " + versionString
2020-11-24 00:40:39 +00:00
return title
2020-11-17 15:59:42 +00:00
def getChannelDisplayName ( channel ) :
2020-11-25 02:29:08 +00:00
channelName = " Unknown "
2020-11-17 15:59:42 +00:00
if type ( channel ) == discord . DMChannel :
2020-11-25 02:29:08 +00:00
channelName = channel . recipient . name + " # " + channel . recipient . discriminator
2020-11-17 15:59:42 +00:00
elif type ( channel ) == discord . GroupChannel :
if channel . name :
2020-11-25 02:29:08 +00:00
channelName = " Group: " + channel . name
else :
channelName = " Group: [No name] "
2020-11-26 02:19:25 +00:00
if channel . id in unreadChannels :
2020-11-25 02:29:08 +00:00
channelName = " * " + channelName
return channelName
2020-11-17 15:59:42 +00:00
2020-11-24 23:13:16 +00:00
def playNotificationSound ( ) :
if notificationSound : notificationSound . play ( )
2020-11-24 00:40:39 +00:00
class dbfApp :
def __init__ ( self ) :
self . app = QApplication ( sys . argv )
self . uiTimer = QTimer ( self . app )
2020-11-25 02:29:08 +00:00
self . uiTimer . setInterval ( round ( float ( config [ " performance " ] [ " uiTickTime " ] ) * 1000 ) )
2020-11-24 00:40:39 +00:00
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
2020-11-25 02:29:08 +00:00
2020-11-24 00:40:39 +00:00
index + = 1
2020-11-25 02:29:08 +00:00
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 " :
2020-11-26 02:19:25 +00:00
if activeWindow . channel . id in unreadChannels :
2020-11-25 02:29:08 +00:00
if activeWindow . messageLog . scrollbar . value ( ) == activeWindow . messageLog . scrollbar . maximum ( ) :
2020-11-26 02:19:25 +00:00
del unreadChannels [ unreadChannels . index ( activeWindow . channel . id ) ]
2020-11-25 02:29:08 +00:00
index = 0
while index < length :
gui = openGuis [ index ]
if gui . type == " channel " :
2020-11-26 02:19:25 +00:00
if gui . channel . id in unreadChannels :
2020-11-25 02:29:08 +00:00
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
2020-11-26 14:25:31 +00:00
unreadChannelsLength = len ( unreadChannels )
2020-11-25 02:29:08 +00:00
if oldUnreadChannels != unreadChannels :
for gui in openGuis :
if gui . type == " channels " :
2020-11-26 14:25:31 +00:00
if unreadChannelsLength > 0 :
gui . title = " * Channels "
gui . window . setWindowTitle ( getTitle ( gui . title ) )
else :
gui . title = " Channels "
gui . window . setWindowTitle ( getTitle ( gui . title ) )
2020-11-25 02:29:08 +00:00
gui . repopulateChannels ( " conversations " )
oldUnreadChannels = unreadChannels . copy ( )
2020-11-26 02:19:25 +00:00
fh = open ( p ( loginPath , " unreadChannels.json " ) , " w " )
fh . write ( json . dumps ( unreadChannels ) )
fh . close ( )
2020-11-25 02:29:08 +00:00
2020-11-24 00:40:39 +00:00
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
2020-11-24 16:57:22 +00:00
#print(str(text))
2020-11-24 00:40:39 +00:00
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 ] )
2020-11-26 02:05:25 +00:00
2020-11-17 15:59:42 +00:00
class guiLoginChooser :
def __init__ ( self ) :
2020-11-24 00:40:39 +00:00
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
2020-11-17 15:59:42 +00:00
self . createElements ( )
def createElements ( self ) :
2020-11-24 00:40:39 +00:00
self . label = QLabel ( " Choose an account: " , self . window )
self . label . setAlignment ( Qt . AlignCenter )
self . dropdown = QComboBox ( self . window )
self . buttonNew = QPushButton ( " New " , self . window )
2020-11-26 02:05:25 +00:00
self . buttonNew . clicked . connect ( self . newLogin )
2020-11-24 00:40:39 +00:00
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
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-26 02:05:25 +00:00
self . window . show ( )
2020-11-17 15:59:42 +00:00
def resizeElements ( self ) :
2020-11-24 00:40:39 +00:00
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 ( )
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-24 00:40:39 +00:00
QWidget . resizeEvent ( self . window , event )
2020-11-17 15:59:42 +00:00
2020-11-26 02:05:25 +00:00
def newLogin ( self , event ) :
openGuis . append ( guiNewLogin ( ) )
self . window . close ( )
2020-11-24 00:40:39 +00:00
def login ( self , event ) :
threading . Thread ( target = init , args = ( self . dropdown . currentText ( ) , ) ) . start ( )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def closeEvent ( self , event ) :
self . closed = True
2020-11-17 15:59:42 +00:00
2020-11-26 02:05:25 +00:00
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
2020-11-17 15:59:42 +00:00
class guiLoginProgress :
def __init__ ( self ) :
2020-11-20 13:30:01 +00:00
self . closed = False
2020-11-24 00:40:39 +00:00
self . type = " loginProgress "
2020-11-17 15:59:42 +00:00
self . title = " Logging in... "
self . width = 320
self . height = 240
2020-11-24 00:40:39 +00:00
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
2020-11-17 15:59:42 +00:00
self . createElements ( )
def createElements ( self ) :
2020-11-24 00:40:39 +00:00
self . log = dbfTextBrowser ( self . window )
self . log . widget . setFont ( fonts [ " monospace " ] )
self . logScroll = self . log . widget . verticalScrollBar ( )
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-24 00:40:39 +00:00
self . window . show ( )
2020-11-17 15:59:42 +00:00
def resizeElements ( self ) :
2020-11-24 00:40:39 +00:00
self . log . widget . move ( 0 , 0 )
self . log . widget . resize ( self . width , self . height )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def resizeEvent ( self , event ) :
self . width = self . window . width ( )
self . height = self . window . height ( )
self . resizeElements ( )
QWidget . resizeEvent ( self . window , event )
2020-11-20 13:30:01 +00:00
2020-11-24 00:40:39 +00:00
def closeEvent ( self , event ) :
2020-11-20 13:30:01 +00:00
self . closed = True
2020-11-24 00:40:39 +00:00
QWidget . closeEvent ( self . window , event )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
class guiChannels :
2020-11-17 15:59:42 +00:00
def __init__ ( self ) :
self . closed = False
2020-11-24 00:40:39 +00:00
self . type = " channels "
self . title = " Channels "
2020-11-26 14:25:31 +00:00
global unreadChannels
if len ( unreadChannels ) > 0 : self . title = " * " + self . title
2020-11-17 15:59:42 +00:00
self . width = 200
self . height = 350
2020-11-24 00:40:39 +00:00
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
2020-11-17 15:59:42 +00:00
self . createElements ( )
def createElements ( self ) :
2020-11-24 00:40:39 +00:00
self . menuBar = self . window . menuBar ( )
self . fileMenu = self . menuBar . addMenu ( " File " )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . conversations = [ ]
self . friends = [ ]
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . picture = QLabel ( self . window )
2020-11-24 16:56:06 +00:00
self . picturePix = QPixmap ( " assets/images/windows.png " ) . scaled ( 32 , 32 , Qt . KeepAspectRatio )
2020-11-24 00:40:39 +00:00
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 " )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . tabs = QTabWidget ( self . window )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . conversationTab = QWidget ( self . window )
self . tabs . addTab ( self . conversationTab , " Conversations " )
self . conversationList = QListWidget ( self . conversationTab )
self . conversationList . itemDoubleClicked . connect ( self . openChannel )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . friendTab = QWidget ( self . window )
self . tabs . addTab ( self . friendTab , " Friends " )
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-24 00:40:39 +00:00
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 )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
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 )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def closeEvent ( self , event ) :
2020-11-17 15:59:42 +00:00
self . closed = True
2020-11-24 00:40:39 +00:00
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 ( )
2020-11-25 01:08:41 +00:00
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 ) )
2020-11-24 00:40:39 +00:00
def moveChannel ( self , list , channel , index ) :
2020-11-25 01:08:41 +00:00
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 )
2020-11-24 00:40:39 +00:00
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 ( )
2020-11-25 03:30:18 +00:00
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
2020-11-24 00:40:39 +00:00
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 ( )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
class guiChannel :
2020-11-17 15:59:42 +00:00
def __init__ ( self , channel ) :
self . closed = False
2020-11-24 00:40:39 +00:00
self . ready = False
self . type = " channel "
self . title = getChannelDisplayName ( channel )
self . width = 320
2020-11-25 03:29:33 +00:00
self . height = 350
2020-11-25 12:07:46 +00:00
self . window = dbfMainWindow ( )
2020-11-24 00:40:39 +00:00
global style
self . window . setStyleSheet ( style )
self . window . setWindowTitle ( getTitle ( self . title ) )
self . window . resize ( self . width , self . height )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . channel = channel
2020-11-26 02:19:25 +00:00
self . read = ( not channel . id in unreadChannels )
2020-11-17 15:59:42 +00:00
self . messages = [ ]
2020-11-24 00:40:39 +00:00
self . pendingMessages = [ ]
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . window . resizeEvent = self . resizeEvent
self . window . closeEvent = self . closeEvent
2020-11-17 15:59:42 +00:00
self . createElements ( )
2020-11-27 02:06:33 +00:00
class chatTextBox ( QPlainTextEdit ) :
2020-11-25 12:07:46 +00:00
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
2020-11-17 15:59:42 +00:00
def createElements ( self ) :
2020-11-24 00:40:39 +00:00
self . menuBar = self . window . menuBar ( )
self . fileMenu = self . menuBar . addMenu ( " File " )
self . messageLog = dbfTextBrowser ( self . window )
self . messageLogScroll = self . messageLog . widget . verticalScrollBar ( )
2020-11-25 12:07:46 +00:00
self . textbox = self . chatTextBox ( self . window , self )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
self . buttonSend = QPushButton ( " Send " , self . window )
self . buttonSend . clicked . connect ( self . sendMessage )
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-24 00:40:39 +00:00
self . window . show ( )
2020-11-17 15:59:42 +00:00
def resizeElements ( self ) :
2020-11-24 00:40:39 +00:00
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 ( )
2020-11-17 15:59:42 +00:00
self . resizeElements ( )
2020-11-24 00:40:39 +00:00
QWidget . resizeEvent ( self . window , event )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def closeEvent ( self , event ) :
2020-11-17 15:59:42 +00:00
self . closed = True
2020-11-24 00:40:39 +00:00
QWidget . closeEvent ( self . window , event )
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def confirmEvent ( self , event ) :
pass
2020-11-17 15:59:42 +00:00
2020-11-24 00:40:39 +00:00
def addMessage ( self , message ) :
global config
2020-11-25 19:56:03 +00:00
while len ( self . messages ) > = int ( config [ " performance " ] [ " maxMessagesListed " ] ) : del self . messages [ 0 ]
2020-11-24 00:40:39 +00:00
self . messages . append ( message )
self . messageLog . set ( { } )
2020-11-17 15:59:42 +00:00
for message in self . messages :
2020-11-25 12:43:56 +00:00
texts = [
2020-11-24 00:40:39 +00:00
{ " text " : " \n " + message . author . name + " : " , " style " : config [ " textStyle " ] [ " userHighlight " ] } ,
{ " text " : message . content }
2020-11-25 12:43:56 +00:00
]
for attachment in message . attachments :
texts . append (
{ " text " : " \n > Attach: " , " style " : " color: orange " }
)
texts . append (
{ " text " : attachment . url }
)
self . messageLog . append ( texts )
2020-11-24 00:40:39 +00:00
def sendMessage ( self ) :
self . pendingMessages . append ( self . textbox . toPlainText ( ) )
self . textbox . setPlainText ( " " )
def setReady ( self ) :
self . ready = True
2020-11-17 15:59:42 +00:00
2020-11-24 15:45:29 +00:00
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 )
2020-11-24 14:41:05 +00:00
def getMessageTimePath ( channel ) :
return p ( loginPath , " messageTimes " , str ( channel . id ) )
2020-11-26 02:55:42 +00:00
msgTimeLock = threading . Lock ( )
2020-11-24 14:41:05 +00:00
def getLastMessageTime ( channel ) :
2020-11-26 02:55:42 +00:00
msgTimeLock . acquire ( )
2020-11-24 14:41:05 +00:00
mtpath = getMessageTimePath ( channel )
2020-11-26 02:55:42 +00:00
if not os . path . isfile ( mtpath ) : msgTimeLock . release ( ) ; return None
fh = open ( mtpath , " r " )
st = fh . read ( )
fh . close ( )
msgTimeLock . release ( )
return dtFromString ( st )
2020-11-24 14:41:05 +00:00
def setLastMessageTime ( channel , time ) :
2020-11-25 19:56:38 +00:00
msgTimeLock . acquire ( )
2020-11-24 14:41:05 +00:00
mtpath = getMessageTimePath ( channel )
fh = open ( mtpath , " w " )
2020-11-24 15:45:29 +00:00
fh . write ( dtToString ( time ) )
2020-11-24 14:41:05 +00:00
fh . close ( )
2020-11-25 19:56:38 +00:00
msgTimeLock . release ( )
2020-11-24 14:41:05 +00:00
2020-11-20 13:30:01 +00:00
def discordClient ( token ) :
2020-11-17 15:59:42 +00:00
logging . basicConfig ( level = logging . INFO )
2020-11-20 13:30:01 +00:00
loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( loop )
2020-11-17 15:59:42 +00:00
global client
client = discord . Client ( )
2020-11-24 14:41:05 +00:00
global channelFilter
channelFilter = ( discord . DMChannel , discord . GroupChannel )
global loginPath
2020-11-17 15:59:42 +00:00
2020-11-24 16:57:22 +00:00
async def fetchLostMessages ( channel ) :
newTime = None
2020-11-26 02:55:42 +00:00
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... " )
2020-11-24 23:13:16 +00:00
if not newTime : return False
2020-11-24 16:57:22 +00:00
oldTime = getLastMessageTime ( channel )
if not oldTime :
setLastMessageTime ( channel , newTime )
2020-11-24 23:13:16 +00:00
return False
2020-11-24 16:57:22 +00:00
2020-11-25 02:29:08 +00:00
if newTime == oldTime :
return False
2020-11-24 23:13:16 +00:00
fetchMsgs = False
guiLock . acquire ( )
2020-11-26 02:19:25 +00:00
if not channel . id in unreadChannels : unreadChannels . append ( channel . id )
2020-11-24 23:13:16 +00:00
for gui in openGuis :
if gui . type == " channel " :
if channel != gui . channel : continue
fetchMsgs = True
break
guiLock . release ( )
if fetchMsgs == True :
2020-11-26 02:55:42 +00:00
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 :
2020-11-24 23:13:16 +00:00
for gui in openGuis :
if gui . type == " channel " :
if channel != gui . channel : continue
addGuiTask ( gui , gui . addMessage , ( message , ) , { } )
2020-11-26 02:55:42 +00:00
guiLock . release ( )
2020-11-24 16:57:22 +00:00
setLastMessageTime ( channel , newTime )
2020-11-27 15:07:07 +00:00
if not channel . id in unreadChannels :
unreadChannels . append ( channel . id )
2020-11-24 23:13:16 +00:00
return True
2020-11-24 16:57:22 +00:00
async def fetchAllLostMessages ( ) :
print ( " fetching all lost messages... " )
2020-11-24 23:13:16 +00:00
newMessages = False
2020-11-24 16:57:22 +00:00
for channel in client . private_channels :
if not type ( channel ) in channelFilter : continue
2020-11-24 23:13:16 +00:00
r = await fetchLostMessages ( channel )
if newMessages == False and r == True : newMessages = True
if newMessages == True :
guiLock . acquire ( )
addGuiTask ( None , playNotificationSound , ( ) , { } )
guiLock . release ( )
2020-11-24 16:57:22 +00:00
print ( " done fetching. " )
2020-11-17 15:59:42 +00:00
@client.event
async def on_ready ( ) :
2020-11-24 14:41:05 +00:00
print ( " on_ready " )
global online
global channelFilter
2020-11-24 00:40:39 +00:00
global ready
if ready == False :
condition = threading . Condition ( )
condition . acquire ( )
2020-11-20 13:30:01 +00:00
guiLock . acquire ( )
for gui in openGuis :
2020-11-24 00:40:39 +00:00
if gui . type == " loginProgress " :
addGuiTask ( gui , gui . window . close , ( ) , { } )
addGuiTask ( " open " , guiChannels , ( ) , { } , condition )
2020-11-20 13:30:01 +00:00
guiLock . release ( )
2020-11-24 00:40:39 +00:00
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 , ) , { } )
2020-11-25 01:08:41 +00:00
addGuiTask ( gui , gui . sortChannels , ( " conversations " , ) , { } , condition )
2020-11-24 00:40:39 +00:00
guiLock . release ( )
condition . wait ( )
condition . release ( )
2020-11-17 15:59:42 +00:00
guiLoop . start ( )
2020-11-24 00:40:39 +00:00
ready = True
2020-11-24 14:41:05 +00:00
if config [ " messageTracking " ] [ " trackMessages " ] == " true " and config [ " messageTracking " ] [ " recheckMessages " ] == " true " :
2020-11-24 16:57:22 +00:00
await fetchAllLostMessages ( )
2020-11-24 14:41:05 +00:00
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 " :
2020-11-24 16:57:22 +00:00
await fetchAllLostMessages ( )
2020-11-24 14:41:05 +00:00
if online == False :
online = True
2020-11-17 15:59:42 +00:00
@client.event
async def on_message ( message ) :
2020-11-24 14:41:05 +00:00
messages = [ message ]
if type ( message . channel ) in channelFilter and config [ " messageTracking " ] [ " trackMessages " ] == " true " :
oldTime = getLastMessageTime ( message . channel )
if oldTime :
if config [ " messageTracking " ] [ " paranoidMessages " ] == " true " :
2020-11-26 02:55:42 +00:00
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 )
2020-11-24 14:41:05 +00:00
setLastMessageTime ( message . channel , message . created_at )
2020-11-20 13:30:01 +00:00
guiLock . acquire ( )
2020-11-24 23:13:16 +00:00
if type ( message . channel ) in channelFilter :
addGuiTask ( None , playNotificationSound , ( ) , { } )
2020-11-26 02:19:25 +00:00
if not message . channel . id in unreadChannels : unreadChannels . append ( message . channel . id )
2020-11-24 23:13:16 +00:00
2020-11-17 15:59:42 +00:00
for gui in openGuis :
2020-11-25 01:08:41 +00:00
if gui . type == " channels " :
if message . channel in gui . conversations :
addGuiTask ( gui , gui . moveChannel , ( " conversations " , message . channel , 0 ) , { } )
2020-11-24 00:40:39 +00:00
if gui . type == " channel " :
2020-11-17 15:59:42 +00:00
if message . channel != gui . channel : continue
2020-11-24 14:41:05 +00:00
for msg in messages :
addGuiTask ( gui , gui . addMessage , ( msg , ) , { } )
2020-11-20 13:30:01 +00:00
guiLock . release ( )
2020-11-17 15:59:42 +00:00
2020-11-25 02:29:08 +00:00
@tasks.loop ( seconds = float ( config [ " performance " ] [ " uiTickTime " ] ) )
2020-11-17 15:59:42 +00:00
async def guiLoop ( ) :
2020-11-21 00:48:43 +00:00
guis = [ ]
2020-11-21 00:54:16 +00:00
guiLock . acquire ( )
2020-11-17 15:59:42 +00:00
for gui in openGuis :
2020-11-24 00:40:39 +00:00
guis . append ( gui )
if gui . type == " channel " :
while len ( gui . pendingMessages ) > 0 :
2020-11-26 02:55:42 +00:00
msg = gui . pendingMessages . pop ( 0 )
try :
await gui . channel . send ( msg )
except Exception :
print ( traceback . format_exc ( ) )
print ( " ! failed sending message: " + msg )
2020-11-21 00:54:16 +00:00
guiLock . release ( )
2020-11-24 00:40:39 +00:00
for gui in guis :
if gui . type == " channel " :
if gui . ready == False :
channel = gui . channel
2020-11-26 02:55:42 +00:00
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 ) )
2020-11-24 00:40:39 +00:00
messages = reversed ( messages )
guiLock . acquire ( )
2020-11-25 19:59:00 +00:00
message = None
2020-11-24 00:40:39 +00:00
for message in messages :
addGuiTask ( gui , gui . addMessage , ( message , ) , { } )
2020-11-25 19:59:00 +00:00
if message != None and config [ " messageTracking " ] [ " trackMessages " ] == " true " : setLastMessageTime ( channel , message . created_at )
2020-11-24 00:40:39 +00:00
condition = threading . Condition ( )
condition . acquire ( )
addGuiTask ( gui , gui . setReady , ( ) , { } , condition )
guiLock . release ( )
condition . wait ( )
condition . release ( )
2020-11-17 15:59:42 +00:00
2020-11-21 13:28:46 +00:00
loop . create_task ( client . start ( token , bot = False ) )
loop . run_forever ( )
2020-11-24 00:40:39 +00:00
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
2020-11-24 14:41:05 +00:00
global loginPath
loginPath = p ( pathLogins , login )
2020-11-24 00:40:39 +00:00
token = " "
2020-11-24 14:41:05 +00:00
with open ( p ( loginPath , " token " ) ) as tokenFile :
2020-11-24 00:40:39 +00:00
token = tokenFile . read ( )
2020-11-26 02:19:25 +00:00
if os . path . isfile ( p ( loginPath , " unreadChannels.json " ) ) :
global unreadChannels
fh = open ( p ( loginPath , " unreadChannels.json " ) )
unreadChannels = json . loads ( fh . read ( ) )
fh . close ( )
2020-11-24 00:40:39 +00:00
guiLock . acquire ( )
for gui in openGuis :
if gui . type == " loginProgress " :
addGuiTask ( gui , gui . log . append , ( [ [
{ " text " : " \n loading 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 " : " \n logging 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 " ] + " ; "
2020-11-17 15:59:42 +00:00
2020-11-20 13:30:01 +00:00
def main ( ) :
2020-11-24 00:40:39 +00:00
pyqtRemoveInputHook ( )
2020-11-20 13:30:01 +00:00
global config
config = configparser . ConfigParser ( )
config . read ( p ( sp , " default.ini " ) )
2020-11-24 00:40:39 +00:00
global app
app = dbfApp ( )
loadStyle ( config )
2020-11-20 13:30:01 +00:00
2020-11-24 23:13:16 +00:00
global notificationSound
if config [ " sound " ] [ " notification " ] . lower ( ) != " none " :
notificationSound = QSound ( config [ " sound " ] [ " notification " ] )
else :
notificationSound = None
2020-11-24 00:40:39 +00:00
loginGui = guiLoginChooser ( )
openGuis . append ( loginGui )
sys . exit ( app . app . exec_ ( ) )
2020-11-20 13:30:01 +00:00
2020-11-17 15:59:42 +00:00
if __name__ == " __main__ " :
2020-11-21 13:28:46 +00:00
main ( )