From 57ff09098ef3d2ac3046b4fb1bd25f5091d9c9c6 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Mon, 31 Oct 2016 22:05:45 +0200 Subject: [PATCH 01/40] LC-23 Add ban/unban/warn messages for goodgame.ru --- http/czt/js/socket.js | 18 +++++++++++++++++- modules/chat/goodgame.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/http/czt/js/socket.js b/http/czt/js/socket.js index 2493070..9923670 100644 --- a/http/czt/js/socket.js +++ b/http/czt/js/socket.js @@ -131,6 +131,8 @@ function showMessage(message) { var elements = {}; elements['message'] = document.createElement('div'); elements.message.setAttribute('class', 'msg'); + elements.message.setAttribute('id', message.id); + elements.message.setAttribute('user', message.user); if(timeout > 0) { elements.message.setAttribute('timer-id', setTimeout(removeMessage, timeout * 1000, elements.message)); } @@ -272,4 +274,18 @@ function runCommand(message) { if(message.command == 'reload'){ window.location.reload(); } -} \ No newline at end of file + else if(message.command == 'remove_msg') { + if(message.ids) { + message.ids.forEach(function(message_item) { + item = document.getElementById(message_item) + if (item) { + item.parentNode.removeChild(item) + } + }) + } + } + else { + console.log("Got unknown command " + message.command) + } +} + diff --git a/modules/chat/goodgame.py b/modules/chat/goodgame.py index 270aa0d..41e316a 100644 --- a/modules/chat/goodgame.py +++ b/modules/chat/goodgame.py @@ -1,3 +1,5 @@ +# This Python file uses the following encoding: utf-8 +# -*- coding: utf-8 -*- import json import threading import os @@ -5,7 +7,6 @@ import Queue import re import logging -import time from collections import OrderedDict from modules.helper.parser import self_heal from modules.helper.system import system_message @@ -17,6 +18,7 @@ SOURCE = 'gg' SOURCE_ICON = 'http://goodgame.ru/images/icons/favicon.png' SYSTEM_USER = 'GoodGame' +ID_PREFIX = 'gg_{0}' CONF_DICT = OrderedDict() CONF_DICT['gui_information'] = {'category': 'chat'} CONF_DICT['config'] = OrderedDict() @@ -50,7 +52,8 @@ def process_message(self, msg): if msg['type'] == "message": # Getting all needed data from received message # and sending it to queue for further message handling - comp = {'source': self.source, + comp = {'id': ID_PREFIX.format(msg['data']['message_id']), + 'source': self.source, 'source_icon': SOURCE_ICON, 'user': msg['data']['user_name'], 'text': msg['data']['text'], @@ -100,6 +103,27 @@ def process_message(self, msg): if msg['data']['errorMsg'] == 'Invalid channel id': self.ws_class.close(reason='INV_CH_ID') log.error("Failed to find channel on GoodGame, please check channel name") + elif msg['type'] == 'user_warn': + self.ws_class.system_message(u"{0} вынес предупреждение {1}".format(msg['data']['moder_name'], + msg['data']['user_name'])) + elif msg['type'] == 'remove_message': + remove_id = ID_PREFIX.format(msg['data']['message_id']) + self.message_queue.put({'command': 'remove_msg', + 'ids': [remove_id]}) + elif msg['type'] == 'user_ban': + if msg['data']['duration']: + self.ws_class.system_message(u'{0} забанил {1} на {2} минут ' + u'по причине: {3}'.format(msg['data']['moder_name'], + msg['data']['user_name'], + msg['data']['duration']/60, + msg['data']['reason'])) + else: + if msg['data']['permanent']: + self.ws_class.system_message(u'{0} забанил бессрочно {1}'.format(msg['data']['moder_name'], + msg['data']['user_name'])) + else: + self.ws_class.system_message(u'{0} разбанил {1}'.format(msg['data']['moder_name'], + msg['data']['user_name'])) class GGChat(WebSocketClient): From 9dcff158703c49790576f5b996d53ca3ad3abe42 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Mon, 31 Oct 2016 23:39:36 +0200 Subject: [PATCH 02/40] LC-137 Delete messages from Twitch --- http/czt/js/socket.js | 11 ++++ modules/chat/twitch.py | 111 ++++++++++++++++++++++------------------- 2 files changed, 70 insertions(+), 52 deletions(-) diff --git a/http/czt/js/socket.js b/http/czt/js/socket.js index 9923670..de86c13 100644 --- a/http/czt/js/socket.js +++ b/http/czt/js/socket.js @@ -283,6 +283,17 @@ function runCommand(message) { } }) } + else if(message.user) { + message.user.forEach(function(user) { + var children = chatMessages.childNodes + var node_length = children.length - 1 + for(i=node_length; i >= 0; --i) { + if(children[i].getAttribute('user') == user) { + children[i].parentNode.removeChild(children[i]) + } + } + }) + } } else { console.log("Got unknown command " + message.command) diff --git a/modules/chat/twitch.py b/modules/chat/twitch.py index 90eca23..a162741 100644 --- a/modules/chat/twitch.py +++ b/modules/chat/twitch.py @@ -68,63 +68,66 @@ def process_message(self, msg): # Also, there is slight problem with some users, they don't have # the display-name tag, so we have to check their "real" username # and capitalize it because twitch does so, so we do the same. - comp = {'source': self.source, - 'source_icon': SOURCE_ICON, - 'badges': [], - 'emotes': [], - 'bttv_emotes': [], - 'user': 'TwitchSystem', - 'msg_type': msg.type} - for tag in msg.tags: - tag_value, tag_key = tag.values() - if tag_key == 'display-name': - if tag_value: - comp['user'] = tag_value - else: - # If there is not display-name then we strip the user - # from the string and use it as it is. - comp['user'] = msg.source.split('!')[0].capitalize() - elif tag_key == 'badges' and tag_value: - for badge in tag_value.split(','): - badge_tag, badge_size = badge.split('/') - # Fix some of the names - badge_tag = badge_tag.replace('moderator', 'mod') - - if badge_tag in self.badges: - badge_info = self.badges.get(badge_tag) - if 'svg' in badge_info: - url = badge_info.get('svg') - elif 'image' in badge_info: - url = badge_info.get('image') - else: - url = 'none' - elif badge_tag in self.custom_badges: - badge_info = self.custom_badges.get(badge_tag)['versions'][badge_size] - url = badge_info.get('image_url_4x') + if msg.type in ['pubmsg', 'action']: + comp = {'source': self.source, + 'source_icon': SOURCE_ICON, + 'badges': [], + 'emotes': [], + 'bttv_emotes': [], + 'user': 'TwitchSystem', + 'msg_type': msg.type} + for tag in msg.tags: + tag_value, tag_key = tag.values() + if tag_key == 'display-name': + if tag_value: + comp['user'] = tag_value else: - url = NOT_FOUND - comp['badges'].append({'badge': badge_tag, 'size': badge_size, 'url': url}) - - elif tag_key == 'emotes' and tag_value: - emotes_split = tag_value.split('/') - for emote in emotes_split: - emote_name, emote_pos_diap = emote.split(':') - emote_pos_list = emote_pos_diap.split(',') - comp['emotes'].append({'emote_id': emote_name, 'emote_pos': emote_pos_list}) + # If there is not display-name then we strip the user + # from the string and use it as it is. + comp['user'] = msg.source.split('!')[0].capitalize() + elif tag_key == 'badges' and tag_value: + for badge in tag_value.split(','): + badge_tag, badge_size = badge.split('/') + # Fix some of the names + badge_tag = badge_tag.replace('moderator', 'mod') + + if badge_tag in self.badges: + badge_info = self.badges.get(badge_tag) + if 'svg' in badge_info: + url = badge_info.get('svg') + elif 'image' in badge_info: + url = badge_info.get('image') + else: + url = 'none' + elif badge_tag in self.custom_badges: + badge_info = self.custom_badges.get(badge_tag)['versions'][badge_size] + url = badge_info.get('image_url_4x') + else: + url = NOT_FOUND + comp['badges'].append({'badge': badge_tag, 'size': badge_size, 'url': url}) - # Then we comp the message and send it to queue for message handling. - comp['text'] = msg.arguments.pop() + elif tag_key == 'emotes' and tag_value: + emotes_split = tag_value.split('/') + for emote in emotes_split: + emote_name, emote_pos_diap = emote.split(':') + emote_pos_list = emote_pos_diap.split(',') + comp['emotes'].append({'emote_id': emote_name, 'emote_pos': emote_pos_list}) - for word in comp['text'].split(): - if word in self.bttv: - bttv_smile = self.bttv.get(word) - comp['bttv_emotes'].append({'emote_id': bttv_smile['regex'], - 'emote_url': 'http:{0}'.format(bttv_smile['url'])}) + # Then we comp the message and send it to queue for message handling. + comp['text'] = msg.arguments.pop() - if re.match('^@?{0}[ ,]?'.format(self.nick), comp['text'].lower()): - comp['pm'] = True + for word in comp['text'].split(): + if word in self.bttv: + bttv_smile = self.bttv.get(word) + comp['bttv_emotes'].append({'emote_id': bttv_smile['regex'], + 'emote_url': 'http:{0}'.format(bttv_smile['url'])}) - self.message_queue.put(comp) + if re.match('^@?{0}[ ,]?'.format(self.nick), comp['text'].lower()): + comp['pm'] = True + self.message_queue.put(comp) + elif msg.type in ['clearchat']: + self.message_queue.put({'command': 'remove_msg', + 'user': msg.arguments}) class TwitchPingHandler(threading.Thread): @@ -188,6 +191,7 @@ def on_welcome(self, connection, event): # Subscriber, etc) connection.join(self.channel) connection.cap('REQ', ':twitch.tv/tags') + connection.cap('REQ', ':twitch.tv/commands') ping_handler = TwitchPingHandler(connection) ping_handler.start() @@ -202,6 +206,9 @@ def on_pubmsg(self, connection, event): def on_action(self, connection, event): self.twitch_queue.put(event) + def on_clearchat(self, connection, event): + self.twitch_queue.put(event) + class twThread(threading.Thread): def __init__(self, queue, host, port, channel, bttv_smiles, anon=True): From 379ba02fa4c5831c259ed91da47cc683811f16e7 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Tue, 1 Nov 2016 20:52:45 +0200 Subject: [PATCH 03/40] LC-143 Hide category panel, reuse if chosen --- gui.py | 66 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/gui.py b/gui.py index 661c407..9ebf636 100644 --- a/gui.py +++ b/gui.py @@ -146,7 +146,7 @@ def __init__(self, *args, **kwargs): self.settings_saved = True self.tree_ctrl = None self.content_page = None - self.sizer_dict = {} + self.sizer_list = [] # Setting up the window self.SetBackgroundColour('cream') @@ -208,6 +208,19 @@ def on_listbox_change(self, event): descr_static_text.SetLabel(description) descr_static_text.Wrap(descr_static_text.GetSize()[0]) + def on_tree_ctrl_changed(self, event): + self.settings_saved = False + tree_ctrl = event.EventObject # type: wx.TreeCtrl + selection = tree_ctrl.GetFocusedItem() + selection_text = tree_ctrl.GetItemData(selection).GetData() + key_list = selection_text.split(MODULE_KEY) + + # Drawing page + self.fill_page_with_content(self.content_page, key_list[1], key_list[-1], + self.main_class.loaded_modules[key_list[-1]]) + + event.Skip() + def create_layout(self): self.main_grid = wx.BoxSizer(wx.HORIZONTAL) tree_ctrl_size = wx.Size(220, -1) @@ -234,7 +247,7 @@ def create_layout(self): tree_ctrl.ExpandAll() self.tree_ctrl = tree_ctrl - self.Bind(wx.EVT_TREE_SEL_CHANGED, self.tree_ctrl_changed, id=tree_ctrl_id) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_ctrl_changed, id=tree_ctrl_id) self.main_grid.Add(self.tree_ctrl, 0, wx.EXPAND | wx.ALL, 7) content_page_id = id_renew(MODULE_KEY.join(['settings', 'content'])) @@ -255,22 +268,30 @@ def create_button(button_key, function): page_sizer = panel.GetSizer() # type: wx.Sizer if not page_sizer: page_sizer = wx.BoxSizer(wx.VERTICAL) + panel.SetSizer(page_sizer) else: - page_sizer.DeleteWindows() - - # Creating sizer for page - sizer = wx.BoxSizer(wx.VERTICAL) - # Window for settings - sizer.Add(self.fill_sc_with_config(panel, category_config, category_item), 1, wx.EXPAND) - # Buttons - button_sizer = wx.BoxSizer(wx.HORIZONTAL) - for button_name in ['apply_button', 'cancel_button']: - button_sizer.Add(create_button(MODULE_KEY.join([setting_category, category_item, button_name]), - self.button_clicked), 0, wx.ALIGN_RIGHT) - sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT) - page_sizer.Add(sizer, 1, wx.EXPAND) + children = page_sizer.GetChildren() + for child in children: + window = child.GetWindow() if child.GetWindow() else child.GetSizer() + page_sizer.Hide(window) + + if category_item in self.sizer_list: + sizer_index = self.sizer_list.index(category_item) + page_sizer.Show(sizer_index) + else: + # Creating sizer for page + sizer = wx.BoxSizer(wx.VERTICAL) + # Window for settings + sizer.Add(self.fill_sc_with_config(panel, category_config, category_item), 1, wx.EXPAND) + # Buttons + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + for button_name in ['apply_button', 'cancel_button']: + button_sizer.Add(create_button(MODULE_KEY.join([setting_category, category_item, button_name]), + self.button_clicked), 0, wx.ALIGN_RIGHT) + sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT) + page_sizer.Add(sizer, 1, wx.EXPAND) + self.sizer_list.append(category_item) page_sizer.Layout() - panel.SetSizer(page_sizer) panel.Layout() def fill_sc_with_config(self, panel, category_config, category_item): @@ -532,19 +553,6 @@ def button_clicked(self, event): self.on_close(event) event.Skip() - def tree_ctrl_changed(self, event): - self.settings_saved = False - tree_ctrl = event.EventObject # type: wx.TreeCtrl - selection = tree_ctrl.GetFocusedItem() - selection_text = tree_ctrl.GetItemData(selection).GetData() - key_list = selection_text.split(MODULE_KEY) - - # Drawing page - self.fill_page_with_content(self.content_page, key_list[1], key_list[-1], - self.main_class.loaded_modules[key_list[-1]]) - - event.Skip() - def save_settings(self, module): module_settings = self.main_class.loaded_modules.get(module, {}) non_dynamic = module_settings.get('gui', {}).get('non_dynamic', []) From 9532f3fdd27b43cc923eb75652d217c64b49e608 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Tue, 1 Nov 2016 22:59:05 +0200 Subject: [PATCH 04/40] LC-103 Apply button should be grayed out before any changes --- gui.py | 125 +++++++++++++++++++++++++++++------ http/czt/js/socket.js | 73 ++++++++++---------- main.py | 8 +-- modules/chat/twitch.py | 2 +- modules/helper/parser.py | 17 +++-- modules/helper/system.py | 5 +- modules/messaging/webchat.py | 8 +-- 7 files changed, 163 insertions(+), 75 deletions(-) diff --git a/gui.py b/gui.py index 9ebf636..7587935 100644 --- a/gui.py +++ b/gui.py @@ -9,6 +9,7 @@ from ConfigParser import ConfigParser from cefpython3.wx import chromectrl from modules.helper.system import MODULE_KEY, translate_key +from modules.helper.parser import return_type # ToDO: Support customization of borders/spacings # ToDO: Exit by cancel button @@ -147,6 +148,8 @@ def __init__(self, *args, **kwargs): self.tree_ctrl = None self.content_page = None self.sizer_list = [] + self.changes = {} + self.buttons = {} # Setting up the window self.SetBackgroundColour('cream') @@ -202,12 +205,58 @@ def on_listbox_change(self, event): item_key = IDS[event.GetId()].split(MODULE_KEY) show_description = self.main_class.loaded_modules[item_key[0]]['gui'][item_key[1]].get('description', False) + if isinstance(item_object, KeyListBox): + self.on_change(IDS[event.GetId()], selection, section=True) + if show_description: item_id_key = MODULE_KEY.join(item_key[:-1]) descr_static_text = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([item_id_key, 'descr_explain']))) descr_static_text.SetLabel(description) descr_static_text.Wrap(descr_static_text.GetSize()[0]) + def on_checklist_box_change(self, event): + window = event.EventObject + item_ids = window.GetChecked() + items_values = [window.get_key_from_index(item_id) for item_id in item_ids] + self.on_change(IDS[event.GetId()], items_values, section=True) + + def on_change(self, key, value, section=False, listbox=False): + def apply_changes(): + self.changes[key] = value + self.buttons[MODULE_KEY.join(['settings', 'apply_button'])].Enable() + + def clear_changes(): + if key in self.changes: + self.changes.pop(key) + if not self.changes: + self.buttons[MODULE_KEY.join(['settings', 'apply_button'])].Disable() + split_keys = key.split(MODULE_KEY) + config = self.main_class.loaded_modules[split_keys[0]]['config'] + if section: + if isinstance(value, list): + if set(config[split_keys[1]].keys()) != set(value): + apply_changes() + else: + clear_changes() + else: + if config[split_keys[1]].decode('utf-8') != return_type(value): + apply_changes() + else: + clear_changes() + elif listbox: + apply_changes() + else: + if isinstance(value, bool): + if config[split_keys[1]][split_keys[2]] != value: + apply_changes() + else: + clear_changes() + else: + if config[split_keys[1]][split_keys[2]].decode('utf-8') != return_type(value): + apply_changes() + else: + clear_changes() + def on_tree_ctrl_changed(self, event): self.settings_saved = False tree_ctrl = event.EventObject # type: wx.TreeCtrl @@ -221,15 +270,29 @@ def on_tree_ctrl_changed(self, event): event.Skip() + def on_textctrl(self, event): + text_ctrl = event.EventObject + self.on_change(IDS[event.GetId()], text_ctrl.GetValue()) + event.Skip() + + def on_spinctrl(self, event): + spin_ctrl = event.EventObject + self.on_change(IDS[event.GetId()], spin_ctrl.GetValue()) + event.Skip() + + def on_check_change(self, event): + check_ctrl = event.EventObject + self.on_change(IDS[event.GetId()], check_ctrl.IsChecked()) + event.Skip() + def create_layout(self): self.main_grid = wx.BoxSizer(wx.HORIZONTAL) - tree_ctrl_size = wx.Size(220, -1) style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.TR_TWIST_BUTTONS | wx.TR_NO_LINES # style = wx.TR_HAS_BUTTONS | wx.TR_SINGLE | wx.TR_HIDE_ROOT tree_ctrl_id = id_renew('settings.tree', update=True) tree_ctrl = wx.TreeCtrl(self, id=tree_ctrl_id, style=style) - tree_ctrl.SetMinSize(tree_ctrl_size) + tree_ctrl.SetQuickBestSize(False) root_key = MODULE_KEY.join(['settings', 'tree', 'root']) root_node = tree_ctrl.AddRoot(translate_key(root_key)) for item, value in self.categories.iteritems(): @@ -245,6 +308,7 @@ def create_layout(self): f_item_data.SetData(f_item_key) tree_ctrl.AppendItem(item_node, translate_key(f_item), data=f_item_data) tree_ctrl.ExpandAll() + tree_ctrl.SetMinSize(wx.Size(tree_ctrl.GetSize()[0] + 70, -1)) self.tree_ctrl = tree_ctrl self.Bind(wx.EVT_TREE_SEL_CHANGED, self.on_tree_ctrl_changed, id=tree_ctrl_id) @@ -259,9 +323,12 @@ def create_layout(self): tree_ctrl.SelectItem(tree_ctrl.GetFirstChild(root_node)[0]) def fill_page_with_content(self, panel, setting_category, category_item, category_config): - def create_button(button_key, function): + def create_button(button_key, function, enabled=True): button_id = id_renew(button_key, update=True) c_button = wx.Button(panel, id=button_id, label=translate_key(button_key)) + if not enabled: + c_button.Disable() + self.buttons[button_key] = c_button self.Bind(wx.EVT_BUTTON, function, id=button_id) return c_button @@ -269,13 +336,23 @@ def create_button(button_key, function): if not page_sizer: page_sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(page_sizer) + # Buttons + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.Add(create_button(MODULE_KEY.join(['settings', 'apply_button']), + self.button_clicked, enabled=False), 0, wx.ALIGN_RIGHT) + button_sizer.Add(create_button(MODULE_KEY.join(['settings', 'cancel_button']), + self.button_clicked), 0, wx.ALIGN_RIGHT) + page_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 4) else: children = page_sizer.GetChildren() - for child in children: + children_len = len(children) + for index, child in enumerate(children): window = child.GetWindow() if child.GetWindow() else child.GetSizer() - page_sizer.Hide(window) + if index < children_len - 1: + page_sizer.Hide(window) if category_item in self.sizer_list: + # +1 because we have button sizer sizer_index = self.sizer_list.index(category_item) page_sizer.Show(sizer_index) else: @@ -283,14 +360,8 @@ def create_button(button_key, function): sizer = wx.BoxSizer(wx.VERTICAL) # Window for settings sizer.Add(self.fill_sc_with_config(panel, category_config, category_item), 1, wx.EXPAND) - # Buttons - button_sizer = wx.BoxSizer(wx.HORIZONTAL) - for button_name in ['apply_button', 'cancel_button']: - button_sizer.Add(create_button(MODULE_KEY.join([setting_category, category_item, button_name]), - self.button_clicked), 0, wx.ALIGN_RIGHT) - sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT) - page_sizer.Add(sizer, 1, wx.EXPAND) - self.sizer_list.append(category_item) + page_sizer.Prepend(sizer, 1, wx.EXPAND) + self.sizer_list.insert(0, category_item) page_sizer.Layout() panel.Layout() @@ -424,10 +495,12 @@ def create_choose(self, parent, view, key, section, section_gui): if is_single: item_list_box = KeyListBox(parent, id=id_renew(item_key, update=True), keys=list_items, choices=translated_items if translated_items else list_items, style=style) + item_list_box.Bind(wx.EVT_LISTBOX, self.on_listbox_change) else: item_list_box = KeyCheckListBox(parent, id=id_renew(item_key, update=True), keys=list_items, choices=translated_items if translated_items else list_items) - self.Bind(wx.EVT_LISTBOX, self.on_listbox_change, item_list_box) + item_list_box.Bind(wx.EVT_LISTBOX, self.on_listbox_change) + item_list_box.Bind(wx.EVT_CHECKLISTBOX, self.on_checklist_box_change) section_for = section if not is_single else {section: None} if is_single: @@ -471,7 +544,9 @@ def create_spin(self, parent, view, key, section, section_gui, section_item=Fals key = key if section_item else MODULE_KEY.join([key, 'spin']) value = short_key if section_item else section item_box = wx.SpinCtrl(parent, id=id_renew(key, update=True), min=section_gui['min'], max=section_gui['max'], - initial=value) + initial=int(value)) + item_box.Bind(wx.EVT_SPINCTRL, self.on_spinctrl) + item_box.Bind(wx.EVT_TEXT, self.on_spinctrl) return item_text, item_box def create_item(self, parent, view, key, section, section_gui): @@ -512,11 +587,13 @@ def create_item(self, parent, view, key, section, section_gui): item_box = wx.CheckBox(parent, id=id_renew(item_name, update=True), label=translate_key(item_name), style=style) item_box.SetValue(value) + item_box.Bind(wx.EVT_CHECKBOX, self.on_check_change) flex_grid.Add(item_box, 0, wx.ALIGN_LEFT) flex_grid.AddSpacer(wx.Size(0, 0)) else: # TextCtrl item_box = wx.TextCtrl(parent, id=id_renew(item_name, update=True), value=str(value).decode('utf-8')) + item_box.Bind(wx.EVT_TEXT, self.on_textctrl) item_text = wx.StaticText(parent, label=translate_key(item_name), style=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_HORIZONTAL) flex_grid.Add(item_text) @@ -530,9 +607,9 @@ def button_clicked(self, event): keys = IDS[button_id].split(MODULE_KEY) if keys[-1] in ['list_add', 'list_remove']: self.list_operation(MODULE_KEY.join(keys[:-1]), action=keys[-1]) + self.on_change(IDS[button_id], 'listbox_changed', listbox=True) elif keys[-1] == 'apply_button': - module_name = MODULE_KEY.join(keys[1:-1]) - if self.save_settings(module_name): + if self.save_settings(): log.debug('Got non-dynamic changes') dialog = wx.MessageDialog(self, message=translate_key(MODULE_KEY.join(['main', 'save', 'non_dynamic'])), @@ -545,15 +622,19 @@ def button_clicked(self, event): self.on_exit(event) else: event.StopPropagation() - module_class = self.main_class.loaded_modules[module_name].get('class') - if module_class: - module_class.apply_settings() self.settings_saved = True elif keys[-1] == 'cancel_button': self.on_close(event) event.Skip() - def save_settings(self, module): + def save_settings(self): + dynamic_check = False + for module in self.main_class.loaded_modules.keys(): + if not self.save_module(module): + dynamic_check = True + return dynamic_check + + def save_module(self, module): module_settings = self.main_class.loaded_modules.get(module, {}) non_dynamic = module_settings.get('gui', {}).get('non_dynamic', []) module_config = module_settings.get('config') @@ -653,6 +734,8 @@ def save_settings(self, module): module_config[section][item_name] = item_value with open(module_settings['file'], 'w') as config_file: parser.write(config_file) + if 'class' in module_settings: + module_settings['class'].apply_settings() return non_dynamic_check def select_cell(self, event): diff --git a/http/czt/js/socket.js b/http/czt/js/socket.js index de86c13..9b3ec86 100644 --- a/http/czt/js/socket.js +++ b/http/czt/js/socket.js @@ -1,48 +1,54 @@ var MAX_MESSAGES = 70; -var find_location = window.location.href; -var RegExp = /:(\d+)/; -var find_list = RegExp.exec(find_location.toString()); -var find_port = find_list[1]; -var ws_url = "ws://127.0.0.1:".concat(find_port, "/ws"); +var ws_url = "ws://" + window.location.host + "/ws"; // Chat settings var timeout = 0; var loadHistory = true; +var socket; +var chatMessages; -var socket = new WebSocket(ws_url); +function connectToWS() { + console.log("Connecting to WS") + socket = new WebSocket(ws_url); -var chatMessages; -socket.onopen = function() { - console.log("Socket connected") - chatMessages = document.getElementById('ChatContainer'); -}; + socket.onopen = function() { + console.log("Socket connected") + chatMessages = document.getElementById('ChatContainer'); + }; -socket.onclose = function(event) { - if (event.wasClean) { - console.log("Socket closed cleanly") - } - else { - console.log("Socket closed not cleanly") - } -}; + socket.onclose = function(event) { + if (event.wasClean) { + console.log("Socket closed cleanly"); + } + else { + console.log("Socket closed not cleanly"); + } + setTimeout(function() { + connectToWS(ws_url); + }, 1000) + }; -socket.onmessage = function(event) { - var incomingMessage = JSON.parse(event.data); - if(incomingMessage.hasOwnProperty('command')) { - runCommand(incomingMessage); - } - else { - if (loadHistory) { - showMessage(incomingMessage); + socket.onmessage = function(event) { + var incomingMessage = JSON.parse(event.data); + if(incomingMessage.hasOwnProperty('command')) { + runCommand(incomingMessage); } - else if (!incomingMessage.hasOwnProperty('history')) { - showMessage(incomingMessage); + else { + if (loadHistory) { + showMessage(incomingMessage); + } + else if (!incomingMessage.hasOwnProperty('history')) { + showMessage(incomingMessage); + } } - } -}; + }; -socket.onerror = function(error) { -}; + socket.onerror = function(error) { + console.log("Error") + }; +} + +connectToWS(); twitch_processEmoticons = function(message, emotes) { if (!emotes) { @@ -124,7 +130,6 @@ function updateMessages() { removeMessage(element); } - function showMessage(message) { var badge_colors = 1; diff --git a/main.py b/main.py index 0c9152c..bdf9f58 100644 --- a/main.py +++ b/main.py @@ -100,8 +100,8 @@ def close(): main_config_dict = OrderedDict() main_config_dict['gui_information'] = OrderedDict() main_config_dict['gui_information']['category'] = 'main' - main_config_dict['gui_information']['width'] = 450 - main_config_dict['gui_information']['height'] = 500 + main_config_dict['gui_information']['width'] = '450' + main_config_dict['gui_information']['height'] = '500' main_config_dict['gui'] = OrderedDict() main_config_dict['gui']['show_hidden'] = False main_config_dict['gui']['gui'] = True @@ -132,8 +132,8 @@ def close(): gui_settings['on_top'] = main_config_dict[GUI_TAG].get('on_top') gui_settings['language'] = main_config_dict.get('language') gui_settings['show_hidden'] = main_config_dict[GUI_TAG].get('show_hidden') - gui_settings['size'] = (main_config_dict['gui_information'].get('width'), - main_config_dict['gui_information'].get('height')) + gui_settings['size'] = (int(main_config_dict['gui_information'].get('width')), + int(main_config_dict['gui_information'].get('height'))) # Checking updates log.info("Checking for updates") diff --git a/modules/chat/twitch.py b/modules/chat/twitch.py index a162741..9f6aed9 100644 --- a/modules/chat/twitch.py +++ b/modules/chat/twitch.py @@ -339,7 +339,7 @@ def __init__(self, queue, python_folder, **kwargs): self.queue = queue self.host = CONF_DICT['config']['host'] - self.port = CONF_DICT['config']['port'] + self.port = int(CONF_DICT['config']['port']) self.channel = CONF_DICT['config']['channel'] self.bttv = CONF_DICT['config']['bttv'] diff --git a/modules/helper/parser.py b/modules/helper/parser.py index c1e2187..9615caa 100644 --- a/modules/helper/parser.py +++ b/modules/helper/parser.py @@ -33,15 +33,14 @@ def self_heal(conf_file, heal_dict): def return_type(item): if item: - try: - if isinstance(item, bool): - return item - return int(item) - except: - if item.lower() == 'true': - return True - elif item.lower() == 'false': - return False + if isinstance(item, bool): + return item + elif isinstance(item, int): + return str(item) + elif item.lower() == 'true': + return True + elif item.lower() == 'false': + return False return item diff --git a/modules/helper/system.py b/modules/helper/system.py index 92f27ce..109e78f 100644 --- a/modules/helper/system.py +++ b/modules/helper/system.py @@ -61,8 +61,9 @@ def load_language(language_folder): def find_key_translation(item): translation = TRANSLATIONS.get(item) if translation is None: - if len(item.split(MODULE_KEY)) > 2: - wildcard_item = MODULE_KEY.join([split for split in item.split(MODULE_KEY) if split != '*'][1:]) + split_item = [f_item for f_item in item.split(MODULE_KEY) if f_item != '*'] + if len(split_item) > 1: + wildcard_item = MODULE_KEY.join(split_item[1:]) return find_key_translation('*{0}{1}'.format(MODULE_KEY, wildcard_item)) else: return item diff --git a/modules/messaging/webchat.py b/modules/messaging/webchat.py index 66f91b8..b8272e8 100644 --- a/modules/messaging/webchat.py +++ b/modules/messaging/webchat.py @@ -47,8 +47,8 @@ def __init__(self, ws, history): def run(self): sleep(0.1) - for item in self.history: - if item: + if self.ws.stream: + for item in self.history: self.ws.send(json.dumps(item)) @@ -112,7 +112,7 @@ def __init__(self, http_folder, settings): self.http_folder = http_folder self.settings = settings - @cherrypy.expose() + @cherrypy.expose def style_css(self): with open(os.path.join(self.http_folder, 'css', 'style.css'), 'r') as css: return Template(css.read()).render(**self.settings) @@ -170,7 +170,7 @@ def run(self): 'tools.staticdir.dir': os.path.join(http_folder, 'img')}} css_config = { - '/': {} + '/': {'tools.response_headers.headers': [('Content-Type', 'text/plain')]} } cherrypy.tree.mount(HttpRoot(http_folder), '', config) From cf08ec0e442a6c8ca6fb15aecf06cf1c957340b4 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Wed, 2 Nov 2016 21:54:58 +0200 Subject: [PATCH 05/40] LC-147 rewrite setting window button logic --- gui.py | 42 +++++++++------------------------------- translations/en/main.key | 1 + translations/ru/main.key | 1 + 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/gui.py b/gui.py index 7587935..aec8b71 100644 --- a/gui.py +++ b/gui.py @@ -19,7 +19,7 @@ SECTION_GUI_TAG = '__gui' SKIP_TAGS = [INFORMATION_TAG] SKIP_TXT_CONTROLS = ['list_input', 'list_input2'] -SKIP_BUTTONS = ['list_add', 'list_remove', 'apply_button', 'cancel_button'] +SKIP_BUTTONS = ['list_add', 'list_remove', 'apply_button', 'cancel_button', 'ok_button'] ITEM_SPACING_VERT = 6 ITEM_SPACING_HORZ = 30 @@ -156,7 +156,7 @@ def __init__(self, *args, **kwargs): self.show_hidden = self.main_class.gui_settings.get('show_hidden') # Setting up events - self.Bind(wx.EVT_CLOSE, self.on_close_save) + self.Bind(wx.EVT_CLOSE, self.on_close) styles = wx.DEFAULT_FRAME_STYLE if wx.STAY_ON_TOP & self.main_class.GetWindowStyle() == wx.STAY_ON_TOP: @@ -171,31 +171,7 @@ def on_exit(self, event): self.Destroy() def on_close(self, event): - dialog = wx.MessageDialog(self, message=translate_key(MODULE_KEY.join(['main', 'quit'])), - caption="Caption", - style=wx.YES_NO | wx.CANCEL, - pos=wx.DefaultPosition) - response = dialog.ShowModal() - - if response == wx.ID_YES: - self.on_exit(event) - else: - event.StopPropagation() - - def on_close_save(self, event): - if not self.settings_saved: - dialog = wx.MessageDialog(self, message=translate_key(MODULE_KEY.join(['main', 'quit', 'nosave'])), - caption="Caption", - style=wx.YES_NO, - pos=wx.DefaultPosition) - response = dialog.ShowModal() - - if response == wx.ID_YES: - self.on_exit(event) - else: - event.StopPropagation() - else: - self.on_exit(event) + self.on_exit(event) def on_listbox_change(self, event): item_object = event.EventObject @@ -338,6 +314,8 @@ def create_button(button_key, function, enabled=True): panel.SetSizer(page_sizer) # Buttons button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.Add(create_button(MODULE_KEY.join(['settings', 'ok_button']), + self.button_clicked), 0, wx.ALIGN_RIGHT) button_sizer.Add(create_button(MODULE_KEY.join(['settings', 'apply_button']), self.button_clicked, enabled=False), 0, wx.ALIGN_RIGHT) button_sizer.Add(create_button(MODULE_KEY.join(['settings', 'cancel_button']), @@ -608,7 +586,7 @@ def button_clicked(self, event): if keys[-1] in ['list_add', 'list_remove']: self.list_operation(MODULE_KEY.join(keys[:-1]), action=keys[-1]) self.on_change(IDS[button_id], 'listbox_changed', listbox=True) - elif keys[-1] == 'apply_button': + elif keys[-1] in ['ok_button', 'apply_button']: if self.save_settings(): log.debug('Got non-dynamic changes') dialog = wx.MessageDialog(self, @@ -616,15 +594,13 @@ def button_clicked(self, event): caption="Caption", style=wx.OK_DEFAULT, pos=wx.DefaultPosition) - response = dialog.ShowModal() - - if response == wx.ID_YES: + dialog.ShowModal() + if keys[-1] == 'ok_button': self.on_exit(event) - else: - event.StopPropagation() self.settings_saved = True elif keys[-1] == 'cancel_button': self.on_close(event) + event.Skip() def save_settings(self): diff --git a/translations/en/main.key b/translations/en/main.key index 5f3ff33..0384c3e 100644 --- a/translations/en/main.key +++ b/translations/en/main.key @@ -2,6 +2,7 @@ menu.settings = Settings menu.reload = Reload WebChat *.apply_button = Apply *.cancel_button = Cancel +*.ok_button = OK *.list_add = Add *.list_remove = Remove *.descr_explain = Click on an item to view description of the item diff --git a/translations/ru/main.key b/translations/ru/main.key index 39c5fc2..28b38df 100644 --- a/translations/ru/main.key +++ b/translations/ru/main.key @@ -2,6 +2,7 @@ menu.settings = Настройки menu.reload = Перезагрузить Чат *.apply_button = Принять *.cancel_button = Отменить +*.ok_button = ОК *.list_add = Добавить *.list_remove = Удалить *.descr_explain = Выберите вещь, описание которой вы хотите прочитать. From d3ba4b7ac21bbb60f7dddc99fd04680168a59c11 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Wed, 2 Nov 2016 22:42:03 +0200 Subject: [PATCH 06/40] LC-146 split remove to different calls --- http/czt/js/socket.js | 21 ++++++++++----------- modules/chat/goodgame.py | 6 ++++-- modules/chat/sc2tv.py | 3 ++- modules/chat/twitch.py | 4 +++- modules/helper/system.py | 3 ++- modules/messaging/blacklist.py | 3 ++- modules/messaging/c2b.py | 3 ++- modules/messaging/df.py | 3 ++- modules/messaging/levels.py | 5 ++--- modules/messaging/logger.py | 3 ++- modules/messaging/mentions.py | 3 ++- modules/messaging/webchat.py | 9 +++++---- 12 files changed, 38 insertions(+), 28 deletions(-) diff --git a/http/czt/js/socket.js b/http/czt/js/socket.js index 9b3ec86..b1ae418 100644 --- a/http/czt/js/socket.js +++ b/http/czt/js/socket.js @@ -276,19 +276,19 @@ function showMessage(message) { } function runCommand(message) { - if(message.command == 'reload'){ - window.location.reload(); - } - else if(message.command == 'remove_msg') { - if(message.ids) { + switch (message.command) { + case 'reload': + window.location.reload(); + break + case 'remove_by_id': message.ids.forEach(function(message_item) { item = document.getElementById(message_item) if (item) { item.parentNode.removeChild(item) } }) - } - else if(message.user) { + break + case 'remove_by_user': message.user.forEach(function(user) { var children = chatMessages.childNodes var node_length = children.length - 1 @@ -298,10 +298,9 @@ function runCommand(message) { } } }) - } - } - else { - console.log("Got unknown command " + message.command) + break + default: + console.log("Got unknown command") } } diff --git a/modules/chat/goodgame.py b/modules/chat/goodgame.py index 41e316a..e462e8c 100644 --- a/modules/chat/goodgame.py +++ b/modules/chat/goodgame.py @@ -57,7 +57,8 @@ def process_message(self, msg): 'source_icon': SOURCE_ICON, 'user': msg['data']['user_name'], 'text': msg['data']['text'], - 'emotes': []} + 'emotes': [], + 'type': 'message'} smiles_array = re.findall(self.smile_regex, comp['text']) for smile in smiles_array: @@ -108,7 +109,8 @@ def process_message(self, msg): msg['data']['user_name'])) elif msg['type'] == 'remove_message': remove_id = ID_PREFIX.format(msg['data']['message_id']) - self.message_queue.put({'command': 'remove_msg', + self.message_queue.put({'type': 'command', + 'command': 'remove_by_id', 'ids': [remove_id]}) elif msg['type'] == 'user_ban': if msg['data']['duration']: diff --git a/modules/chat/sc2tv.py b/modules/chat/sc2tv.py index 0d75f76..a59864a 100644 --- a/modules/chat/sc2tv.py +++ b/modules/chat/sc2tv.py @@ -130,7 +130,8 @@ def received_message(self, mes): 'source_icon': SOURCE_ICON, 'user': message['from']['name'], 'text': message['text'], - 'emotes': []} + 'emotes': [], + 'type': 'message'} if message['to'] is not None: comp['to'] = message['to']['name'] if comp['to'] == self.channel_name: diff --git a/modules/chat/twitch.py b/modules/chat/twitch.py index 9f6aed9..a78390b 100644 --- a/modules/chat/twitch.py +++ b/modules/chat/twitch.py @@ -75,6 +75,7 @@ def process_message(self, msg): 'emotes': [], 'bttv_emotes': [], 'user': 'TwitchSystem', + 'type': 'message', 'msg_type': msg.type} for tag in msg.tags: tag_value, tag_key = tag.values() @@ -126,7 +127,8 @@ def process_message(self, msg): comp['pm'] = True self.message_queue.put(comp) elif msg.type in ['clearchat']: - self.message_queue.put({'command': 'remove_msg', + self.message_queue.put({'type': 'command', + 'command': 'remove_by_user', 'user': msg.arguments}) diff --git a/modules/helper/system.py b/modules/helper/system.py index 109e78f..bab5068 100644 --- a/modules/helper/system.py +++ b/modules/helper/system.py @@ -10,6 +10,7 @@ SOURCE_USER = 'System' SOURCE_ICON = '/img/sources/lalka_cup.png' +IGNORED_TYPES = ['command', 'system_message'] TRANSLATIONS = {} SPLIT_TRANSLATION = '=' MODULE_KEY = '.' @@ -23,7 +24,7 @@ def system_message(message, queue, source=SOURCE, icon=SOURCE_ICON, from_user=SO 'source_icon': icon, 'user': from_user, 'text': message, - 'system_msg': True}) + 'type': 'system_message'}) class ModuleLoadException(Exception): diff --git a/modules/messaging/blacklist.py b/modules/messaging/blacklist.py index cce810e..875f898 100644 --- a/modules/messaging/blacklist.py +++ b/modules/messaging/blacklist.py @@ -5,6 +5,7 @@ from collections import OrderedDict from modules.helper.parser import self_heal from modules.helper.modules import MessagingModule +from modules.helper.system import IGNORED_TYPES DEFAULT_PRIORITY = 30 @@ -67,7 +68,7 @@ def __init__(self, conf_folder, **kwargs): def process_message(self, message, queue, **kwargs): if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message user = self.blacklist_user_handler(message) # True = Hide, False = Del, None = Do Nothing diff --git a/modules/messaging/c2b.py b/modules/messaging/c2b.py index 451322f..f0a4e7d 100644 --- a/modules/messaging/c2b.py +++ b/modules/messaging/c2b.py @@ -7,6 +7,7 @@ from collections import OrderedDict from modules.helper.parser import self_heal from modules.helper.modules import MessagingModule +from modules.helper.system import IGNORED_TYPES DEFAULT_PRIORITY = 10 log = logging.getLogger('c2b') @@ -68,7 +69,7 @@ def process_message(self, message, queue, **kwargs): # Replacing the message if needed. # Please do the needful if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message for replace in self.f_items: if replace['filter'] in message['text']: diff --git a/modules/messaging/df.py b/modules/messaging/df.py index 8f05ad8..5b6be8c 100644 --- a/modules/messaging/df.py +++ b/modules/messaging/df.py @@ -6,6 +6,7 @@ from modules.helper.parser import self_heal from modules.helper.modules import MessagingModule +from modules.helper.system import IGNORED_TYPES class df(MessagingModule): @@ -61,7 +62,7 @@ def write_to_file(self, message): def process_message(self, message, queue, **kwargs): if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message for regexp in self.prof: if re.search(regexp[1], message['text']): diff --git a/modules/messaging/levels.py b/modules/messaging/levels.py index dd3972c..a335568 100644 --- a/modules/messaging/levels.py +++ b/modules/messaging/levels.py @@ -10,7 +10,7 @@ import datetime from modules.helper.parser import self_heal -from modules.helper.system import system_message, ModuleLoadException +from modules.helper.system import system_message, ModuleLoadException, IGNORED_TYPES from modules.helper.modules import MessagingModule log = logging.getLogger('levels') @@ -67,7 +67,6 @@ def __init__(self, conf_folder, **kwargs): self.threshold_users = None def load_module(self, *args, **kwargs): - main_settings = kwargs.get('main_settings') loaded_modules = kwargs.get('loaded_modules') if 'webchat' not in loaded_modules: raise ModuleLoadException("Unable to find webchat module that is needed for level module") @@ -153,7 +152,7 @@ def set_level(self, user, queue): def process_message(self, message, queue, **kwargs): if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message if 'system_msg' not in message or not message['system_msg']: if 'user' in message and message['user'] in self.special_levels: diff --git a/modules/messaging/logger.py b/modules/messaging/logger.py index e438b22..65e7cf6 100644 --- a/modules/messaging/logger.py +++ b/modules/messaging/logger.py @@ -6,6 +6,7 @@ from modules.helper.parser import self_heal from modules.helper.modules import MessagingModule +from modules.helper.system import IGNORED_TYPES DEFAULT_PRIORITY = 20 @@ -50,7 +51,7 @@ def __init__(self, conf_folder, **kwargs): def process_message(self, message, queue, **kwargs): if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message with open('{0}.txt'.format( os.path.join(self.destination, datetime.datetime.now().strftime(self.format))), 'a') as f: diff --git a/modules/messaging/mentions.py b/modules/messaging/mentions.py index 5b46b9a..529731d 100644 --- a/modules/messaging/mentions.py +++ b/modules/messaging/mentions.py @@ -6,6 +6,7 @@ from modules.helper.parser import self_heal from modules.helper.modules import MessagingModule +from modules.helper.system import IGNORED_TYPES class mentions(MessagingModule): @@ -48,7 +49,7 @@ def process_message(self, message, queue, **kwargs): # Replacing the message if needed. # Please do the needful if message: - if 'command' in message: + if message['type'] in IGNORED_TYPES: return message for mention in self.mentions: if re.search(mention, message['text'].lower()): diff --git a/modules/messaging/webchat.py b/modules/messaging/webchat.py index b8272e8..1293c91 100644 --- a/modules/messaging/webchat.py +++ b/modules/messaging/webchat.py @@ -12,13 +12,14 @@ from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool from ws4py.websocket import WebSocket from modules.helper.parser import self_heal -from modules.helper.system import THREADS +from modules.helper.system import THREADS, IGNORED_TYPES from modules.helper.modules import MessagingModule from gui import MODULE_KEY from main import PYTHON_FOLDER, CONF_FOLDER DEFAULT_PRIORITY = 9001 HISTORY_SIZE = 20 +HISTORY_TYPES = ['system_message', 'message'] HTTP_FOLDER = os.path.join(PYTHON_FOLDER, "http") s_queue = Queue.Queue() logging.getLogger('ws4py').setLevel(logging.ERROR) @@ -34,7 +35,7 @@ def run(self): while True: message = s_queue.get() cherrypy.engine.publish('websocket-broadcast', json.dumps(message)) - if 'command' not in message: + if message['type'] in HISTORY_TYPES: cherrypy.engine.publish('add-history', message) @@ -260,7 +261,7 @@ def load_module(self, *args, **kwargs): log.error("Port is already used, please change webchat port") def reload_chat(self): - self.queue.put({'command': 'reload'}) + self.queue.put({'type': 'command', 'command': 'reload'}) def apply_settings(self): self.reload_chat() @@ -275,7 +276,7 @@ def gui_button_press(self, gui_module, event, list_keys): def process_message(self, message, queue, **kwargs): if message: if 'flags' in message: - if message['flags'] == 'hidden': + if 'hidden' in message['flags']: return message s_queue.put(message) return message From 51d4dc05f64fff99ae99320e4b93ce674d5afe86 Mon Sep 17 00:00:00 2001 From: Vladislav Ivanov Date: Thu, 3 Nov 2016 22:16:09 +0200 Subject: [PATCH 07/40] LC-108 Check for changes on save --- .gitignore | 3 + gui.py | 207 +++++++++++------------- http/czt/css/style.css | 115 +++++++------ http/czt/index.html | 50 ++++-- http/czt/js/app.js | 171 ++++++++++++++++++++ http/czt/js/polyfills.min.js | 1 + http/czt/js/socket.js | 306 ----------------------------------- modules/messaging/webchat.py | 6 +- 8 files changed, 373 insertions(+), 486 deletions(-) create mode 100644 http/czt/js/app.js create mode 100644 http/czt/js/polyfills.min.js delete mode 100644 http/czt/js/socket.js diff --git a/.gitignore b/.gitignore index b230d97..bcfff50 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ levels.db* *.spec *.log govno_rutony.py +http/**/js/purify.min.js +http/**/js/vue.min.js + diff --git a/gui.py b/gui.py index aec8b71..642b031 100644 --- a/gui.py +++ b/gui.py @@ -28,8 +28,6 @@ def get_id_from_name(name, error=False): for item, item_id in IDS.iteritems(): if item_id == name: return item - if error: - raise ReferenceError return None @@ -182,7 +180,7 @@ def on_listbox_change(self, event): show_description = self.main_class.loaded_modules[item_key[0]]['gui'][item_key[1]].get('description', False) if isinstance(item_object, KeyListBox): - self.on_change(IDS[event.GetId()], selection, section=True) + self.on_change(IDS[event.GetId()], selection, item_type='listbox', section=True) if show_description: item_id_key = MODULE_KEY.join(item_key[:-1]) @@ -194,11 +192,14 @@ def on_checklist_box_change(self, event): window = event.EventObject item_ids = window.GetChecked() items_values = [window.get_key_from_index(item_id) for item_id in item_ids] - self.on_change(IDS[event.GetId()], items_values, section=True) + self.on_change(IDS[event.GetId()], items_values, item_type='listbox_check', section=True) + + def on_change(self, key, value, item_type=None, section=False): + def compare_2d_lists(list1, list2): + return not set(map(tuple, list1)) ^ set(map(tuple, list2)) - def on_change(self, key, value, section=False, listbox=False): def apply_changes(): - self.changes[key] = value + self.changes[key] = {'value': value, 'type': item_type} self.buttons[MODULE_KEY.join(['settings', 'apply_button'])].Enable() def clear_changes(): @@ -219,8 +220,18 @@ def clear_changes(): apply_changes() else: clear_changes() - elif listbox: - apply_changes() + elif item_type == 'gridbox': + main_tuple = [] + for item, i_value in self.main_class.loaded_modules[split_keys[0]]['config'][split_keys[1]].iteritems(): + if i_value: + main_tuple.append((item, i_value)) + else: + main_tuple.append((item,)) + + if compare_2d_lists(value, main_tuple): + clear_changes() + else: + apply_changes() else: if isinstance(value, bool): if config[split_keys[1]][split_keys[2]] != value: @@ -248,17 +259,22 @@ def on_tree_ctrl_changed(self, event): def on_textctrl(self, event): text_ctrl = event.EventObject - self.on_change(IDS[event.GetId()], text_ctrl.GetValue()) + self.on_change(IDS[event.GetId()], text_ctrl.GetValue(), item_type='textctrl') event.Skip() def on_spinctrl(self, event): spin_ctrl = event.EventObject - self.on_change(IDS[event.GetId()], spin_ctrl.GetValue()) + self.on_change(IDS[event.GetId()], str(spin_ctrl.GetValue()), item_type='spinctrl') + event.Skip() + + def on_dropdown(self, event): + drop_ctrl = event.EventObject + self.on_change(IDS[event.GetId()], drop_ctrl.GetString(drop_ctrl.GetCurrentSelection()), item_type='dropctrl') event.Skip() def on_check_change(self, event): check_ctrl = event.EventObject - self.on_change(IDS[event.GetId()], check_ctrl.IsChecked()) + self.on_change(IDS[event.GetId()], check_ctrl.IsChecked(), item_type='checkbox') event.Skip() def create_layout(self): @@ -482,7 +498,11 @@ def create_choose(self, parent, view, key, section, section_gui): section_for = section if not is_single else {section: None} if is_single: - [item_list_box.SetSelection(list_items.index(item)) for item, value in section_for.items()] + item, value = section_for.items()[0] + if item not in item_list_box.GetItems(): + item_list_box.SetSelection(0) + else: + item_list_box.SetSelection(list_items.index(item)) else: check_items = [list_items.index(item) for item, value in section_for.items()] item_list_box.SetChecked(check_items) @@ -512,6 +532,7 @@ def create_dropdown(self, parent, view, key, section, section_gui, section_item= key = key if section_item else MODULE_KEY.join([key, 'dropdown']) item_box = KeyChoice(parent, id=id_renew(key, update=True), keys=choices, choices=choices) + item_box.Bind(wx.EVT_CHOICE, self.on_dropdown) item_value = section[short_key] if section_item else section item_box.SetSelection(choices.index(item_value)) return item_text, item_box @@ -585,7 +606,6 @@ def button_clicked(self, event): keys = IDS[button_id].split(MODULE_KEY) if keys[-1] in ['list_add', 'list_remove']: self.list_operation(MODULE_KEY.join(keys[:-1]), action=keys[-1]) - self.on_change(IDS[button_id], 'listbox_changed', listbox=True) elif keys[-1] in ['ok_button', 'apply_button']: if self.save_settings(): log.debug('Got non-dynamic changes') @@ -595,35 +615,39 @@ def button_clicked(self, event): style=wx.OK_DEFAULT, pos=wx.DefaultPosition) dialog.ShowModal() - if keys[-1] == 'ok_button': - self.on_exit(event) + if keys[-1] == 'ok_button': + self.on_exit(event) self.settings_saved = True elif keys[-1] == 'cancel_button': self.on_close(event) - event.Skip() def save_settings(self): dynamic_check = False for module in self.main_class.loaded_modules.keys(): - if not self.save_module(module): + change_list = {} + for item, change in self.changes.iteritems(): + if module == item.split(MODULE_KEY)[0]: + change_list[item] = change + for key in change_list.keys(): + self.changes.pop(key) + + if self.save_module(module, change_list): dynamic_check = True + self.buttons[MODULE_KEY.join(['settings', 'apply_button'])].Disable() return dynamic_check - def save_module(self, module): - module_settings = self.main_class.loaded_modules.get(module, {}) - non_dynamic = module_settings.get('gui', {}).get('non_dynamic', []) - module_config = module_settings.get('config') + def save_module(self, module, changed_items): non_dynamic_check = False - if module_settings: + if changed_items: + module_settings = self.main_class.loaded_modules.get(module, {}) + non_dynamic = module_settings.get('gui', {}).get('non_dynamic', []) + module_config = module_settings.get('config') parser = module_settings['parser'] # type: ConfigParser - items = get_list_of_ids_from_module_name(module, return_tuple=True) - for item, name in items: - module_name, section, item_name = name.split(MODULE_KEY) - if not parser.has_section(section): - continue - # Check for non-dynamic items + for item, change in changed_items.iteritems(): + item_split = item.split(MODULE_KEY) + section, item_name = item_split[1:] if len(item_split) > 2 else (item_split[1], None) for d_item in non_dynamic: if section in d_item: if MODULE_KEY.join([section, '*']) in d_item: @@ -632,82 +656,36 @@ def save_module(self, module): elif MODULE_KEY.join([section, item_name]) in d_item: non_dynamic_check = True break - # Saving - wx_window = wx.FindWindowById(item) - if isinstance(wx_window, wx.CheckBox): - if name == MODULE_KEY.join(['main', 'gui', 'show_hidden']): - self.show_hidden = wx_window.IsChecked() - parser.set(section, item_name, wx_window.IsChecked()) - module_config[section][item_name] = wx_window.IsChecked() - elif isinstance(wx_window, wx.TextCtrl): - if item_name not in SKIP_TXT_CONTROLS: - parser.set(section, item_name, wx_window.GetValue().encode('utf-8').strip()) - module_config[section][item_name] = wx_window.GetValue().encode('utf-8').strip() - elif isinstance(wx_window, wx.grid.Grid): - col_count = wx_window.GetNumberCols() - row_count = wx_window.GetNumberRows() - parser_options = parser.options(section) - grid_elements = [[wx_window.GetCellValue(row, col).encode('utf-8').strip() - for col in range(col_count)] - for row in range(row_count)] - if not grid_elements: - for option in parser_options: - parser.remove_option(section, option) - module_config[section].pop(option) - else: - item_list = [item[0] for item in grid_elements] - for option in parser_options: - if option not in item_list: - module_config[section].pop(option) - parser.remove_option(section, option) - for elements in grid_elements: - parser.set(section, *elements) - if len(elements) == 1: - module_config[section][elements[0]] = None - elif len(elements) == 2: - module_config[section][elements[0]] = elements[1] - elif isinstance(wx_window, wx.Button): - if item_name not in SKIP_BUTTONS: - parser.set(section, item_name) - module_config[section][item_name] = None - elif isinstance(wx_window, KeyListBox): - item_id = wx_window.GetSelection() - parser_options = parser.options(section) - item_value = wx_window.get_key_from_index(item_id) - if not item_value: - for option in parser_options: - parser.remove_option(section, option) - module_config[section] = None - else: - for option in parser_options: - parser.remove_option(section, option) - parser.set(section, item_value) - module_config[section] = item_value - elif isinstance(wx_window, KeyCheckListBox): - item_ids = wx_window.GetChecked() - parser_options = parser.options(section) - items_values = [wx_window.get_key_from_index(item_id) for item_id in item_ids] - if not items_values: - for option in parser_options: - parser.remove_option(section, option) - module_config[section].pop(option) - else: - for option in parser_options: - if option not in items_values: - parser.remove_option(section, option) - module_config[section].pop(option) - for value in items_values: - parser.set(section, value) - module_config[section][value] = None - elif isinstance(wx_window, KeyChoice): - item_id = wx_window.GetSelection() - item_value = wx_window.get_key_from_index(item_id) - parser.set(section, item_name, item_value) - module_config[section][item_name] = item_value - elif isinstance(wx_window, wx.SpinCtrl): - item_value = wx_window.GetValue() - parser.set(section, item_name, item_value) - module_config[section][item_name] = item_value + change_type = change['type'] + if change_type in ['gridbox']: + for option in parser.options(section): + parser.remove_option(section, option) + module_config[section] = OrderedDict() + for change_tuple in change['value']: + if len(change_tuple) > 1: + change_item, change_value = change_tuple + else: + change_item, change_value = (change_tuple[0], None) + parser.set(section, change_item, change_value) + module_config[section][change_item] = change_value + elif change_type in ['checkbox', 'textctrl', 'dropctrl', 'spinctrl']: + value = change['value'] + if item == MODULE_KEY.join(['main', 'gui', 'show_hidden']): + self.show_hidden = value + parser.set(section, item_name, value) + module_config[section][item_name] = value + elif change_type in ['listbox']: + for option in parser.options(section): + parser.remove_option(section, option) + parser.set(section, change['value']) + module_config[section] = change['value'] + elif change_type in ['listbox_check']: + for option in parser.options(section): + parser.remove_option(section, option) + module_config[section] = {} + for value in change['value']: + parser.set(section, value) + module_config[section][value] = None with open(module_settings['file'], 'w') as config_file: parser.write(config_file) if 'class' in module_settings: @@ -719,24 +697,20 @@ def select_cell(self, event): event.Skip() def list_operation(self, key, action): + list_box = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([key, 'list_box']))) if action == 'list_add': list_input_value = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([key, 'list_input']))).GetValue() - try: - list_input2 = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([key, 'list_input2']), error=True)) - list_input2_value = list_input2.GetValue() if list_input2 else None - except ReferenceError: - list_input2_value = None - - list_box = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([key, 'list_box']))) list_box.AppendRows(1) row_count = list_box.GetNumberRows() - 1 list_box.SetCellValue(row_count, 0, list_input_value.strip()) - if list_input2_value: + + list_input2_id = get_id_from_name(MODULE_KEY.join([key, 'list_input2'])) + if list_input2_id: + list_input2_value = wx.FindWindowById(list_input2_id).GetValue() list_box.SetCellValue(row_count, 1, list_input2_value.strip()) elif action == 'list_remove': - list_box = wx.FindWindowById(get_id_from_name(MODULE_KEY.join([key, 'list_box']))) top = list_box.GetSelectionBlockTopLeft() bot = list_box.GetSelectionBlockBottomRight() if top and bot: @@ -751,6 +725,13 @@ def list_operation(self, key, action): for select in del_rows: list_box.DeleteRows(select - ids_deleted) ids_deleted += 1 + rows = list_box.GetNumberRows() + cols = list_box.GetNumberCols() + grid_elements = [tuple([list_box.GetCellValue(row, col).encode('utf-8').strip() + for col in range(cols)]) + for row in range(rows)] + + self.on_change(key, grid_elements, item_type='gridbox') class ChatGui(wx.Frame): diff --git a/http/czt/css/style.css b/http/czt/css/style.css index b43106f..759745d 100644 --- a/http/czt/css/style.css +++ b/http/czt/css/style.css @@ -1,77 +1,86 @@ -::-webkit-scrollbar { - visibility: hidden; -} -body{ +body { + height: 100%; + overflow: hidden; + padding: 0; margin: 0; } -#ChatContainer{ + +#chat-container { position: absolute; bottom: 0; width: 100%; } -.msg{ - background-color: rgba( 35, 35, 37, 0.627451 ); - font-family: 'Consolas', serif; - font-size: {{ font_size }}pt; + +.message { + background-color: rgba( 35, 35, 37, 0.627451); + word-wrap: break-word; text-shadow: 0 1px 1px black; + font-family: 'Consolas', serif; + font-size: 0; padding-top: 2px; - word-wrap: break-word; - color: #FFFFFF; + color: #FFF; } -.msgSource, -.msgBadge, -.msgLevel, -.msgSLevel{ - width: 16px; - height: 16px; - background-color: rgba(0,0,0,0.6); - border-radius: 3px; - margin: 0 3px 0 0; + +.message > div { + display: inline; + font-size: {{ font_size }}pt; +} + +.message-source, +.message-level, +.message-badges { position: relative; + margin: 0 3px 0 0; top: 3px; left: 3px; - display: inline-block; } -.msgUser{ - display: inline-block; - position: relative; - padding-left: 5px; + +.badge, +.platform, +.level, +.s-level { + background-color: rgba(0, 0, 0, 0.6); + width: 16px; + height: 16px; + padding: 2px; + border-radius: 5px; } -.msgText{ - display: inline; - height: auto; + +.smile { + margin-top: -4px; + height: 20px; + width: auto; + top: 2px; +} + +.username { padding-left: 5px; } -.msgTextPriv{ - display: inline; - height: auto; + +.text { padding-left: 5px; +} + +.private { color: #e57017; } -.msgTextMention{ - display: inline; - height: auto; - padding-left: 5px; + +.system { + color: #ff68fb; +} + +.mention { color: #c0ffc0; } -.msgTextSystem{ - display: inline; - height: auto; - padding-left: 5px; - color: #ff68fb; + +.broadcaster { + background-color: #e71818; } -.imgSmile{ - margin-top: -4px; - height: 20px; - width: auto; - top: 2px; - position: relative; + +.mod { + background-color: #34ae0a; } -.imgBadge, -.imgSource, -.imgLevel, -.imgSLevel{ - width: 16px; - height: 16px; - border-radius: 3px; + +.turbo { + background-color: #6441a5; } \ No newline at end of file diff --git a/http/czt/index.html b/http/czt/index.html index cf8d366..0a9577a 100644 --- a/http/czt/index.html +++ b/http/czt/index.html @@ -1,12 +1,38 @@ - - LalkaChat - - - - - - - - -
- + + + + Lalka - chat + + + + + + + + + + + +
+
+
+ +
+ +
+ +
+ + + +
{{message.user}}:
+
+
+
+ + + \ No newline at end of file diff --git a/http/czt/js/app.js b/http/czt/js/app.js new file mode 100644 index 0000000..0fa643e --- /dev/null +++ b/http/czt/js/app.js @@ -0,0 +1,171 @@ +(function (WebSocket, Vue, Sanitizer) { + 'use strict'; + + var viewModel = new Vue({ + el: '#chat-container', + data: function () { + var wsUrl = 'ws://' + window.location.host + '/ws'; + var messages = []; + var socket = new WebSocket(wsUrl); + + return { + messages: messages, + url: wsUrl, + socket: socket, + attempts: 0, + socketInterval: null, + messagesInterval: -1 + } + }, + created: function () { + this.socket.onmessage = this.onmessage; + this.socket.onopen = this.onopen; + this.socket.onclose = this.onclose; + if (this.messagesInterval > 0) setInterval(this.clear, 500) + }, + methods: { + clear: function () { + var that = this; + var time = new Date(); + + this.messages = this.messages.filter(function (message) { + return Math.abs(time - message.time) < that.messagesInterval; + }); + }, + sanitize: function (message) { + var html = ''; + + switch (message.source) { + case 'tw': + html = this.replaceTwitchEmotions(message.text, message.emotes); + + if (message.hasOwnProperty('bttv_emotes')) { + html = this.replaceBttvEmoticons(html, message.bttv_emotes); + } + break; + case 'gg': + case 'fs': + html = this.replaceDefaultEmotions(message.text, message.emotes); + break; + default: + html = message.text; + break; + } + + return Sanitizer.sanitize(html); + }, + replaceTwitchEmotions: function (message, emotes) { + if (!emotes || emotes.length <= 0) { + return message; + } + var placesToReplace = []; + for (var emote in emotes) { + if (Array.isArray(emotes[emote]['emote_pos'])) { + for (var i = 0; i < emotes[emote]['emote_pos'].length; ++i) { + var range = emotes[emote]['emote_pos'][i]; + var rangeParts = range.split('-'); + placesToReplace.push({ + "emote_id": emotes[emote]['emote_id'], + "from": parseInt(rangeParts[0]), + "to": parseInt(rangeParts[1]) + 1 + }); + } + } + } + + placesToReplace.sort(function(first, second) { + return second.from - first.from; + }); + + for (var iPlace = 0; iPlace < placesToReplace.length; iPlace++) { + var place = placesToReplace[iPlace]; + message = message.substring(0, place.from) + "$emoticon#" + place.emote_id + "$" + message.substring(place.to); + } + + return message.replace(/\$emoticon#(\d+)\$/g, function(code, emoteId) { + var url = 'http://static-cdn.jtvnw.net/emoticons/v1/' + emoteId + '/1.0'; + return ''; + }); + }, + replaceBttvEmoticons: function(message, emotes) { + return message.replace(/(^| )?(\S+)?( |$)/g, function (code, b1, emote_key, b2) { + for(var emote in emotes) { + if(emotes[emote].emote_id == emote_key && emotes[emote].emote_url) { + return ''; + } + } + return code; + }); + }, + replaceDefaultEmotions: function (message, emotes) { + return message.replace(/:(\w+|\d+):/g, function(code, emote_key) { + for (var emote in emotes) { + if (!!emotes[emote] && emotes[emote]['emote_id'] == emote_key) { + return ''; + } + } + + return code; + }); + }, + removeByIds: function (ids) { + this.messages = this.messages.filter(function (message) { + return !ids.includes(message.id); + }); + }, + removeByUsernames: function (usernames) { + this.messages = this.messages.filter(function(message) { + return !usernames.includes(message.user); + }); + }, + run: function (message) { + if (!message.command) + return; + + switch (message.command) { + case 'reload': + window.location.reload(); + break; + case 'remove_by_user': + this.removeByUsernames(message.user); + break; + case 'remove_by_id': + this.removeByIds(message.ids); + break; + default: + console.log('Got unknown command ', message.command); + } + }, + onmessage: function (event) { + var message = JSON.parse(event.data); + if (!message.type) + return; + + switch (message.type) { + case 'command': + this.run(message); + break; + default: + message.time = new Date(); + this.messages.push(message); + } + }, + onopen: function () { + this.attempts = 0; + if (!this.socketInterval) { + clearInterval(this.socketInterval); + this.socketInterval = null; + } + }, + onclose: function (reason) { + this.socketInterval = setInterval(this.reconnect, 1000); + }, + reconnect: function () { + this.attempts++; + + this.socket = new WebSocket(this.url); + } + }, + filters: {} + }); +})(window.WebSocket, window.Vue, window.DOMPurify); \ No newline at end of file diff --git a/http/czt/js/polyfills.min.js b/http/czt/js/polyfills.min.js new file mode 100644 index 0000000..313f47a --- /dev/null +++ b/http/czt/js/polyfills.min.js @@ -0,0 +1 @@ +if(![].includes){Array.prototype.includes=function(searchElement){"use strict";var O=Object(this);var len=parseInt(O.length)||0;if(len===0){return false}var n=parseInt(arguments[1])||0;var k;if(n>=0){k=n}else{k=len+n;if(k<0){k=0}}while(k"; - } - } - return code; - }); -}; - -htmlifyBTTVEmoticons = function(message, emotes) { - return message.replace(/(^| )?(\S+)?( |$)/g, function (code, b1, emote_key, b2) { - for(var emote in emotes) { - if(emote_key == emotes[emote]['emote_id']) { - return ""; - } - } - return code; - }); -}; - -htmlifyTwitchEmoticons = function(message) { - return message.replace(/\$emoticon#(\d+)\$/g, function (code, emoteId) { - return ""; - }); -}; - -escapeHtml = (function () { - 'use strict'; - var chr = { '"': '"', '&': '&', '<': '<', '>': '>' }; - return function (text) { - return text.replace(/[\"&<>]/g, function (a) { return chr[a]; }); - }; -}()); - -function removeMessage(element) { - var elm = element || chatMessages.lastChild; - chatMessages.removeChild(elm); - } - -function updateMessages() { - if(chatMessages.children.length < MAX_MESSAGES) return; - var element = chatMessages.lastChild; - if(element.hasAttribute('timer-id')) { - var timerId = element.getAttribute('timer-id'); - window.clearTimeout(timerId); - } - removeMessage(element); - } - -function showMessage(message) { - var badge_colors = 1; - - var elements = {}; - elements['message'] = document.createElement('div'); - elements.message.setAttribute('class', 'msg'); - elements.message.setAttribute('id', message.id); - elements.message.setAttribute('user', message.user); - if(timeout > 0) { - elements.message.setAttribute('timer-id', setTimeout(removeMessage, timeout * 1000, elements.message)); - } - - var messageJSON = message; - - if(messageJSON.hasOwnProperty('source')) { - //console.log("message has source " + messageJSON.source); - - elements.message['source'] = document.createElement('div'); - elements.message.source.setAttribute('class', 'msgSource'); - - elements.message.source['img'] = document.createElement('img'); - if(messageJSON.hasOwnProperty('source_icon')) { - elements.message.source.img.setAttribute('src', messageJSON.source_icon); - } - else{ - elements.message.source.img.setAttribute('src', '/img/sources/' + messageJSON.source + '.png'); - } - elements.message.source.img.setAttribute('class', 'imgSource'); - - elements.message.source.appendChild(elements.message.source.img); - elements.message.appendChild(elements.message.source); - } - - if(messageJSON.hasOwnProperty('levels')) { - elements.message['level'] = document.createElement('div'); - elements.message.level.setAttribute('class', 'msgLevel'); - - elements.message.level['img'] = document.createElement('img'); - elements.message.level.img.setAttribute('class', 'imgLevel'); - elements.message.level.img.setAttribute('src', messageJSON.levels.url); - - elements.message.level.appendChild(elements.message.level.img); - elements.message.appendChild(elements.message.level); - } - - if(messageJSON.hasOwnProperty('s_levels')) { - - for (i = 0; i < messageJSON.s_levels.length; i++) { - elements.message['s_level'] = document.createElement('div'); - elements.message.s_level.setAttribute('class', 'msgSLevel'); - - elements.message.s_level['img'] = document.createElement('img'); - elements.message.s_level.img.setAttribute('class', 'imgSLevel'); - elements.message.s_level.img.setAttribute('src', messageJSON.s_levels[i].url); - - elements.message.s_level.appendChild(elements.message.s_level.img); - elements.message.appendChild(elements.message.s_level); - } - } - - if(messageJSON.hasOwnProperty('badges')) { - - for (i = 0; i < messageJSON.badges.length; i++) { - elements.message['badge'] = document.createElement('div'); - elements.message.badge.setAttribute('class', 'msgBadge'); - - elements.message.badge['img'] = document.createElement('img'); - elements.message.badge.img.setAttribute('class', 'imgBadge'); - elements.message.badge.img.setAttribute('src', messageJSON.badges[i].url); - - if(badge_colors) { - if(messageJSON.badges[i].badge == 'broadcaster') { - elements.message.badge.img.setAttribute('style', 'background-color: #e71818'); - } - else if(messageJSON.badges[i].badge == 'mod') { - elements.message.badge.img.setAttribute('style', 'background-color: #34ae0a'); - } - else if(messageJSON.badges[i].badge == 'turbo') { - elements.message.badge.img.setAttribute('style', 'background-color: #6441a5'); - } - } - elements.message.badge.appendChild(elements.message.badge.img); - elements.message.appendChild(elements.message.badge); - } - } - - if(messageJSON.hasOwnProperty('user')) { - // console.log("message has user " + messageJSON.user); - elements.message['user'] = document.createElement('div'); - elements.message.user.setAttribute('class', 'msgUser'); - var addString = messageJSON.user; - - if (messageJSON.hasOwnProperty('msg_type')) { - if (messageJSON.msg_type == 'pubmsg') { - addString += ": " - } - } - else { - addString += ": " - } - - elements.message.user.appendChild(document.createTextNode(addString)); - - elements.message.appendChild(elements.message.user); - } - - if(messageJSON.hasOwnProperty('text')) { - // console.log("message has text " + messageJSON.text); - elements.message['text'] = document.createElement('div'); - if(messageJSON.source == 'sy') { - elements.message.text.setAttribute('class', 'msgTextSystem'); - } - else if(messageJSON.hasOwnProperty('pm') && messageJSON.pm == true) { - elements.message.text.setAttribute('class', 'msgTextPriv'); - } - else if(messageJSON.hasOwnProperty('mention') && messageJSON.mention == true){ - elements.message.text.setAttribute('class', 'msgTextMention'); - } - else { - elements.message.text.setAttribute('class', 'msgText'); - } - - if(messageJSON.source == 'tw') { - messageJSON.text = htmlifyTwitchEmoticons(escapeHtml(twitch_processEmoticons(messageJSON.text, messageJSON.emotes))); - if(messageJSON.hasOwnProperty('bttv_emotes')) { - messageJSON.text = htmlifyBTTVEmoticons(messageJSON.text, messageJSON.bttv_emotes); - } - } - else if(messageJSON.source == 'gg') { - messageJSON.text = htmlifyGGEmoticons(messageJSON.text, messageJSON.emotes) - } - else if(messageJSON.source == 'fs') { - messageJSON.text = htmlifyGGEmoticons(escapeHtml(messageJSON.text), messageJSON.emotes) - } - - // elements.message.text.appendChild(document.createTextNode(messageJSON.text)); - elements.message.text.innerHTML = messageJSON.text; - - elements.message.appendChild(elements.message.text); - - } - document.getElementById('ChatContainer').appendChild(elements.message); - // updateMessages(); -} - -function runCommand(message) { - switch (message.command) { - case 'reload': - window.location.reload(); - break - case 'remove_by_id': - message.ids.forEach(function(message_item) { - item = document.getElementById(message_item) - if (item) { - item.parentNode.removeChild(item) - } - }) - break - case 'remove_by_user': - message.user.forEach(function(user) { - var children = chatMessages.childNodes - var node_length = children.length - 1 - for(i=node_length; i >= 0; --i) { - if(children[i].getAttribute('user') == user) { - children[i].parentNode.removeChild(children[i]) - } - } - }) - break - default: - console.log("Got unknown command") - } -} - diff --git a/modules/messaging/webchat.py b/modules/messaging/webchat.py index 1293c91..439f5a6 100644 --- a/modules/messaging/webchat.py +++ b/modules/messaging/webchat.py @@ -115,8 +115,10 @@ def __init__(self, http_folder, settings): @cherrypy.expose def style_css(self): + cherrypy.response.headers['Content-Type'] = 'text/css' with open(os.path.join(self.http_folder, 'css', 'style.css'), 'r') as css: - return Template(css.read()).render(**self.settings) + css_content = css.read() + return Template(css_content).render(**self.settings) class HttpRoot(object): @@ -171,7 +173,7 @@ def run(self): 'tools.staticdir.dir': os.path.join(http_folder, 'img')}} css_config = { - '/': {'tools.response_headers.headers': [('Content-Type', 'text/plain')]} + '/': {} } cherrypy.tree.mount(HttpRoot(http_folder), '', config) From 4b6680e1746c82a08c2dd4a4d1ef44a1934dab2f Mon Sep 17 00:00:00 2001 From: Ilya Chursin Date: Thu, 10 Nov 2016 00:16:43 +0300 Subject: [PATCH 08/40] a) remove polyfills.min.js functions b) refactor badges (use :class bind instead of :style) c) refactor css (optimize classes, add premium class) --- http/czt/css/style.css | 10 ++++++---- http/czt/index.html | 4 +--- http/czt/js/app.js | 15 +++++++++++---- http/czt/js/polyfills.min.js | 1 - 4 files changed, 18 insertions(+), 12 deletions(-) delete mode 100644 http/czt/js/polyfills.min.js diff --git a/http/czt/css/style.css b/http/czt/css/style.css index 759745d..ddabe42 100644 --- a/http/czt/css/style.css +++ b/http/czt/css/style.css @@ -53,11 +53,9 @@ body { top: 2px; } -.username { - padding-left: 5px; -} -.text { +.text, +.username { padding-left: 5px; } @@ -83,4 +81,8 @@ body { .turbo { background-color: #6441a5; +} + +.premium { + background-color: #009CDC; } \ No newline at end of file diff --git a/http/czt/index.html b/http/czt/index.html index 0a9577a..8b59810 100644 --- a/http/czt/index.html +++ b/http/czt/index.html @@ -6,8 +6,6 @@ - - @@ -20,7 +18,7 @@
- +