commit 424cf77cedcdd6522422a9d9cb046119abcd663a Author: Fierelier Date: Tue Nov 17 16:59:42 2020 +0100 Hello world diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..749356c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logins/ diff --git a/default.ini b/default.ini new file mode 100644 index 0000000..be3a7a4 --- /dev/null +++ b/default.ini @@ -0,0 +1,20 @@ +[performance] +; How many messages should be retrieved upon opening a conversation? +historyFetch = 20 +; How many messages can be collectively displayed in a conversation? +maxMessagesListed = 100 + +[font] +; Font face. Only changes some elements. +type = Tahoma +; Font size in px. Only changes some elements, unsupported. +size = 8 + +[color] +; Color used to highlight user in chat log +userHighlight = #ff0000 +; Conversation/friend-list colors +userOnline = #00ff00 +userAway = #ffff00 +userBusy = #ff0000 +userOffline = #999999 \ No newline at end of file diff --git a/discord-but-fast.py b/discord-but-fast.py new file mode 100644 index 0000000..79dfe49 --- /dev/null +++ b/discord-but-fast.py @@ -0,0 +1,596 @@ +#!/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 tkinter +import tkinter.ttk +import threading +import logging +import configparser +distro = "Discord but fast" +version = (0,0,0) +versionString = ".".join(map(str,version)) +pathLogins = p(sp,"logins") +initialized = False +connected = False + +def getTitle(text = False): + title = "" + if text == False: + title = distro + " " + versionString + else: + title = text + " - " + distro + " " + versionString + if initialized == False: return title + if connected == True: return title + return "[Offline] " +title + +def fetchLogins(): + logins = [] + for root,dirs,files in os.walk(pathLogins): + for file in dirs: + ffile = p(root,file) + lfile = ffile.replace(pathLogins + os.sep,"") + logins.append(lfile) + return logins + +def filterText(text): + newtext = "" + for char in text: + if not ord(char) in range(65536): + char = "?" + newtext += char + return newtext + +def taggedText(textObj,text): + textObj.delete("1.0","end") + lineIndex = 1 + columnIndex = 0 + index = 0 + length = len(text) + while index < length: + tags = text[index] + part = text[index + 1] + newLineIndex = lineIndex + part.count("\n") + if newLineIndex != lineIndex: + newColumnIndex = len(part.rsplit("\n",1)) + else: + newColumnIndex = len(part) + textObj.insert(tkinter.END,part) + for tag in tags.split(";"): + textObj.tag_add(tag,str(lineIndex)+ "." +str(columnIndex),str(newLineIndex)+ "." +str(newColumnIndex)) + lineIndex = newLineIndex + index = index + 2 + +def getChannelDisplayName(channel): + if type(channel) == discord.DMChannel: + return filterText(channel.recipient.name) + "#" +channel.recipient.discriminator + elif type(channel) == discord.GroupChannel: + if channel.name: + return "Group: " +filterText(channel.name) + return "Group: [No name]" + +class guiLoginChooser: + def __init__(self): + self.window = tkinter.Tk() + self.title = "Choose login" + self.width = 320 + self.height = 82 + self.window.geometry(str(self.width) + "x" + str(self.height)) + self.window.minsize(220,82) + self.window.title(getTitle(self.title)) + self.createElements() + self.window.bind("", self.onResize) + self.window.protocol("WM_DELETE_WINDOW", self.onClose) + + def createElements(self): + self.label = tkinter.Label(self.window, text="Select a login:", font=(defaultFont,defaultFontSize)) + self.choices = fetchLogins() + self.login = "" + self.dropdownLoginChoice = tkinter.StringVar(self.window) + self.dropdownLogin = tkinter.OptionMenu(self.window, self.dropdownLoginChoice, *self.choices) + self.buttonNew = tkinter.Button(self.window, text="New", command=self.window.quit, font=(defaultFont,defaultFontSize)) + self.buttonLogin = tkinter.Button(self.window, text="Login", command=self.confirm, font=(defaultFont,defaultFontSize)) + self.resizeElements() + + def resizeElements(self): + self.label.place( + x = 5, + y = 5, + width = self.width - 10, + height = 12 + ) + + self.dropdownLogin.place( + x = 40, + y = 20, + width = self.width - 80, + height = 24 + ) + + self.buttonNew.place( + x = 5, + y = self.height - 24 - 5, + width = 64, + height = 24 + ) + + self.buttonLogin.place( + x = self.width - 64 - 5, + y = self.height - 24 - 5, + width = 64, + height = 24 + ) + + def onResize(self,event): + if event.widget != self.window: return + if event.width == self.width and event.height == self.height: return + self.width = event.width + self.height = event.height + self.resizeElements() + + def onClose(self): + self.window.destroy() + + def confirm(self): + if self.dropdownLoginChoice.get() == "": + import tkinter.messagebox as messagebox + messagebox.showinfo(title=getTitle("Info"), message='Please choose a login from the dropdown menu, or create one by pressing "New"!') + return + self.login = self.dropdownLoginChoice.get() + self.window.destroy() + +class guiLoginProgress: + def __init__(self): + self.window = tkinter.Tk() + self.title = "Logging in..." + self.width = 320 + self.height = 240 + self.window.geometry(str(self.width) + "x" + str(self.height)) + self.window.title(getTitle(self.title)) + self.createElements() + self.window.bind("", self.onResize) + self.window.protocol("WM_DELETE_WINDOW", self.onClose) + + def createElements(self): + self.log = tkinter.Text(self.window, font=(defaultFont,defaultFontSize)) + self.logScrollbar = tkinter.ttk.Scrollbar(self.window,command=self.log.yview) + self.log.config(state=tkinter.DISABLED) + self.log.bind("<1>", lambda event: self.log.focus_set()) + self.log["yscrollcommand"] = self.logScrollbar.set + self.resizeElements() + + def resizeElements(self): + self.log.place( + x = 0, + y = 0, + width = self.width - 20, + height = self.height + ) + + self.logScrollbar.place( + x = self.width - 20, + y = 0, + width = 20, + height = self.height + ) + + def addToLog(self,text): + self.log.config(state=tkinter.NORMAL) + self.log.insert(tkinter.END,text) + self.log.config(state=tkinter.DISABLED) + self.log.see(tkinter.END) + self.window.update() + + def onResize(self,event): + if event.widget != self.window: return + if event.width == self.width and event.height == self.height: return + self.width = event.width + self.height = event.height + self.resizeElements() + + def onClose(self): + self.window.destroy() + +class guiConversationList: + def __init__(self): + self.type = "conversationList" + self.closed = False + + self.window = tkinter.Tk() + self.title = "Conversations" + self.width = 200 + self.height = 350 + self.window.geometry(str(self.width) + "x" + str(self.height)) + self.window.title(getTitle(self.title)) + self.createElements() + self.window.bind("", self.onResize) + self.window.protocol("WM_DELETE_WINDOW", self.onClose) + + def createElements(self): + self.menuConv = tkinter.Menu(self.window,tearoff=0,font=(defaultFont,defaultFontSize)) + self.menuConv.add_command(label="New") + self.menuApp = tkinter.Menu(self.window,tearoff=0,font=(defaultFont,defaultFontSize)) + self.menuApp.add_command(label="New instance") + self.menuApp.add_command(label="Exit") + self.menubar = tkinter.Menu(self.window,font=(defaultFont,defaultFontSize)) + self.menubar.add_cascade(label="App", menu=self.menuApp) + self.menubar.add_cascade(label="Conversation", menu=self.menuConv) + self.window.config(menu=self.menubar) + + self.pfp = tkinter.Canvas(self.window,bg="#ffffff") + self.name = tkinter.Label(self.window,text=filterText(client.user.name + "#" +client.user.discriminator),anchor="w",font=(defaultFont,defaultFontSize,"bold")) + self.statusImg = tkinter.Canvas(self.window,bg="#00ff00") + self.status = tkinter.Label(self.window,text="A status",anchor="w",font=(defaultFont,defaultFontSize)) + + self.tablist = tkinter.ttk.Notebook(self.window) + self.tabConv = tkinter.ttk.Frame(self.tablist) + self.convs = [] + self.tabFriends = tkinter.ttk.Frame(self.tablist) + self.tablist.add(self.tabConv,text="Conversations") + self.tablist.add(self.tabFriends,text="Friends") + + self.listConv = tkinter.Listbox(self.tabConv,font=(defaultFont,defaultFontSize)) + self.listConvScrollbar = tkinter.ttk.Scrollbar(self.tabConv,command=self.listConv.yview) + self.listConv["yscrollcommand"] = self.listConvScrollbar.set + self.listConv.bind("",self.listConvSelect) + self.listConv.bind("",self.listConvSelect) + + self.resizeElements() + + def listConvSelect(self,evt): + index = int(evt.widget.curselection()[0]) + openGuis.append(guiConversation(self.convs[index])) + + def updateConvs(self,channels): + size = self.listConv.size() + self.listConv.delete(0,size - 1) + listIndex = 1 + + self.convs = [] + + for channel in channels: + self.listConv.insert(listIndex," " +getChannelDisplayName(channel)+ " ") + self.convs.append(channel) + listIndex = listIndex + 1 + + def resizeElements(self): + pfps = 50 + + self.pfp.place( + x = 5, + y = 5, + width = pfps, + height = pfps + ) + + self.name.place( + x = pfps + 10, + y = 10, + width = self.width - pfps - 10, + height = 16 + ) + + self.statusImg.place( + x = pfps + 10, + y = 10 + 16, + width = 16, + height = 16 + ) + + self.status.place( + x = pfps + 10 + 16 + 2, + y = 10 + 16, + width = self.width - pfps - 10 - 16 - 2, + height = 16 + ) + + self.tablist.place( + x = 0, + y = pfps + 10, + width = self.width, + height = self.height - pfps - 10 + ) + + self.listConv.place( + x = 0, + y = 0, + relwidth = 1.0, + width = -20, + relheight = 1.0 + ) + + self.listConvScrollbar.place( + x = -20, + y = 0, + relx = 1.0, + width = 20, + relheight = 1.0 + ) + + def onResize(self,event): + if event.widget != self.window: return + if event.width == self.width and event.height == self.height: return + self.width = event.width + self.height = event.height + self.resizeElements() + + def onClose(self): + self.closeGui() + + def closeGui(self): + self.closed = True + self.window.destroy() + +class guiConversation: + def __init__(self,channel): + self.type = "conversation" + self.channel = channel + self.closed = False + + self.readied = False + self.pendingMessages = [] + self.messages = [] + + self.window = tkinter.Tk() + self.title = getChannelDisplayName(channel) + self.width = 380 + self.height = 360 + self.window.geometry(str(self.width) + "x" + str(self.height)) + self.window.title(getTitle(self.title)) + self.createElements() + self.window.bind("", self.onResize) + self.window.protocol("WM_DELETE_WINDOW", self.onClose) + + def createElements(self): + self.menuConv = tkinter.Menu(self.window,tearoff=0,font=(defaultFont,defaultFontSize)) + self.menuConv.add_command(label="Add member") + self.menuConv.add_command(label="Remove member") + self.menuConv.add_command(label="Leave") + self.menuConv.add_separator() + self.menuConv.add_command(label="Save") + self.menuConv.add_separator() + self.menuConv.add_command(label="Close") + self.menubar = tkinter.Menu(self.window) + self.menubar.add_cascade(label="Conversation", menu=self.menuConv) + self.window.config(menu=self.menubar) + + self.messagelog = tkinter.Text(self.window,wrap=tkinter.WORD,font=(defaultFont,defaultFontSize)) + self.messagelog.insert(tkinter.END,"Fetching messages...") + self.messagelogScrollbar = tkinter.ttk.Scrollbar(self.window,command=self.messagelog.yview) + self.messagelog["yscrollcommand"] = self.messagelogScrollbar.set + self.messagelog.config(state=tkinter.DISABLED) + self.messagelog.bind("<1>", lambda event: self.messagelog.focus_set()) + self.messagelog.tag_config("user",foreground=config["color"]["userHighlight"],font=(defaultFont,defaultFontSize,"bold")) + + self.messagebox = tkinter.Text(self.window,wrap=tkinter.WORD,font=(defaultFont,defaultFontSize)) + self.messageboxScrollbar = tkinter.ttk.Scrollbar(self.window,command=self.messagebox.yview) + self.messagebox["yscrollcommand"] = self.messageboxScrollbar.set + self.messagebox.bind("",self.sendMessagebox) + + self.resizeElements() + + def resizeElements(self): + self.messagelog.place( + x = 0, + y = 0, + width = self.width - 20, + height = self.height - 48 + ) + + self.messagelogScrollbar.place( + x = self.width - 20, + y = 0, + width = 20, + height = self.height - 48 + ) + + self.messagebox.place( + x = 0, + y = self.height - 48, + width = self.width - 20, + height = 48 + ) + + self.messageboxScrollbar.place( + x = self.width - 20, + y = self.height - 48, + width = 20, + height = 48 + ) + + def onResize(self,event): + if event.widget != self.window: return + if event.width == self.width and event.height == self.height: return + self.width = event.width + self.height = event.height + self.resizeElements() + + def onClose(self): + self.closeGui() + + def closeGui(self): + self.closed = True + self.window.destroy() + + def addMessage(self,message): + if len(self.messages) > int(config["performance"]["maxMessagesListed"]): + del self.messages[0] + + self.messages.append(message) + + def fillMessageLog(self): + taggedTextList = [] + for message in self.messages: + if type(message) == discord.Message: + taggedTextList += [ + "","\n", + "user",filterText(message.author.name) + ":", + ""," " + filterText(message.content) + ] + + for attachment in message.attachments: + taggedTextList += [ + "","\n> Attach: " +attachment.url + ] + else: + taggedTextList += message + + self.messagelog.config(state=tkinter.NORMAL) + taggedText(self.messagelog,taggedTextList) + self.messagelog.config(state=tkinter.DISABLED) + self.messagelog.see("end") + + def sendMessagebox(self,evt=False): + messageText = self.messagebox.get("1.0","end") + self.pendingMessages.append(messageText) + self.messagebox.delete("1.0","end") + +def main(): + global config + config = configparser.ConfigParser() + config.read(p(sp,"default.ini")) + global defaultFont + global defaultFontSize + defaultFont = config["font"]["type"] + defaultFontSize = config["font"]["size"] + + loginChooser = guiLoginChooser() + loginChooser.window.mainloop() + global login + login = loginChooser.login + if login == "": return + token = False + + global iLoginProgress + iLoginProgress = guiLoginProgress() + iLoginProgress.addToLog("Fetching token...") + + with open(p(pathLogins,login,"token"),"r") as fileh: + token = fileh.read() + + iLoginProgress.addToLog("\nLoading discord.py...") + global discord + import discord + global tasks + from discord.ext import tasks + logging.basicConfig(level=logging.INFO) + + iLoginProgress.addToLog("\nConnecting...") + global client + client = discord.Client() + + @client.event + async def on_ready(): + print("on_ready") + global initialized + if initialized == False: + iLoginProgress.window.destroy() + global openGuis + openGuis = [] + openGuis.append(guiConversationList()) + guiLoop.start() + conversationUpdate.start() + initialized = True + + global connected + connected = True + + for gui in openGuis: + if gui.closed == True: continue + gui.window.title(getTitle(gui.title)) + + await client.change_presence(status=discord.Status.dnd, activity=discord.CustomActivity("working on discord but fast")) + + @client.event + async def on_resumed(): + print("on_resumed") + global connected + connected = True + + for gui in openGuis: + if gui.closed == True: continue + gui.window.title(getTitle(gui.title)) + + @client.event + async def on_disconnect(): + print("on_disconnect") + + global connected + connected = False + + for gui in openGuis: + if gui.closed == True: continue + gui.window.title(getTitle(gui.title)) + + @client.event + async def on_message(message): + for gui in openGuis: + if gui.closed == True: continue + if gui.type == "conversation": + if message.channel != gui.channel: continue + gui.addMessage(message) + gui.fillMessageLog() + + @tasks.loop(seconds=0.033) + async def guiLoop(): + length = len(openGuis) + index = 0 + while index < length: + gui = openGuis[index] + if gui.closed == True: + del openGuis[index] + length = length - 1 + continue + + if gui.type == "conversation": + if gui.readied == False: + try: + messages = await gui.channel.history(limit=int(config["performance"]["historyFetch"])).flatten() + for message in reversed(messages): + gui.addMessage(message) + except: + gui.addMessage(["","Fetching messages failed."]) + gui.fillMessageLog() + gui.readied = True + + while len(gui.pendingMessages) > 0: + message = "" + try: + message = gui.pendingMessages.pop(0) + await gui.channel.send(message) + except: + print("error sending message") + + gui.window.update() + index = index + 1 + + @tasks.loop(seconds=1.0) + async def conversationUpdate(): + channels = client.private_channels + + for gui in openGuis: + if gui.type != "conversationList": continue + if gui.closed == True: continue + if channels == gui.convs: continue + gui.updateConvs(channels) + + client.run(token,bot = False) + +if __name__ == "__main__": + main() \ No newline at end of file