show broadcaster the list of banned users; undo bans; fix colour -> tag generation

このコミットが含まれているのは:
n9k 2021-04-15 15:18:46 +00:00
コミット e72e45c2b4
8個のファイルの変更131行の追加50行の削除

ファイルの表示

@ -82,23 +82,29 @@ def comment(text, token, c_response, c_token, nonce):
viewers[BROADCASTER_TOKEN]['verified'] = True
return failure_reason
def mod(message_ids, hide, ban, ban_and_purge):
def mod_messages(message_ids, hide, ban, ban_and_purge):
purge = ban_and_purge
ban = ban_and_purge or ban
if ban:
banned = {message_id.split('-')[0] for message_id in message_ids}
for token in banned:
viewers[token]['banned'] = True
with viewership.lock:
if ban:
banned = {message_id.split('-')[0] for message_id in message_ids}
for token in banned:
viewers[token]['banned'] = True
for message in messages:
if hide and message['id'] in message_ids:
message['hidden'] = True
if purge and message['viewer']['token'] in banned:
message['hidden'] = True
for message in messages:
if hide and message['id'] in message_ids:
message['hidden'] = True
if purge and message['viewer']['token'] in banned:
message['hidden'] = True
viewership.setdefault(BROADCASTER_TOKEN)
viewers[BROADCASTER_TOKEN]['banned'] = False
viewership.setdefault(BROADCASTER_TOKEN)
viewers[BROADCASTER_TOKEN]['banned'] = False
def mod_users(tokens, banned):
with viewership.lock:
for token in tokens:
viewers[token]['banned'] = banned
def get_captcha(token):
viewership.setdefault(token)

ファイルの表示

@ -230,14 +230,24 @@ def settings():
viewership.preset_comment_iframe[token] = {'note': note, 'show_settings': True}
return redirect(url_for('comment_iframe', token=token))
# TODO: undo bans; undo hides; optionally show that a comment was hidden
@current_app.route('/mod', methods=['POST'])
# TODO: undo hides; optionally show that a comment was hidden; optionally show bans in chat
@current_app.route('/mod/messages', methods=['POST'])
@current_app.auth.login_required
def mod():
def mod_messages():
message_ids = request.form.getlist('message_id[]')
chat.mod(message_ids, request.form.get('hide'), request.form.get('ban'), request.form.get('ban_purge'))
chat.mod_messages(message_ids, request.form.get('hide'), request.form.get('ban'), request.form.get('ban_purge'))
return f'<meta http-equiv="refresh" content="0;url={url_for("chat_iframe")}"><div style="font-weight:bold;color:white;transform: scaleY(-1);">it is done</div>'
@current_app.route('/mod/users', methods=['POST'])
@current_app.auth.login_required
def mod_users():
tokens = request.form.getlist('token[]')
banned = bool(request.form.get('banned', type=int))
chat.mod_users(tokens, banned=banned)
noscript = bool(request.form.get('noscript', type=int))
return f'<meta http-equiv="refresh" content="0;url={url_for("users") if noscript else url_for("chat_iframe")}"><div style="font-weight:bold;color:white;">it is done</div>'
@current_app.route('/stream-info')
def stream_info():
token = get_token()
@ -263,7 +273,14 @@ def stream_info():
def users():
token = get_token()
viewership.made_request(token)
return render_template('users-iframe.html', token=token, people=viewership.get_people_list(), default_nickname=viewership.default_nickname, broadcaster_colour=BROADCASTER_COLOUR, len=len)
return render_template('users-iframe.html',
token=token,
people=viewership.get_people_list(),
default_nickname=viewership.default_nickname,
broadcaster=token == BROADCASTER_TOKEN,
debug=request.args.get('debug'),
broadcaster_colour=BROADCASTER_COLOUR,
len=len)
@current_app.route('/static/radial.apng')
def radial():

ファイルの表示

