From d848d1532ea01961cb602e5399e5feeb8e2539c8 Mon Sep 17 00:00:00 2001 From: n9k Date: Fri, 18 Feb 2022 05:16:54 +0000 Subject: [PATCH] Reflect appearance changes with js --- anonstream/chat.py | 2 +- anonstream/routes/nojs.py | 29 ++--- anonstream/routes/wrappers.py | 4 +- anonstream/static/anonstream.js | 187 ++++++++++++++++++++++++---- anonstream/static/style.css | 7 ++ anonstream/templates/nojs_chat.html | 11 +- anonstream/templates/nojs_form.html | 1 + anonstream/user.py | 37 +++++- 8 files changed, 230 insertions(+), 48 deletions(-) diff --git a/anonstream/chat.py b/anonstream/chat.py index 3fc230b..529c3be 100644 --- a/anonstream/chat.py +++ b/anonstream/chat.py @@ -70,7 +70,7 @@ async def add_chat_message(user, nonce, comment): 'seq': seq, 'token_hash': user['token_hash'], 'markup': markup, - } + }, ) return markup diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index 008f1b4..43c6845 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -1,10 +1,9 @@ from quart import current_app, request, render_template, redirect, url_for, escape, Markup from anonstream.stream import get_stream_title -from anonstream.user import add_notice, pop_notice, change_name, change_color, change_tripcode, delete_tripcode, BadAppearance +from anonstream.user import add_notice, pop_notice, try_change_appearance from anonstream.chat import add_chat_message, Rejected from anonstream.routes.wrappers import with_user_from -from anonstream.wrappers import try_except_log from anonstream.helpers.user import get_default_name from anonstream.utils.chat import generate_nonce from anonstream.utils.user import concatenate_for_notice @@ -72,29 +71,19 @@ async def nojs_submit_appearance(user): want_delete_tripcode = form.get('clear-tripcode', type=bool) want_change_tripcode = form.get('set-tripcode', type=bool) - errors = [] - def try_(f, *args, **kwargs): - return try_except_log(errors, BadAppearance)(f)(*args, **kwargs) - - try_(change_name, user, name, dry_run=True) - try_(change_color, user, color, dry_run=True) - if want_delete_tripcode: - pass - elif want_change_tripcode: - try_(change_tripcode, user, password, dry_run=True) - + errors = await try_change_appearance( + user, + name, + color, + password, + want_delete_tripcode, + want_change_tripcode, + ) if errors: notice = Markup('
').join( concatenate_for_notice(*error.args) for error in errors ) else: - change_name(user, name) - change_color(user, color) - if want_delete_tripcode: - delete_tripcode(user) - elif want_change_tripcode: - change_tripcode(user, password) - notice = 'Changed appearance' notice_id = add_notice(user, notice, verbose=len(errors) > 1) diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index c8bd0a8..b50dbec 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -64,7 +64,7 @@ def with_user_from(context): payload={ 'type': 'rem-users', 'token_hashes': sunsetted_token_hashes, - } + }, ) # Update / create user @@ -84,7 +84,7 @@ def with_user_from(context): 'type': 'add-user', 'token_hash': user['token_hash'], 'user': user_for_websocket(user), - } + }, ) # Set cookie diff --git a/anonstream/static/anonstream.js b/anonstream/static/anonstream.js index 31d8ad3..8662571 100644 --- a/anonstream/static/anonstream.js +++ b/anonstream/static/anonstream.js @@ -2,7 +2,9 @@ const token = document.querySelector("body").dataset.token; /* insert js-only markup */ -const jsmarkup_style = '' +const jsmarkup_style_color = '' +const jsmarkup_style_tripcode_display = '' +const jsmarkup_style_tripcode_colors = '' const jsmarkup_info = '
'; const jsmarkup_info_title = '
'; const jsmarkup_chat_messages = ''; @@ -18,9 +20,17 @@ const jsmarkup_chat_form = `\ `; const insert_jsmarkup = () => { - if (document.getElementById("style_js") === null) { + if (document.getElementById("style-color") === null) { const parent = document.head; - parent.insertAdjacentHTML("beforeend", jsmarkup_style); + parent.insertAdjacentHTML("beforeend", jsmarkup_style_color); + } + if (document.getElementById("style-tripcode-display") === null) { + const parent = document.head; + parent.insertAdjacentHTML("beforeend", jsmarkup_style_tripcode_display); + } + if (document.getElementById("style-tripcode-colors") === null) { + const parent = document.head; + parent.insertAdjacentHTML("beforeend", jsmarkup_style_tripcode_colors); } if (document.getElementById("info_js") === null) { const parent = document.getElementById("info"); @@ -41,7 +51,9 @@ const insert_jsmarkup = () => { } insert_jsmarkup(); -const stylesheet = document.styleSheets[1]; +const stylesheet_color = document.styleSheets[1]; +const stylesheet_tripcode_display = document.styleSheets[2]; +const stylesheet_tripcode_colors = document.styleSheets[3]; /* create websocket */ const info_title = document.getElementById("info_js__title"); @@ -60,11 +72,24 @@ const create_chat_message = (object) => { chat_message_name.innerText = user.name || default_name[user.broadcaster]; //chat_message_name.dataset.color = user.color; // not working in any browser + const chat_message_tripcode_nbsp = document.createElement("span"); + chat_message_tripcode_nbsp.classList.add("for-tripcode"); + chat_message_tripcode_nbsp.innerHTML = " "; + + const chat_message_tripcode = document.createElement("span"); + chat_message_tripcode.classList.add("tripcode"); + chat_message_tripcode.classList.add("for-tripcode"); + if (user.tripcode !== null) { + chat_message_tripcode.innerHTML = user.tripcode.digest; + } + const chat_message_markup = document.createElement("span"); chat_message_markup.classList.add("chat-message__markup"); chat_message_markup.innerHTML = object.markup; chat_message.insertAdjacentElement("beforeend", chat_message_name); + chat_message.insertAdjacentElement("beforeend", chat_message_tripcode_nbsp); + chat_message.insertAdjacentElement("beforeend", chat_message_tripcode); chat_message.insertAdjacentHTML("beforeend", ": "); chat_message.insertAdjacentElement("beforeend", chat_message_markup); @@ -73,38 +98,143 @@ const create_chat_message = (object) => { let users = {}; let default_name = {true: "Broadcaster", false: "Anonymous"}; -const equal = (color1, color2) => { - /* comparing css colors is annoying */ - return false; -} -const update_user_styles = () => { +const tidy_stylesheet = (stylesheet, selector_regex, ignore_condition) => { const to_delete = []; const to_ignore = new Set(); for (let index = 0; index < stylesheet.cssRules.length; index++) { - const css_rule = stylesheet.cssRules[index]; - const match = css_rule.selectorText.match(/.chat-message\[data-token-hash="([a-z2-7]{26})"\] > .chat-message__name/); + const css_rule = stylesheet_color.cssRules[index]; + const match = css_rule.selectorText.match(selector_regex); const token_hash = match === null ? null : match[1]; const user = token_hash === null ? null : users[token_hash]; if (user === null || user === undefined) { to_delete.push(index); - } else if (!equal(css_rule.style.color, user.color)) { + } else if (!ignore_condition(token_hash, user, css_rule)) { to_delete.push(index); } else { to_ignore.add(token_hash); } } - - for (const token_hash of Object.keys(users)) { - if (!to_ignore.has(token_hash)) { - const user = users[token_hash]; - stylesheet.insertRule( - `.chat-message[data-token-hash="${token_hash}"] > .chat-message__name { color: ${user.color}; }`, - stylesheet.cssRules.length, + return {to_delete, to_ignore}; +} +const equal = (color1, color2) => { + /* comparing css colors is annoying */ + // when this is working, remove `ignore_other_token_hashes` from functions below + return false; +} +const update_user_colors = (token_hash=null) => { + ignore_other_token_hashes = token_hash !== null; + token_hashes = token_hash === null ? Object.keys(users) : [token_hash]; + const {to_delete, to_ignore} = tidy_stylesheet( + stylesheet=stylesheet_color, + selector_regex=/.chat-message\[data-token-hash="([a-z2-7]{26})"\] > .chat-message__name/, + ignore_condition=(this_token_hash, this_user, css_rule) => { + irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash; + correct_color = equal(css_rule.style.color, this_user.color); + return irrelevant || correct_color; + }, + ); + // update colors + for (const this_token_hash of token_hashes) { + if (!to_ignore.has(this_token_hash)) { + const user = users[this_token_hash]; + stylesheet_color.insertRule( + `.chat-message[data-token-hash="${this_token_hash}"] > .chat-message__name { color: ${user.color}; }`, + stylesheet_color.cssRules.length, ); } } + // delete css rules for (const index of to_delete.reverse()) { - stylesheet.deleteRule(index); + stylesheet_color.deleteRule(index); + } +} +const update_user_name = (token_hash) => { + const name = users[token_hash].name; + for (const chat_message of chat_messages.children) { + if (token_hash === chat_message.dataset.tokenHash) { + chat_message.querySelector(".chat-message__name").innerText = name; + } + } +} +const update_user_tripcodes = (token_hash=null) => { + ignore_other_token_hashes = token_hash !== null; + token_hashes = token_hash === null ? Object.keys(users) : [token_hash]; + const {to_delete: to_delete_display, to_ignore: to_ignore_display} = tidy_stylesheet( + stylesheet=stylesheet_tripcode_display, + selector_regex=/.chat-message\[data-token-hash="([a-z2-7]{26})"\] > .for-tripcode/, + ignore_condition=(this_token_hash, this_user, css_rule) => { + irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash; + correctly_hidden = this_user.tripcode === null && css_rule.style.display === "none"; + correctly_showing = this_user.tripcode !== null && css_rule.style.display === "inline"; + return irrelevant || correctly_hidden || correctly_showing; + }, + ); + const {to_delete: to_delete_colors, to_ignore: to_ignore_colors} = tidy_stylesheet( + stylesheet=stylesheet_tripcode_colors, + regex=/.chat-message\[data-token-hash="([a-z2-7]{26})"\] > .tripcode/, + ignore_condition=(this_token_hash, this_user, css_rule) => { + irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash; + correctly_blank = ( + this_user.tripcode === null + && css_rule.style.backgroundColor === "initial" + && css_rule.style.color === "initial" + ); + correctly_colored = ( + this_user.tripcode !== null + && equal(css_rule.style.backgroundColor, this_user.tripcode.background_color) + && equal(css_rule.style.color, this_user.tripcode.foreground_color) + ); + return irrelevant || correctly_blank || correctly_colored; + }, + ); + + // update colors + for (const this_token_hash of token_hashes) { + const tripcode = users[this_token_hash].tripcode; + if (tripcode === null) { + if (!to_ignore_display.has(token_hash)) { + stylesheet_tripcode_display.insertRule( + `.chat-message[data-token-hash="${this_token_hash}"] > .for-tripcode { display: none; }`, + stylesheet_tripcode_display.cssRules.length, + ); + } + if (!to_ignore_colors.has(token_hash)) { + stylesheet_tripcode_colors.insertRule( + `.chat-message[data-token-hash="${this_token_hash}"] > .tripcode { background-color: initial; color: initial; }`, + stylesheet_tripcode_colors.cssRules.length, + ); + } + } else { + if (!to_ignore_display.has(token_hash)) { + stylesheet_tripcode_display.insertRule( + `.chat-message[data-token-hash="${this_token_hash}"] > .for-tripcode { display: inline; }`, + stylesheet_tripcode_display.cssRules.length, + ); + } + if (!to_ignore_colors.has(token_hash)) { + stylesheet_tripcode_colors.insertRule( + `.chat-message[data-token-hash="${this_token_hash}"] > .tripcode { background-color: ${tripcode.background_color}; color: ${tripcode.foreground_color}; }`, + stylesheet_tripcode_colors.cssRules.length, + ); + } + } + } + + // delete css rules + for (const index of to_delete_display.reverse()) { + stylesheet_tripcode_display.deleteRule(index); + } + for (const index of to_delete_colors.reverse()) { + stylesheet_tripcode_colors.deleteRule(index); + } + + // update inner texts + for (const chat_message of chat_messages.children) { + const this_token_hash = chat_message.dataset.tokenHash; + const tripcode = users[this_token_hash].tripcode; + if (token_hashes.includes(this_token_hash)) { + chat_message.querySelector(".tripcode").innerText = tripcode === null ? "" : tripcode.digest; + } } } @@ -124,7 +254,8 @@ const on_websocket_message = (event) => { default_name = receipt.default; users = receipt.users; - update_user_styles(); + update_user_colors(); + update_user_tripcodes(); const seqs = new Set(receipt.messages.map((message) => {return message.seq;})); const to_delete = []; @@ -186,7 +317,19 @@ const on_websocket_message = (event) => { case "add-user": console.log("ws add-user", receipt); users[receipt.token_hash] = receipt.user; - update_user_styles(); + update_user_colors(receipt.token_hash); + update_user_tripcodes(receipt.token_hash); + break; + + case "mut-user": + console.log("ws mut-user", receipt); + const user = users[receipt.token_hash]; + user.name = receipt.name; + user.color = receipt.color; + user.tripcode = receipt.tripcode; + update_user_name(receipt.token_hash); + update_user_colors(receipt.token_hash); + update_user_tripcodes(receipt.token_hash); break; case "rem-users": diff --git a/anonstream/static/style.css b/anonstream/static/style.css index 463bb14..c050f18 100644 --- a/anonstream/static/style.css +++ b/anonstream/static/style.css @@ -156,6 +156,13 @@ noscript { overflow-wrap: anywhere; line-height: 1.3125; } +.tripcode { + padding: 0 5px; + border-radius: 7px; + font-family: monospace; + font-size: 9pt; + cursor: default; +} #chat-live { font-size: 9pt; line-height: var(--button-height); diff --git a/anonstream/templates/nojs_chat.html b/anonstream/templates/nojs_chat.html index d80a5dd..b37fbcf 100644 --- a/anonstream/templates/nojs_chat.html +++ b/anonstream/templates/nojs_chat.html @@ -36,12 +36,19 @@ overflow-wrap: anywhere; font-weight: bold; /* color: attr("data-color"); */ - cursor: normal; + cursor: default; } .chat-message__markup { overflow-wrap: anywhere; line-height: 1.3125; } + .tripcode { + padding: 0 5px; + border-radius: 7px; + font-family: monospace; + font-size: 9pt; + cursor: default; + } @@ -49,7 +56,7 @@ {% for message in messages | reverse %}
  • {% with user = users_by_token[message.token] %} - {{ user.name or get_default_name(user) }}{{ message.markup }} + {{ user.name or get_default_name(user) }}{% if user.tripcode != none %} {{ user.tripcode.digest }}{% endif %}: {{ message.markup }} {% endwith %}
  • {% endfor %} diff --git a/anonstream/templates/nojs_form.html b/anonstream/templates/nojs_form.html index f9615ea..471db7b 100644 --- a/anonstream/templates/nojs_form.html +++ b/anonstream/templates/nojs_form.html @@ -33,6 +33,7 @@ padding: 0 5px; border-radius: 7px; font-family: monospace; + font-size: 9pt; cursor: default; } .tripcode:not(#tripcode) { diff --git a/anonstream/user.py b/anonstream/user.py index 843fcd3..3d83b00 100644 --- a/anonstream/user.py +++ b/anonstream/user.py @@ -3,7 +3,8 @@ from math import inf from quart import current_app -from anonstream.wrappers import with_timestamp, with_first_argument +from anonstream.chat import broadcast +from anonstream.wrappers import try_except_log, with_timestamp from anonstream.helpers.user import is_visible from anonstream.helpers.tripcode import generate_tripcode from anonstream.utils.colour import color_to_colour, get_contrast, NotAColor @@ -30,6 +31,40 @@ def pop_notice(user, notice_id): notice, verbose = None, False return notice, verbose +async def try_change_appearance(user, name, color, password, + want_delete_tripcode, want_change_tripcode): + errors = [] + def try_(f, *args, **kwargs): + return try_except_log(errors, BadAppearance)(f)(*args, **kwargs) + + try_(change_name, user, name, dry_run=True) + try_(change_color, user, color, dry_run=True) + if want_delete_tripcode: + pass + elif want_change_tripcode: + try_(change_tripcode, user, password, dry_run=True) + + if not errors: + change_name(user, name) + change_color(user, color) + if want_delete_tripcode: + delete_tripcode(user) + elif want_change_tripcode: + change_tripcode(user, password) + + await broadcast( + USERS, + payload={ + 'type': 'mut-user', + 'token_hash': user['token_hash'], + 'name': user['name'], + 'color': user['color'], + 'tripcode': user['tripcode'], + }, + ) + + return errors + def change_name(user, name, dry_run=False): if dry_run: if name is not None: