typo; generate tags from token not colour (just in case there are duplicate colours); add special chat messages
このコミットが含まれているのは:
コミット
f2d9ca4996
|
@ -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
|
||||
|
|
読み込み中…
新しいイシューから参照