@ -44,7 +44,7 @@ svg.users-logo {
.chat-banner-left {
float: left;
width: 1em;
margin-top: 2px;
margin-top: 2px; /* wasn't centred vertically for some reason */
}
.users-logo:hover {
border-radius: 6px;
@ -112,4 +112,4 @@ body.dark-theme {
e.g. https://raw.githubusercontent.com/33kk/uso-archive/flomaster/data/usercss/2154.user.css */
div[class="vjs-text-track-display"] {
display: none;
}
}

ファイルの表示

@ -63,7 +63,7 @@
<body>
<div id="messages">
{% if broadcaster %}
<form action="/mod" method="post">
<form action="{{ url_for('mod_messages') }}" method="post">
<div class="reverse">
<input class="rotate" type="submit" name="ban" value="Ban">
<input class="rotate" type="submit" name="hide" value="Hide">
@ -98,21 +98,26 @@
<a href="" style="text-decoration: none;"><div id="refresh" class="rotate">Manual refresh required</div></a>
</div>
<div id="users" style="display: none;">
{% with broadcaster = people['broadcaster'] %}
{% if broadcaster %}
{% with person = people['broadcaster'] %}
{% if person %}
<div class="group">
<div class="group-name">Broadcaster</div>
<div class="person">
<span class="camera" title="Broadcaster">🎥</span><span class="name" style="color:#{{ broadcaster['colour'].hex() }};">{{ broadcaster['nickname'] or default_nickname(broadcaster['token']) }}</span>{% if broadcaster['tripcode']['string'] %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ broadcaster['tripcode']['string'] }}</div>{% endif %}
<span class="camera" title="Broadcaster">🎥</span><span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}</span>{% if person['tripcode']['string'] %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}
</div>
</div>
{% endif %}
{% endwith %}
{% if broadcaster %}
<form action="{{ url_for('mod_users') }}" method="post">
<input type="hidden" name="noscript" value="0">
<input type="hidden" name="banned" value="1">
{% endif %}
<div class="group">
<div class="group-name">Users watching ({{ len(people['watching']) }})</div>
{% for person in people['watching'] %}
<div class="person">
<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
{% if broadcaster %}<input type="checkbox" name="token[]" value="{{ person['token'] }}">{% endif %}<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
</div>
{% endfor %}
</div>
@ -120,10 +125,28 @@
<div class="group-name">Users not watching ({{ len(people['not_watching']) }})</div>
{% for person in people['not_watching'] %}
<div class="person">
<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
{% if broadcaster %}<input type="checkbox" name="token[]" value="{{ person['token'] }}">{% endif %}<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
</div>
{% endfor %}
</div>
{% if broadcaster %}
<button>Ban</button>
</form>
<div style="margin:1.75em 0 1.5em 0;border-bottom:1px solid #3f3f3f;"></div>
<form action="{{ url_for('mod_users') }}" method="post">
<input type="hidden" name="noscript" value="0">
<input type="hidden" name="banned" value="0">
<div class="group-name">Banned ({{ len(people['banned']) }})</div>
<div class="group">
{% for person in people['banned'] %}
<div class="person">
<input type="checkbox" name="token[]" value="{{ person['token'] }}"><span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}
</div>
{% endfor %}
</div>
<button>Unban</button>
</form>
{% endif %}
</div>
</body>
</html>
</html>

ファイルの表示

