Nojs chat: add list of watching/non-watching users
このコミットが含まれているのは:
コミット
1b26ddb816
|
@ -3,7 +3,7 @@ from quart import current_app, request, render_template, redirect, url_for, esca
|
|||
from anonstream.captcha import get_random_captcha_digest_for
|
||||
from anonstream.chat import add_chat_message, Rejected
|
||||
from anonstream.stream import get_stream_title, get_stream_uptime, get_stream_viewership
|
||||
from anonstream.user import add_state, pop_state, try_change_appearance, verify, deverify, BadCaptcha
|
||||
from anonstream.user import add_state, pop_state, try_change_appearance, get_users_by_presence, verify, deverify, BadCaptcha
|
||||
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
||||
from anonstream.helpers.chat import get_scrollback
|
||||
from anonstream.helpers.user import get_default_name, Presence
|
||||
|
@ -41,6 +41,18 @@ async def nojs_chat(user):
|
|||
async def nojs_chat_redirect(user):
|
||||
return redirect(url_for('nojs_chat', _anchor='end'))
|
||||
|
||||
@current_app.route('/chat/users.html')
|
||||
@with_user_from(request)
|
||||
async def nojs_users(user):
|
||||
users_by_presence = get_users_by_presence()
|
||||
return await render_template(
|
||||
'nojs_users.html',
|
||||
user=user,
|
||||
get_default_name=get_default_name,
|
||||
users_watching=users_by_presence[Presence.WATCHING],
|
||||
users_notwatching=users_by_presence[Presence.NOTWATCHING],
|
||||
)
|
||||
|
||||
@current_app.route('/chat/form.html')
|
||||
@with_user_from(request)
|
||||
async def nojs_form(user):
|
||||
|
@ -113,23 +125,24 @@ async def nojs_submit_message(user):
|
|||
@with_user_from(request)
|
||||
async def nojs_submit_appearance(user):
|
||||
form = await request.form
|
||||
name = form.get('name', '').strip()
|
||||
color = form.get('color', '')
|
||||
password = form.get('password', '')
|
||||
want_delete_tripcode = form.get('clear-tripcode', type=bool)
|
||||
want_change_tripcode = form.get('set-tripcode', type=bool)
|
||||
|
||||
# Collect form data
|
||||
name = form.get('name', '').strip()
|
||||
if len(name) == 0 or name == get_default_name(user):
|
||||
name = None
|
||||
|
||||
errors = try_change_appearance(
|
||||
user,
|
||||
name,
|
||||
color,
|
||||
password,
|
||||
want_delete_tripcode,
|
||||
want_change_tripcode,
|
||||
)
|
||||
color = form.get('color', '')
|
||||
password = form.get('password', '')
|
||||
|
||||
if form.get('clear-tripcode', type=bool):
|
||||
want_tripcode = False
|
||||
elif form.get('set-tripcode', type=bool):
|
||||
want_tripcode = True
|
||||
else:
|
||||
want_tripcode = None
|
||||
|
||||
# Change appearance (iff form data was good)
|
||||
errors = try_change_appearance(user, name, color, password, want_tripcode)
|
||||
if errors:
|
||||
notice = Markup('<br>').join(
|
||||
concatenate_for_notice(*error.args) for error in errors
|
||||
|
|
|
@ -30,7 +30,7 @@ body {
|
|||
grid-auto-rows: var(--video-height) auto min-content 1fr auto;
|
||||
grid-template-areas:
|
||||
"stream"
|
||||
"toggle"
|
||||
"nav"
|
||||
"info"
|
||||
"chat"
|
||||
"footer";
|
||||
|
@ -93,11 +93,61 @@ noscript {
|
|||
grid-area: chat;
|
||||
height: 50vh;
|
||||
min-height: 24ch;
|
||||
position: relative;
|
||||
}
|
||||
#chat__toggle {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: calc(0.5rem + 1px);
|
||||
left: calc(0.5rem + 4px);
|
||||
margin: 0;
|
||||
}
|
||||
#chat__toggle:checked ~ #chat__messages,
|
||||
#chat__toggle:not(:checked) ~ #chat__users {
|
||||
display: none;
|
||||
}
|
||||
#chat__toggle:checked + #chat__header > #chat__header__button {
|
||||
border-style: inset;
|
||||
background-color: #d0d0d7;
|
||||
}
|
||||
#chat__toggle:checked + #chat__header > #chat__header__button:hover {
|
||||
background-color: #c0c0c8;
|
||||
}
|
||||
#chat__toggle:focus-visible + #chat__header > #chat__header__button {
|
||||
border-color: #3584e4;
|
||||
box-shadow: 0 0 6px #3584e4;
|
||||
}
|
||||
#chat__header {
|
||||
text-align: center;
|
||||
padding: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
border-bottom: var(--chat-border);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
}
|
||||
#chat__header__button {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: min-content;
|
||||
z-index: 1;
|
||||
padding: 1px 5px;
|
||||
background-color: #e9e9ed;
|
||||
border: 3px outset #8f8f9d;
|
||||
border-radius: 4px;
|
||||
color: black;
|
||||
user-select: none;
|
||||
}
|
||||
#chat__header__button:hover {
|
||||
background-color: #d0d0d7;
|
||||
}
|
||||
#chat__header__button:active {
|
||||
border-style: inset;
|
||||
background-color: #b1b1b9 !important;
|
||||
}
|
||||
#chat__header__text {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
#chat__messages {
|
||||
position: relative;
|
||||
|
@ -153,6 +203,9 @@ noscript {
|
|||
font-size: 9pt;
|
||||
cursor: default;
|
||||
}
|
||||
#chat-users_nojs {
|
||||
height: 100%;
|
||||
}
|
||||
#chat-form_js {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content min-content 5rem;
|
||||
|
@ -233,13 +286,13 @@ noscript {
|
|||
100% {filter: brightness(100%)}
|
||||
}
|
||||
|
||||
#toggle {
|
||||
grid-area: toggle;
|
||||
#nav {
|
||||
grid-area: nav;
|
||||
border-top: var(--main-border);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
#toggle > a {
|
||||
#nav > a {
|
||||
text-align: center;
|
||||
padding: 1ch;
|
||||
font-variant: all-small-caps;
|
||||
|
@ -266,9 +319,9 @@ footer {
|
|||
#chat:target, #both:target > #chat {
|
||||
display: grid;
|
||||
}
|
||||
#info:target ~ #toggle > [href="#info"],
|
||||
#chat:target ~ #toggle > [href="#chat"],
|
||||
#both:target > #toggle > [href="#both"] {
|
||||
#info:target ~ #nav > [href="#info"],
|
||||
#chat:target ~ #nav > [href="#chat"],
|
||||
#both:target > #nav > [href="#both"] {
|
||||
background-color: #3065a6;
|
||||
border-style: inset;
|
||||
}
|
||||
|
@ -291,7 +344,7 @@ footer {
|
|||
"info chat"
|
||||
"footer chat";
|
||||
}
|
||||
#toggle {
|
||||
#nav {
|
||||
display: none;
|
||||
}
|
||||
#info {
|
||||
|
|
|
@ -10,15 +10,22 @@
|
|||
<noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
</article>
|
||||
<aside id="chat">
|
||||
<header id="chat__header">Stream chat</header>
|
||||
<input id="chat__toggle" type="checkbox">
|
||||
<header id="chat__header">
|
||||
<label id="chat__header__button" for="chat__toggle">Users</label>
|
||||
<h3 id="chat__header__text">Stream chat</h3>
|
||||
</header>
|
||||
<article id="chat__messages">
|
||||
<noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat', token=user.token, _anchor='end') }}" data-js="false"></iframe></noscript>
|
||||
</article>
|
||||
<article id="chat__users">
|
||||
<noscript><iframe id="chat-users_nojs" src="{{ url_for('nojs_users', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
</article>
|
||||
<section id="chat__form">
|
||||
<noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_form', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
</section>
|
||||
</aside>
|
||||
<nav id="toggle">
|
||||
<nav id="nav">
|
||||
<a href="#info">info</a>
|
||||
<a href="#chat">chat</a>
|
||||
<a href="#both">both</a>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{%
|
||||
macro appearance(
|
||||
user,
|
||||
name_class,
|
||||
tag_class,
|
||||
tripcode_nbsp_class='for-tripcode',
|
||||
tripcode_class='tripcode for-tripcode'
|
||||
)
|
||||
%}
|
||||
{{- '' -}}
|
||||
<span class="{{ name_class }}" style="color:{{ user.color }};">
|
||||
{{- user.name or get_default_name(user) -}}
|
||||
{%- if not user.broadcaster and user.name is none -%}
|
||||
<sup class="{{ tag_class }}">{{ user.tag }}</sup>
|
||||
{%- endif -%}
|
||||
</span>
|
||||
{%- if user.tripcode -%}
|
||||
<span class="{{ tripcode_nbsp_class }}"> </span>
|
||||
{{- '' -}}
|
||||
<span class="{{ tripcode_class }}" style="background-color:{{ user.tripcode.background_color }};color:{{ user.tripcode.foreground_color }};">{{ user.tripcode.digest }}</span>
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
|
@ -1,3 +1,4 @@
|
|||
{% from 'macros/user.html' import appearance with context %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -138,17 +139,7 @@
|
|||
{% with user = users_by_token[message.token] %}
|
||||
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
||||
{{- ' ' | safe -}}
|
||||
<span class="chat-message__name" style="color:{{ user.color }};">
|
||||
{{- user.name or get_default_name(user) -}}
|
||||
{%- if not user.broadcaster and user.name is none -%}
|
||||
<sup class="chat-message__name__tag">{{ user.tag }}</sup>
|
||||
{%- endif -%}
|
||||
</span>
|
||||
{%- if user.tripcode -%}
|
||||
<span class="for-tripcode"> </span>
|
||||
{{- '' -}}
|
||||
<span class="tripcode for-tripcode" style="background-color:{{ user.tripcode.background_color }};color:{{ user.tripcode.foreground_color }};">{{ user.tripcode.digest }}</span>
|
||||
{%- endif -%}
|
||||
{{ appearance(user, name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
||||
{{- ': ' | safe -}}
|
||||
<span class="chat-message__markup">{{ message.markup }}</span>
|
||||
{% endwith %}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
{% from 'macros/user.html' import appearance with context %}
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
background: linear-gradient(#121214 calc(100% - 0.625rem), #232327 calc(100% - 0.125rem));
|
||||
color: #ddd;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#header {
|
||||
padding: 0.5rem;
|
||||
background-color: #27272a;
|
||||
border-bottom: 1px solid #4a4a4f;
|
||||
}
|
||||
#header > h4 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
#main {
|
||||
margin: 0.5rem 0.75rem;
|
||||
}
|
||||
#main > h5 {
|
||||
margin: 0;
|
||||
}
|
||||
#main > ul {
|
||||
margin: 0;
|
||||
padding-left: 0.75rem;
|
||||
list-style: none;
|
||||
}
|
||||
.user {
|
||||
line-height: 1.5;
|
||||
}
|
||||
.user__name {
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
.user__name__tag {
|
||||
font-family: monospace;
|
||||
font-size: 9pt;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tripcode {
|
||||
padding: 0 5px;
|
||||
border-radius: 7px;
|
||||
font-family: monospace;
|
||||
font-size: 9pt;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header id="header"><h4>Users in chat</h4></header>
|
||||
<main id="main">
|
||||
<h5>Watching ({{ users_watching | length }})</h5>
|
||||
<ul>
|
||||
{% for user_listed in users_watching %}
|
||||
<li class="user">
|
||||
{{- appearance(user_listed, name_class='user__name', tag_class='user__name__tag') -}}
|
||||
{%- if user.token == user_listed.token %} (You){% endif -%}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<br>
|
||||
<h5>Not watching ({{ users_notwatching | length }})</h5>
|
||||
<ul>
|
||||
{% for user_listed in users_notwatching %}
|
||||
<li class="user">
|
||||
{{- appearance(user_listed, name_class='user__name', tag_class='user__name__tag') -}}
|
||||
{%- if user.token == user_listed.token %} (You){% endif -%}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
|
@ -4,7 +4,7 @@ from math import inf
|
|||
from quart import current_app
|
||||
|
||||
from anonstream.wrappers import try_except_log, with_timestamp
|
||||
from anonstream.helpers.user import is_visible
|
||||
from anonstream.helpers.user import is_visible, get_presence, Presence
|
||||
from anonstream.helpers.captcha import check_captcha_digest, Answer
|
||||
from anonstream.helpers.tripcode import generate_tripcode
|
||||
from anonstream.utils.colour import color_to_colour, get_contrast, NotAColor
|
||||
|
@ -36,25 +36,30 @@ def pop_state(user, state_id):
|
|||
state = None
|
||||
return state
|
||||
|
||||
def try_change_appearance(user, name, color, password,
|
||||
want_delete_tripcode, want_change_tripcode):
|
||||
def try_change_appearance(user, name, color, password, want_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:
|
||||
if want_tripcode:
|
||||
try_(change_tripcode, user, password, dry_run=True)
|
||||
|
||||
if not errors:
|
||||
change_name(user, name)
|
||||
change_color(user, color)
|
||||
if want_delete_tripcode:
|
||||
|
||||
# Leave tripcode
|
||||
if want_tripcode is None:
|
||||
pass
|
||||
|
||||
# Delete tripcode
|
||||
elif not want_tripcode:
|
||||
delete_tripcode(user)
|
||||
elif want_change_tripcode:
|
||||
|
||||
# Change tripcode
|
||||
elif want_tripcode:
|
||||
change_tripcode(user, password)
|
||||
|
||||
# Add to the users update buffer
|
||||
|
@ -153,3 +158,16 @@ def deverify(timestamp, user):
|
|||
|
||||
if n_user_messages >= CONFIG['FLOOD_THRESHOLD']:
|
||||
user['verified'] = False
|
||||
|
||||
@with_timestamp
|
||||
def get_users_by_presence(timestamp):
|
||||
users_by_presence = {
|
||||
Presence.WATCHING: [],
|
||||
Presence.NOTWATCHING: [],
|
||||
Presence.TENTATIVE: [],
|
||||
Presence.ABSENT: [],
|
||||
}
|
||||
for user in USERS:
|
||||
presence = get_presence(timestamp, user)
|
||||
users_by_presence[presence].append(user)
|
||||
return users_by_presence
|
||||
|
|
読み込み中…
新しいイシューから参照