def msg_send(self, event): '''Sending message when pressing the "Send" button (redirecting to datastream)''' msg = self.chatinput.GetValue() ### / PUT THIS INTO COMMAND MANAGER / ### if len(msg.strip(" ")) > 0: # Strip spaces and check for content if msg in ["/" + c for c in self.commands]: # Check for commands # Quit if "/exit", else send if msg == "/exit": self.write_log(now(), self.username + ": " + msg) self.quit(None) else: msgsend = {"datatype": "command", "msg": msg} if self.currentroom != "GLOBAL CHAT": msgsend["room"] = self.currentroom self.datastream.put(("send", msgsend)) else: # HAN SOM SKRIVER DETTE PROGRAMMET ER GOD! # DE SOM TESTER DETTE PROGRAMMET ER GODE! # Sending all chat messages msgsend = {"datatype": "chat", "msg": msg} if self.currentroom != "GLOBAL CHAT": msgsend["room"] = self.currentroom self.datastream.put(("send", msgsend)) self.chatinput.Clear()
def switch_room(self, event): '''Switching room (within current server)''' item = event.GetItem() # Get clicked item if item.IsOk( ): # If selected room is valid (practically: if rooms are not unselected by UnselectAll) roomname = self.chatrooms.GetItemText(item) private = self.GET_ROOMTYPE("private") if self.chatrooms.GetItemParent( item) == self.chatrooms.GetRootItem( ): # Check if clicked room is higher-level if roomname == "GLOBAL CHAT": self.currentroom = "GLOBAL CHAT" else: if roomname in private and "password" in private[roomname]: self.roomprompt = None # Indication wether password was correct or not (from server) # Show password prompt window if user does not have access yet if roomname not in self.roomaccess: passprompt = RoomPasswordWindow(self, roomname) if passprompt.password == None: # If window was closed without user inputting password self.chatinput.SetFocus() event.Veto() # Stop room from being selected return else: # Send data if password input had content self.datastream.put(("send", {"datatype": "roompass", "room": roomname, \ "password": HASH(passprompt.password)})) while self.roomprompt == None: sleep(0.01) # Wait for return message if self.roomprompt: self.roomaccess.append(roomname) else: self.chatinput.SetFocus() event.Veto( ) # Don't select room if password was wrong return self.currentroom = self.chatrooms.GetItemText( item) # Update current room # Load HTML of new room if len(self.seconds[self.host][roomname]) == 0: self.add_msg("", HTML_ROOM_WELCOME(roomname), now(), "black", roomname) self.chattext.LoadURL(self.paths["chatlog"](self.currentroom)) self.chatrooms.UnselectAll() # Prevent multiple rooms self.chatinput.SetFocus()
def afk_actions(self, afk, user=None): '''Function for handling AFK actions''' # Set AFK status status = "AFK" if afk else "back" # Send info if this user becomes AFK if user == None: user = self.username self.add_msg("", "You are " + status + ".", now(), "black", self.currentroom) self.datastream.put(("send", { "datatype": "afk", "status": afk, "room": self.currentroom })) self.edit_userlist(user, afk=afk) # Edit HTML
def add_server(self, event): '''Add server when pressing the "Add server" button. Displays a window for the user to input server settings.''' addserver = AddServerWindow(self, self.serverdata) # Show window if addserver.proceed: addr = addserver.serveraddr port = addserver.serverport self.write_log(now(), "You added server: " + addr) self.serverdata[addr] = port # Add server to list self.chatroomdata[addr] = {"public": {}, "private": {}} self.seconds[addr] = {} # Save and show in list FILE_WRITE(self.paths["rooms"], self.chatroomdata) FILE_WRITE(self.paths["servers"], self.serverdata) self.servers.Append(addr)
def avatar_change(self, user, avatar): '''Function for creating/changing avatar''' path_avatar = self.dirs["avatars"] + "\\" + user + ".png" time_now = now() # Write to log if user == self.username: self.write_log(time_now, "You changed your avatar.") else: self.write_log(time_now, user + " changed their avatar.") # Save avatar if type(avatar) == wx.Bitmap: avatar.SaveFile(path_avatar, wx.BITMAP_TYPE_PNG) else: FILE_WRITE(path_avatar, avatar, "wb") self.avatar_thumb(user) # Create thumbnail for user list # Update user list self.edit_userlist(user, avatar=True) wx.CallAfter(self.userlist.LoadURL, self.paths["online"])
def __init__(self, username, password, host, port, clientsocket): wx.Frame.__init__(self, None, title=__appname__ + " " + __version__) # Making args class-namespaced self.host = host self.port = port self.username = username self.password = password self.clientsocket = clientsocket # Path variables self.userdir = appdata + self.username + "\\" self.dirs = { "avatars": appdata + "avatars", "chatlogs": self.userdir + "chatlogs", "chatlogs_server": lambda: self.userdir + "chatlogs\\" + self.host, "thumbs": self.userdir + "thumbs" } self.paths = { "avatar": self.dirs["avatars"] + "\\" + self.username + ".png", "chatlog": lambda roomname: self.dirs["chatlogs_server"]() + "\\" + roomname + ".html", "contacts": self.userdir + "contacts.txt", "lastmsg": self.userdir + "lastmsg.txt", "lastupdate": self.userdir + "lastupdate.txt", "log": self.userdir + "log.log", "online": self.userdir + "online.html", "rooms": self.userdir + "rooms.txt", "servers": self.userdir + "servers.txt", "settings": self.userdir + "settings.txt" } # Misc variables self.new_server = [None, None] self.currentroom = "GLOBAL CHAT" self.roomaccess = [] self.seconds = {} self.roomtypes = {} self.usercolors = {} self.datastream = Queue() # self.commands = ["afk", "die", "exit", "help"] self.colordata = self.roomhittest = None self.disconnected = self.quitting = self.reconnecting = False self.autoafk = True self.banned_html = ["body", "caption", "form", "frame", "head", "html", "iframe", "link", "script", "style", \ "table", "tbody", "td", "tfoot", "thead", "tr"] # Loading values file_room = self.paths["rooms"] self.chatroomdata = FILE_LOAD(file_room, {}) self.serverdata = FILE_LOAD(self.paths["servers"], {self.host: port}) self.lastupdate = FILE_LOAD(self.paths["lastupdate"], {self.host: time()}) # Getting startup information from server self.userdata = eval(RECVMSG(self.clientsocket)) # Checking if room file needs to be updated if (self.host not in self.lastupdate) or (self.host not in self.chatroomdata) or \ (self.userdata["mtime-rooms"] > self.lastupdate[self.host] + 0.1): self._try_send("SEND-ROOMS") self.chatroomdata[self.host] = eval(RECVMSG(self.clientsocket)) FILE_WRITE(file_room, self.chatroomdata) self.lastupdate[self.host] = time() FILE_WRITE(self.paths["lastupdate"], self.lastupdate) else: self._try_send("DONT-SEND-ROOMS") self.chatroomdata = eval(FILE_READ(file_room)) # Managing settings settings = self.paths["settings"] action_settings = self.startup_data("settings") if action_settings == "FETCH": response = RECVMSG(self.clientsocket) if response == "NO-SETTINGS": self.settings = defaultsettings self._try_send(self.settings) else: self.settings = eval(response) self.new_settings() FILE_WRITE(settings, self.settings) else: self.settings = eval(FILE_READ(settings)) self.new_settings() if self.userdata["mtime-settings"] == None: self._try_send(self.settings) self.initsettings = deepcopy(self.settings) self.afkinit = self.afktimer = self.settings["afkinterval"] # Creating structure of "seconds" variable for server in self.chatroomdata: self.seconds[server] = {} self.seconds[server]["GLOBAL CHAT"] = [] for room in self.GET_ROOMTYPE("public").keys() + self.GET_ROOMTYPE("private").keys(): self.seconds[server][room] = [] # Setting up folders and files DIR_CREATE(self.dirs["avatars"]) DIR_CREATE(self.dirs["chatlogs"]) DIR_CREATE(self.dirs["chatlogs_server"]()) DIR_CREATE(self.dirs["thumbs"]) FILE_WRITE(self.paths["chatlog"](self.currentroom), HTML_CHAT) FILE_WRITE(self.paths["online"], HTML_SESSION) # Panel self.panel = wx.Panel(self) # Popup menu: Colors self.colorpopup = wx.Menu() colors = ["white", "grey", "black", "red", (255, 127, 0), "yellow", "green", "cyan", "blue", "navy", "purple"] for color in colors: coloritem = wx.MenuItem(self.colorpopup, wx.ID_ANY, " ") coloritem.SetBackgroundColour(color) self.colorpopup.AppendItem(coloritem) self.Bind(wx.EVT_MENU, self.set_color, coloritem) self.colorpopup.AppendSeparator() colorwheel = self.colorpopup.Append(wx.ID_ANY, "Color wheel...") self.Bind(wx.EVT_MENU, self.color_dialog, colorwheel) # Popup menu: Rooms self.roommenu = wx.Menu() room_delete = wx.MenuItem(self.roommenu, wx.ID_ANY, "Delete room") self.roommenu.AppendItem(room_delete) self.Bind(wx.EVT_MENU, self.delete_room, room_delete) # Menu bar menubar = wx.MenuBar() menu_settings = wx.Menu() menu_settings_afk = menu_settings.Append(wx.ID_ANY, "AFK...") menu_settings_avatar = menu_settings.Append(wx.ID_ANY, "Avatar...") menu_settings_emotes = menu_settings.Append(wx.ID_ANY, "Emotes...") if basename(split(getcwd())[0]) == "CharlieChat Dev": menu_settings_messages = menu_settings.Append(wx.ID_ANY, "Messages...") # @UnusedVariable menu_settings_sounds = menu_settings.Append(wx.ID_ANY, "Sounds...") # @UnusedVariable menu_settings_text = menu_settings.Append(wx.ID_ANY, "Text...") # @UnusedVariable menu_account = wx.Menu() menu_account_switchuser = menu_account.Append(wx.ID_ANY, "Switch user...") menu_account_register = menu_account.Append(wx.ID_ANY, "Register new user...") menu_account_changepass = menu_account.Append(wx.ID_ANY, "Change password...") menu_account_delete = menu_account.Append(wx.ID_ANY, "Delete account") menu_connection = wx.Menu() self.menu_connection_disconnect = menu_connection.Append(wx.ID_ANY, "Disconnect") menu_connection_restart = menu_connection.Append(wx.ID_ANY, "Restart connection") menubar.Append(menu_settings, "Settings") menubar.Append(menu_account, "Account") menubar.Append(menu_connection, "Connection") self.SetMenuBar(menubar) # Toolbar self.toolbar = self.CreateToolBar() self.tool_sounds = self.toolbar.AddCheckLabelTool(1, "Seconds", wx.Bitmap("images/speaker.png")) self.tool_seconds = self.toolbar.AddCheckLabelTool(2, "Sounds", wx.Bitmap("images/seconds.png")) self.tool_afk = self.toolbar.AddCheckLabelTool(3, "AFK", wx.Bitmap("images/afk.png")) self.toolbar.Realize() self.toolbar.SetToolShortHelp(1, "Toggle sounds") self.toolbar.SetToolShortHelp(2, "Toggle display of seconds in chat window") self.toolbar.SetToolShortHelp(3, "Toggle whether you are shown as AFK or not") # Sizers box = wx.BoxSizer() grid = wx.FlexGridSizer(2, 1, 10, 10) header = wx.FlexGridSizer(1, 2, 10, 10) windows = wx.FlexGridSizer(1, 3, 10, 10) chat = wx.FlexGridSizer(0, 1, 10, 10) chatbuttons = wx.FlexGridSizer(1, 2, 10, 10) roomsettings = wx.FlexGridSizer(5, 1, 10, 10) conversation = wx.GridSizer(1, 0, 5, 5) # Widgets webcam = wx.ToggleButton(self.panel, label="Webcam") voice = wx.ToggleButton(self.panel, label="Voice") self.chattext = WebView.New(self.panel) self.add_msg("", HTML_WELCOME, now(), "black", self.currentroom, False) FILE_WRITE(self.paths["chatlog"](self.currentroom), HTML_LINE, "a") self.chatinput = wx.TextCtrl(self.panel, style=wx.TE_MULTILINE | wx.TE_PROCESS_ENTER) self.send = wx.Button(self.panel, label="SEND", size=(-1, 50)) colorbutton = wx.BitmapButton(self.panel, bitmap=wx.Image("images/colorwheel.png", \ wx.BITMAP_TYPE_PNG).ConvertToBitmap(), size=(50, 50)) usercontainer = wx.Notebook(self.panel) users_online = PanelUsermode(self, usercontainer, self.paths["online"]) self.users_contacts = PanelUsermode(self, usercontainer, "contacts") usercontainer.AddPage(users_online, "Online") usercontainer.AddPage(self.users_contacts, "Contacts") self.conv_info = wx.StaticText(self.panel, label=self.host + ": " + self.username) self.reconnect = wx.StaticText(self.panel, label="Reconnecting") self.servers = wx.ListBox(self.panel, style=wx.LB_SINGLE) server_add = wx.Button(self.panel, label="Add server") self.chatrooms = wx.TreeCtrl(self.panel, style=wx.TR_HAS_BUTTONS | wx.TR_HIDE_ROOT | \ wx.TR_ROW_LINES | wx.TR_MULTIPLE) room_search = wx.SearchCtrl(self.panel) room_add = wx.Button(self.panel, label="Add room") # Fonts font = self.conv_info.GetFont() font.SetPointSize(14) self.conv_info.SetFont(font) font.SetPointSize(12) font.SetWeight(wx.BOLD) self.send.SetFont(font) font = self.chatinput.GetFont() font.SetPointSize(11) self.chatinput.SetFont(font) font = self.reconnect.GetFont() font.SetWeight(wx.BOLD) self.reconnect.SetFont(font) # Add servers to list widget if self.host not in self.serverdata: self.serverdata[self.host] = self.port FILE_WRITE(self.paths["servers"], self.serverdata) self.servers.Append(self.host) for lbl_server in self.serverdata: self.servers.Append(lbl_server) self.servers.SetSelection(self.servers.GetItems().index(self.host)) # Global room root = self.chatrooms.AddRoot("") globalroom = self.chatrooms.AppendItem(root, "GLOBAL CHAT") self.chatrooms.SetItemBold(globalroom) # Loop: roomtypes for lbl_roomtype in self.GET_SERVER(): roomtype = self.chatrooms.AppendItem(root, lbl_roomtype.title() + " rooms") self.chatrooms.SetItemBold(roomtype) self.roomtypes[lbl_roomtype] = roomtype # Loop: rooms for lbl_room in self.GET_ROOMTYPE(lbl_roomtype): self.chatrooms.AppendItem(roomtype, lbl_room) # Update HTML for each room FILE_WRITE(self.paths["chatlog"](lbl_room), HTML_CHAT) self.chatrooms.ExpandAll() self.chatrooms.SelectItem(globalroom) # Add widgets to sizers conversation.AddMany([webcam, voice]) chat.AddMany([conversation, (self.chattext, 1, wx.EXPAND), (self.chatinput, 1, wx.EXPAND), (chatbuttons, 1, wx.EXPAND)]) header.AddMany([self.conv_info, (self.reconnect, 0, wx.ALIGN_RIGHT | wx.ALIGN_BOTTOM)]) chatbuttons.AddMany([(self.send, 1, wx.EXPAND), colorbutton]) roomsettings.AddMany([(self.servers, 1, wx.EXPAND), (server_add, 1, wx.EXPAND), (self.chatrooms, 1, wx.EXPAND), \ (room_search, 1, wx.EXPAND), (room_add, 1, wx.EXPAND)]) windows.AddMany([(usercontainer, 1, wx.EXPAND), (chat, 1, wx.EXPAND), (roomsettings, 1, wx.EXPAND)]) grid.AddMany([(header, 1, wx.EXPAND), (windows, 1, wx.EXPAND)]) box.Add(grid, 1, wx.ALL | wx.EXPAND, 20) self.panel.SetSizer(box) # Define growables and ratios chat.AddGrowableRow(1, 10) # Chat height chat.AddGrowableRow(2, 2) # Input height chat.AddGrowableCol(0) # Chat width windows.AddGrowableRow(0) # Users height windows.AddGrowableCol(0, 3) # Users width windows.AddGrowableCol(1, 10) # Chat + input + buttons width windows.AddGrowableCol(2, 1) # Chatrooms width roomsettings.AddGrowableCol(0) # Chatrooms width roomsettings.AddGrowableRow(0, 1) # Chatrooms height roomsettings.AddGrowableRow(2, 5) # Chatrooms height chatbuttons.AddGrowableCol(0) # Send button width header.AddGrowableCol(1) # Reconnect text grid.AddGrowableCol(0) # Conversation info width grid.AddGrowableRow(1) # Widgets height # Window size self.SetClientSize((1050, 518)) self.SetMinSize((454, 413)) # Binding events self.Bind(wx.EVT_MENU, self.settings_afk, menu_settings_afk) self.Bind(wx.EVT_MENU, self.settings_avatar, menu_settings_avatar) self.Bind(wx.EVT_MENU, self.settings_emotes, menu_settings_emotes) self.Bind(wx.EVT_MENU, self.switch_user, menu_account_switchuser) self.Bind(wx.EVT_MENU, self.register_user, menu_account_register) self.Bind(wx.EVT_MENU, self.change_password, menu_account_changepass) self.Bind(wx.EVT_MENU, self.delete_user, menu_account_delete) self.Bind(wx.EVT_MENU, self.disconnect, self.menu_connection_disconnect) self.Bind(wx.EVT_MENU, self.switch_server, menu_connection_restart) self.Bind(wx.EVT_TOOL, self.toggle_seconds, self.tool_seconds) self.Bind(wx.EVT_TOOL, self.toggle_sounds, self.tool_sounds) self.Bind(wx.EVT_TOOL, self.toggle_afk, self.tool_afk) self.Bind(wx.EVT_LISTBOX, self.switch_server, self.servers) self.Bind(wx.EVT_BUTTON, self.add_server, server_add) self.Bind(wx.EVT_TREE_SEL_CHANGING, self.switch_room, self.chatrooms) self.chatrooms.Bind(wx.EVT_RIGHT_DOWN, self.popup_room) self.Bind(wx.EVT_TEXT, self.search_rooms, room_search) room_search.Bind(wx.EVT_KEY_DOWN, ERASE_TEXT) self.Bind(wx.EVT_BUTTON, self.add_room, room_add) self.Bind(wx.EVT_TOGGLEBUTTON, self.toggle_webcam, webcam) self.Bind(wx.EVT_TOGGLEBUTTON, self.toggle_voice, voice) self.Bind(EVT_WEBVIEW_NAVIGATING, self.url_navigate, self.chattext) self.Bind(wx.EVT_BUTTON, self.popup_color, colorbutton) self.chatinput.Bind(wx.EVT_KEY_DOWN, self.chat_hotkeys) self.Bind(wx.EVT_BUTTON, self.msg_send, self.send) self.Bind(wx.EVT_CLOSE, self.quit) # Hiding reconnect (only show when actually reconnecting) self.reconnect.Hide() # self.contactlist.Hide() # Set focus to text input (start writing instantly) self.chatinput.SetFocus() # Applying settings self.apply_settings() self.chatinput.SetForegroundColour(self.settings["color"]) # Loading contacts action_contacts = self.startup_data("contacts") if action_contacts == "FETCH": response = RECVMSG(self.clientsocket) if response == "NO-CONTACTS": if not isfile(self.paths["contacts"]): with open(self.paths["contacts"], "w"): pass with open(self.paths["contacts"]) as contactfile: self._try_send(contactfile.read()) else: with open(self.paths["contacts"], "w") as contactfile: contactfile.write(response) else: if self.userdata["mtime-contacts"] == None: with open(self.paths["contacts"]) as contactfile: self._try_send(contactfile.read()) self.load_contacts() # Starting threads self.thread_stream = MultiThread(self.data_actions) self.thread_recv = MultiThread(self.msg_recv) self.thread_afk = MultiThread(self.afk_countdown) self.thread_stream.daemon = True self.thread_recv.daemon = True self.thread_afk.daemon = True self.thread_stream.start() self.thread_recv.start() if self.settings["autoafk"]: self.thread_afk.start() self.init_voice() MultiThread(self.empty_voicebuffer).start() # Load timestamp of last message (determine if log file needs new date header) self.lastmsg = FILE_LOAD(self.paths["lastmsg"], "'" + now().split(" ")[0] + "'") # Add users to list of connected users self.add_user(self.username, self.userdata["mtime-avatar"]) for user in self.userdata["users"]: self.add_user(user, self.userdata["users"][user]["mtime-avatar"], user in self.userdata["afks"], \ self.userdata["users"][user]["color"]) # Set icon (REMOVE set_appname WHEN .exe) set_appname() self.SetIcon(wx.Icon("images/testicon.ico")) self.hook_establish() # Showing window self.Center() self.Show()
def data_actions(self): '''Handling all data sent and received''' # nolog = ["add_room", "afk", "avatar-change", "avatar-fetch", "avatar-send", "color", \ # "del_room", "del_user", "noaccess", "roompass", "voice", "webcam"] # log = ["chat", "command", "connect", "disconnect"] while True: if self.quitting: return sleep(0.01) # Preventing CPU gobbling if not self.datastream.empty(): data_raw = self.datastream.get() # Get next message from datastream data = data_raw[1] datatype = data["datatype"] ### SENDING MESSAGE ### if data_raw[0] == "send": time_now = now() # Write log if datatype == "add_room": self.write_log(time_now, "You added " + data["type"] + " room: " + data["name"]) elif datatype in self.log_commands: self.write_log(time_now, self.username + ": " + data["msg"], self.currentroom) self._try_send(data) if datatype in self.log_commands: # Commands if data["msg"] in ["/" + c for c in self.commands]: if data["msg"] == "/help": self.add_msg("", "You need help!", time_now, "black", self.currentroom) elif data["msg"] == "/afk": self.afktimer = 0 self.afk_actions(True) # Chat messages else: self.add_msg(self.username, data["msg"], time_now, self.settings["color"], self.currentroom) ### RECEIVING MESSAGE ### elif data_raw[0] == "recv": print datatype # User connecting # if datatype == "connect": self.write_log(data["time"], data["user"] + " connected.") self.add_user(data["user"], data["mtime-avatar"], color=data["color"]) self.add_msg("", data["user"] + " connected.", data["time"], "black", self.currentroom) # Sending avatar to server # elif datatype == "avatar-send": if data["value"] == True: self._try_send({"datatype": "avatar-send", "value": FILE_READ(self.paths["avatar"], True)}) # Fetching avatar from server # elif datatype == "avatar-fetch": if data["value"] != False: FILE_WRITE(self.dirs["avatars"] + "\\" + data["user"] + ".png", data["value"], "wb") self.avatar_thumb(data["user"]) self.edit_userlist(data["user"], avatar=True) # Changing avatar # elif datatype == "avatar-change": if data["value"] == "delete": self.avatar_delete(data["user"]) else: self.avatar_change(data["user"], data["value"]) elif datatype == "contact-exists": if data["value"]: with open(self.paths["contacts"], "a") as contactfile: contactfile.write(data["contact"] + "\n") self.users_contacts.widget.Append([data["contact"]]) self.users_contacts.contact_name.Clear() # Changing text color elif datatype == "color": self.usercolors[data["user"]] = data["value"] # Chat message # elif datatype == "chat": room = data["room"] if "room" in data else "GLOBAL CHAT" self.write_log(data["time"], data["user"] + ": " + data["msg"], room) self.add_msg(data["user"], data["msg"], data["time"], self.usercolors[data["user"]], room) # Command # elif datatype == "command": self.write_log(data["time"], data["user"] + ": " + data["msg"]) if data["msg"] == "/help": self.add_msg("", data["user"] + " needs help!", data["time"], "black", self.currentroom) # AFK # elif datatype == "afk": if data["status"]: self.write_log(data["time"], data["user"] + " is AFK.") self.add_msg("", data["user"] + " is AFK.", data["time"], "black", self.currentroom) else: self.write_log(data["time"], data["user"] + " is back.") self.add_msg("", data["user"] + " is back.", data["time"], "black", self.currentroom) self.afk_actions(data["status"], data["user"]) # Adding room # elif datatype == "add_room": self.write_log(data["time"], data["user"] + " added " + data["type"] + " room: " + data["name"]) self.add_room(None, "recv", data["type"], data["name"], "password" in data) # Is room unique? # elif datatype == "room_unique": self.room_unique = data["value"] # Was room password correct? # elif datatype == "roompass": self.roomprompt = data["proceed"] # Deleting room # elif datatype == "del_room": self.write_log(data["time"], data["user"] + " deleted " + data["type"] + " room: " + data["name"]) self.delete_room(None, data["name"]) elif datatype == "webcam": webcam.recv_camstream(data["data"]) elif datatype == "voice": voice.recv_voice(data["data"]) # User disconnecting # elif datatype == "disconnect": self.write_log(data["time"], data["user"] + " disconnected.") self.edit_userlist(data["user"], False) self.add_msg("", data["user"] + " disconnected.", data["time"], "black", self.currentroom) # Flash window and play sound (if window doesn't have focus) if (datatype not in ["add_room", "avatar-change", "avatar-fetch", "avatar-send", \ "del_room", "roompass", "room_unique", "voice", "webcam"]): flash_window(self.GetHandle(), self.settings["sounds"])