@ -31,6 +31,10 @@
#users-toggle:checked + .banner form[target="users"] {
display: revert;
}
/* noscript acting weird with svg */
#svg-container {
display: none;
}
</style>
</noscript>
</head>
@ -38,6 +42,7 @@
<div class=pure-g>
<!-- TODO: get rid of PureCSS dependency here; the noscript block on top of the video is too large because the PureCSS files take longer to load -->
<div class="pure-u-1 pure-u-md-2-3">
<!-- TODO: choose which elements get borders more cleverly -->
<div id="stream" class="border">
<!-- https://stackoverflow.com/questions/41014197/how-can-i-play-a-m3u8-file-video-using-the-html5-video-element -->
<video id="videojs" style="width: 100%;height: 100%;" class="video-js vjs-default-skin vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true }'>
@ -57,8 +62,10 @@
<div class="banner">
<div class="chat-banner-left">
<label for="users-toggle" title="Users in chat">
<svg class="users-logo" style="display:none;" version="1.1" viewBox="0 0 20 20" x="0px" y="0px"><g><path fill-rule="evenodd" d="M7 2a4 4 0 00-1.015 7.87c-.098.64-.651 1.13-1.318 1.13A2.667 2.667 0 002 13.667V18h2v-4.333c0-.368.298-.667.667-.667.908 0 1.732-.363 2.333-.953.601.59 1.425.953 2.333.953.369 0 .667.299.667.667V18h2v-4.333A2.667 2.667 0 009.333 11c-.667 0-1.22-.49-1.318-1.13A4.002 4.002 0 007 2zM5 6a2 2 0 104 0 2 2 0 00-4 0z" clip-rule="evenodd"></path><path d="M14 11.83V18h4v-3.75c0-.69-.56-1.25-1.25-1.25a.75.75 0 01-.75-.75v-.42a3.001 3.001 0 10-2 0z"></path></g>
</svg>
<div id="svg-container">
<svg class="users-logo" version="1.1" viewBox="0 0 20 20" x="0px" y="0px"><g><path fill-rule="evenodd" d="M7 2a4 4 0 00-1.015 7.87c-.098.64-.651 1.13-1.318 1.13A2.667 2.667 0 002 13.667V18h2v-4.333c0-.368.298-.667.667-.667.908 0 1.732-.363 2.333-.953.601.59 1.425.953 2.333.953.369 0 .667.299.667.667V18h2v-4.333A2.667 2.667 0 009.333 11c-.667 0-1.22-.49-1.318-1.13A4.002 4.002 0 007 2zM5 6a2 2 0 104 0 2 2 0 00-4 0z" clip-rule="evenodd"></path><path d="M14 11.83V18h4v-3.75c0-.69-.56-1.25-1.25-1.25a.75.75 0 01-.75-.75v-.42a3.001 3.001 0 10-2 0z"></path></g>
</svg>
</div>
<noscript><div class="users-logo"><img src="/static/external/users.png"></div></noscript>
</label>
</div>
@ -76,7 +83,7 @@
</div>
<div id="chat-window">
<div id="chat-container"><noscript><iframe id="chat" name="chat" src="{{ url_for('chat_iframe', token=token) }}"></iframe></noscript></div>
<div id="users-container"><noscript><iframe name="users" src="{{ url_for('users') }}"></iframe></noscript></div>
<div id="users-container"><noscript><iframe name="users" src="{{ url_for('users', token=token) }}"></iframe></noscript></div>
</div>
<iframe style="height:6em;border-top:1px solid #434343;padding-top:0.5em;" src="{{ url_for('comment_iframe', token=token) }}"></iframe>
</div>
@ -96,11 +103,6 @@
replaceFrameURL("stream-info-container", "{{ url_for('stream_info', token=token) }}", "stream-info", "");
replaceFrameURL("chat-container", "{{ url_for('chat_iframe') }}?token={{ token }}&users=1", "chat", "chat");
/* ensure that svg only appears when scripts are enabled */
for ( element of document.querySelectorAll("svg") ) {
element.style.display = null;
}
const chat = document.getElementById("chat");
const usersToggle = document.getElementById("users-toggle");
usersToggle.onchange = function() {
@ -122,4 +124,4 @@
</script>
<script src="/static/platform.js"></script>
</body>
</html>
</html>

ファイルの表示

@ -1,8 +1,8 @@
<html>
<head>
<meta http-equiv="refresh" content="8">
{% if not debug %}<meta http-equiv="refresh" content="8">{% endif %}
<style>
body {color:white;margin-left: 0.5em;}
body {color:white;margin-left: 1em;}
.group {margin-bottom:1.5em;}
.group-name {margin-bottom:0.25em;}
.person {margin-left: 0.5em;}
@ -21,21 +21,26 @@
</style>
</head>
<body>
{% with broadcaster = people['broadcaster'] %}
{% if broadcaster %}
{% with person = people['broadcaster'] %}
{% if person %}
<div class="group">
<div class="group-name">Broadcaster</div>
<div class="person">
<span class="camera" title="Broadcaster">🎥</span><span class="name" style="color:#{{ broadcaster['colour'].hex() }};">{{ broadcaster['nickname'] or default_nickname(broadcaster['token']) }}</span>{% if broadcaster['tripcode']['string'] %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ broadcaster['tripcode']['string'] }}</div>{% endif %}
<span class="camera" title="Broadcaster">🎥</span><span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}</span>{% if person['tripcode']['string'] %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}
</div>
</div>
{% endif %}
{% endwith %}
{% if broadcaster %}
<form action="{{ url_for('mod_users') }}" method="post">
<input type="hidden" name="noscript" value="1">
<input type="hidden" name="banned" value="1">
{% endif %}
<div class="group">
<div class="group-name">Users watching ({{ len(people['watching']) }})</div>
{% for person in people['watching'] %}
<div class="person">
<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
{% if broadcaster %}<input type="checkbox" name="token[]" value="{{ person['token'] }}">{% endif %}<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
</div>
{% endfor %}
</div>
@ -43,9 +48,27 @@
<div class="group-name">Users not watching ({{ len(people['not_watching']) }})</div>
{% for person in people['not_watching'] %}
<div class="person">
<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
{% if broadcaster %}<input type="checkbox" name="token[]" value="{{ person['token'] }}">{% endif %}<span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}{% if person['token'] == token %}<span style="margin-left:0.5em;color:white;">(You)</span>{% endif %}
</div>
{% endfor %}
</div>
{% if broadcaster %}
<button>Ban</button>
</form>
<div style="margin:1.75em 0 1.5em 0;border-bottom:1px solid #3f3f3f;"></div>
<form action="{{ url_for('mod_users') }}" method="post">
<input type="hidden" name="noscript" value="1">
<input type="hidden" name="banned" value="0">
<div class="group-name">Banned ({{ len(people['banned']) }})</div>
<div class="group">
{% for person in people['banned'] %}
<div class="person">
<input type="checkbox" name="token[]" value="{{ person['token'] }}"><span class="name" style="color:#{{ person['colour'].hex() }};">{{ person['nickname'] or default_nickname(person['token']) }}{% with tag = person['nickname'] == None %}{% if tag %}<sup>{{ person['tag'] }}</sup>{% endif %}</span>{% if person['tripcode']['string'] %}{% if tag %}<span style="margin-right:0.125em;"></span>{% endif %}<div class="tripcode" style="background-color:#{{ person['tripcode']['background_colour'].hex() }};color:#{{ person['tripcode']['foreground_colour'].hex() }};">{{ person['tripcode']['string'] }}</div>{% endif %}{% endwith %}
</div>
{% endfor %}
</div>
<button>Unban</button>
</form>
{% endif %}
</body>
</html>
</html>

