diff --git a/website/chat.py b/website/chat.py index 89d63e0..2d3e40e 100644 --- a/website/chat.py +++ b/website/chat.py @@ -12,7 +12,7 @@ from website.constants import BROADCASTER_TOKEN, MESSAGE_MAX_LENGTH, CHAT_MAX_ST from pprint import pprint -messages = deque() +messages = deque() # messages are stored from most recent on the left to least recent on the right captchas = {} viewers = viewership.viewers nonces = set() @@ -72,7 +72,8 @@ def comment(text, token, c_response, c_token, nonce): 'id': f'{token}-{secrets.token_hex(4)}', 'hidden': False, 'time': dt.strftime('%H:%M'), - 'date': dt.strftime('%F %T')}) + 'timestamp': dt.strftime('%F %T'), + 'date': dt.strftime('%F')}) viewers[token]['last_comment'] = now viewers[token]['recent_comments'].append(now) viewers[token]['verified'] = True @@ -138,3 +139,50 @@ def set_tripcode(password, token): def remove_tripcode(token): viewers[token]['tripcode'] = tripcode.default() return N_APPEAR_OK, True + +def decorate(messages): + ''' + add extra stuff to a list of messages, e.g. date, chat command responses + ''' + def _is_visible(message): + # uncomment the end part once the broadcaster can see hidden comments + return not message['hidden'] # or token == BROADCASTER_TOKEN + + first_visible_message = None + final_visible_message = None + for message in messages: + if _is_visible(message): + final_visible_message = message + break + messages.reverse() + for message in messages: + if _is_visible(message): + first_visible_message = message + break + + # if there are no messages, do nothing + if first_visible_message == None or final_visible_message == None: + return + + # TODO: chat commands e.g. !uptime; try to make it so responses always follow the message with the command, so not split over a date separator + + # add date at the top if necessary + if first_visible_message['date'] != final_visible_message['date']: + messages.insert(0, {'special': 'date', 'content': first_visible_message['date']}) + + # add dates between messages that cross over a day boundary + to_insert = [] + previous_message = None + for index, message in enumerate(messages): + if message.get('special'): + continue + if previous_message and message['date'] != previous_message['date']: + to_insert.append(index) + previous_message = message + + to_insert.reverse() + for index in to_insert: + messages.insert(index, {'special': 'date', 'content': messages[index]['date']}) + + # revert back to original ordering + messages.reverse() diff --git a/website/routes.py b/website/routes.py index b0d4a58..ad5d320 100644 --- a/website/routes.py +++ b/website/routes.py @@ -151,6 +151,8 @@ def chat_iframe(): messages = (message for message in chat.messages if not message['hidden']) messages = zip(messages, range(CHAT_SCROLLBACK)) # show at most CHAT_SCROLLBACK messages messages = [message for message, _ in messages] + + chat.decorate(messages) return render_template('chat-iframe.html', token=token, messages=messages, @@ -177,8 +179,9 @@ def heartbeat(): 'title': stream.get_title(), 'start_abs': start_abs if online else None, 'start_rel': start_rel if online else None} - if token in viewership.video_was_corrupted: - response['corrupted'] = True + # commented out because we should be able to tell if we're not receiving the stream already +# if token in viewership.video_was_corrupted: +# response['corrupted'] = True return response diff --git a/website/templates/chat-iframe.html b/website/templates/chat-iframe.html index 851879c..d056ba3 100644 --- a/website/templates/chat-iframe.html +++ b/website/templates/chat-iframe.html @@ -12,6 +12,7 @@ .reverse {direction: rtl;} .comment {color: white;padding:4px 0;margin-top:-4px;margin-bottom:calc(-4px + 0.375em);overflow:hidden;} + .date {color:gray;font-size:75%;text-align:center;border-bottom:1px solid #333;margin:0.5em 0 0.75em 0;} .name {font-weight: bold;unicode-bidi: isolate;} sup {margin: 0 -0.25em 0 0.125em;font-size: 90%;font-family:monospace;} @@ -91,22 +92,26 @@ {% for message in messages %} -
- {% if not message['hidden'] %} - {% if broadcaster %} - + {% if message.get('special') == 'date' %} +
{{ message['content'] }}
+ {% else %} +
+ {% if not message['hidden'] %} + {% if broadcaster %} + + {% endif %} + {{ message['time'] }}🎥{{ RE_WHITESPACE.sub(chr(160), message['viewer']['nickname'] or default_nickname(message['viewer']['token'])) }}{% with tag = message['viewer']['nickname'] == None and not message['viewer']['broadcaster'] %}{% if tag %}{{ message['viewer']['tag'] }}{% endif %}
{{ message['viewer']['tripcode']['string'] }}
{{ message['text'] }} {% endif %} - {{ message['time'] }}🎥{{ RE_WHITESPACE.sub(chr(160), message['viewer']['nickname'] or default_nickname(message['viewer']['token'])) }}{% with tag = message['viewer']['nickname'] == None and not message['viewer']['broadcaster'] %}{% if tag %}{{ message['viewer']['tag'] }}{% endif %}
{{ message['viewer']['tripcode']['string'] }}
{{ message['text'] }} - {% endif %} -
+
+ {% endif %} {% endfor %} {% if broadcaster %} diff --git a/website/utils/colour.py b/website/utils/colour.py index d6d7846..92f876d 100644 --- a/website/utils/colour.py +++ b/website/utils/colour.py @@ -13,7 +13,7 @@ def _distance_sq(c1, c2): def _gen_colour(seed, background=BACKGROUND_COLOUR): ''' - Returns a colour that with sufficient contrast to the background colour + Returns a colour with sufficient contrast to the background colour ''' while True: seed = hashlib.sha256(seed).digest() @@ -24,14 +24,14 @@ def _gen_colour(seed, background=BACKGROUND_COLOUR): def gen_colour(seed, background=BACKGROUND_COLOUR, *avoid): ''' - Returns a colour that with sufficient contrast to the background colour + Returns a colour with sufficient contrast to the background colour Tries to make the colour contrast with all the colours in `avoid` This function hasn't been analysed for efficiency or anything ''' best_colour, best_score = None, None for _ in range(16384): colour = _gen_colour(seed, background) - score = float('inf') if len(avoid) == 0 else sum(_distance_sq(colour, c) for c in avoid) / len(avoid) + score = float('inf') if len(avoid) == 0 else sum(_contrast(colour, c) for c in avoid) / len(avoid) if colour in avoid: score = float('-inf') if 1.8 < score: @@ -45,9 +45,9 @@ def gen_colour(seed, background=BACKGROUND_COLOUR, *avoid): # tag = ((colour[2] & 0xf0) >> 4) | (colour[1] & 0xf0) | ((colour[0] & 0xf0) << 4) # return f'#{tag:03x}' -def tag(colour, length=3): +def tag(token, length=3): ''' - Generates a deterministic pseudorandom tag from a given colour + Generates a deterministic pseudorandom tag from a given token ''' - digest = hashlib.sha256(colour).digest() + digest = hashlib.sha256(token.encode()).digest() return f'#{digest.hex()[:length]}' diff --git a/website/viewership.py b/website/viewership.py index 91ffb76..fc6cd10 100644 --- a/website/viewership.py +++ b/website/viewership.py @@ -38,7 +38,7 @@ def setdefault(token): 'banned': False, 'tripcode': tripcode.default(), 'broadcaster': False} - viewers[token]['tag'] = colour.tag(viewers[token]['colour']) + viewers[token]['tag'] = colour.tag(token) if token == BROADCASTER_TOKEN: viewers[token]['broadcaster'] = True viewers[token]['colour'] = BROADCASTER_COLOUR