typo; generate tags from token not colour (just in case there are duplicate colours); add special chat messages

このコミットが含まれているのは:
n9k 2021-05-18 04:29:47 +00:00
コミット f2d9ca4996
5個のファイルの変更82行の追加26行の削除

ファイルの表示

@ -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()

ファイルの表示

@ -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

ファイルの表示

@ -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 @@
<!-- TODO: mobile tooltip / title -->
{% for message in messages %}
<div class="comment rotate">
{% if not message['hidden'] %}
{% if broadcaster %}
<input type="checkbox" name="message_id[]" value="{{ message['id'] }}">
{% if message.get('special') == 'date' %}
<div class="date rotate">{{ message['content'] }}</div>
{% else %}
<div class="comment rotate">
{% if not message['hidden'] %}
{% if broadcaster %}
<input type="checkbox" name="message_id[]" value="{{ message['id'] }}">
{% endif %}
<span class="time" title="{{ message['timestamp'] }}">{{ message['time'] }}</span
{% if message['viewer']['broadcaster'] %}
><span class="camera" title="Broadcaster">🎥</span
{% endif %}
><span class="name" style="color:#{{ message['viewer']['colour'].hex() }};">{{ 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 %}<sup>{{ message['viewer']['tag'] }}</sup>{% endif %}</span
{% if message['viewer']['tripcode']['string'] %}{% if tag %}><span style="margin-right:0.125em;"></span{% endif %}
><div class="tripcode" style="background-color:#{{ message['viewer']['tripcode']['background_colour'].hex() }};color:#{{ message['viewer']['tripcode']['foreground_colour'].hex() }};">{{ message['viewer']['tripcode']['string'] }}</div
{% endif %}{% endwith %}
><span class="message">{{ message['text'] }}</span>
{% endif %}
<span class="time" title="{{ message['date'] }}">{{ message['time'] }}</span
{% if message['viewer']['broadcaster'] %}
><span class="camera" title="Broadcaster">🎥</span
{% endif %}
><span class="name" style="color:#{{ message['viewer']['colour'].hex() }};">{{ 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 %}<sup>{{ message['viewer']['tag'] }}</sup>{% endif %}</span
{% if message['viewer']['tripcode']['string'] %}{% if tag %}><span style="margin-right:0.125em;"></span{% endif %}
><div class="tripcode" style="background-color:#{{ message['viewer']['tripcode']['background_colour'].hex() }};color:#{{ message['viewer']['tripcode']['foreground_colour'].hex() }};">{{ message['viewer']['tripcode']['string'] }}</div
{% endif %}{% endwith %}
><span class="message">{{ message['text'] }}</span>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{% if broadcaster %}

ファイルの表示

@ -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]}'

ファイルの表示

@ -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