ファイルの表示

@ -38,5 +38,5 @@ def gen_colour(seed, background=BACKGROUND_COLOUR, *avoid):
return best_colour
def tag(colour):
tag = ((colour[2] & 0xf0) >> 2) | ((colour[1] & 0xf0) >> 6) | ((colour[2] & 0xf0) >> 10)
tag = ((colour[2] & 0xf0) >> 4) | (colour[1] & 0xf0) | ((colour[0] & 0xf0) << 4)
return f'#{tag:03x}'

ファイルの表示

@ -28,6 +28,7 @@ def setdefault(token):
return
viewers[token] = {'token': token,
'last_comment': float('-inf'),
'last_segment': float('-inf'),
'last_request': float('-inf'),
'first_request': float('-inf'),
'verified': False,
@ -62,11 +63,15 @@ def view_segment(n, token=None, check_exists=True):
if check_exists and not os.path.isfile(os.path.join(SEGMENTS_DIR, f'stream{n}.m4s')):
return
now = int(time.time())
with lock:
now = int(time.time())
segment_views.setdefault(n, []).append({'time': now, 'token': token})
if token:
made_request(token)
setdefault(token)
if viewers[token]['first_request'] == float('-inf'):
viewers[token]['first_request'] = now
viewers[token]['last_request'] = now
viewers[token]['last_segment'] = now
print(f'seg{n}: {token}')
#def count_site_tokens():
@ -155,11 +160,16 @@ def get_people_list():
users = filter(lambda token: now - viewers[token]['last_request'] < 24, users)
users = sorted(users, key=lambda token: viewers[token]['first_request'])
people = {'broadcaster': None, 'users': []}
people = {'broadcaster': None, 'watching': [], 'not_watching': [], 'banned': []}
for token in users:
if viewers[token]['broadcaster']:
people['broadcaster'] = viewers[token]
person = viewers[token]
if person['broadcaster']:
people['broadcaster'] = person
elif now - person['last_segment'] < 24:
people['watching'].append(person)
else:
people['users'].append(viewers[token])
people['not_watching'].append(person)
if person['banned']:
people['banned'].append(person)
return people