コミットを比較
6 コミット
3e7df5b6e3
...
b6cc4ea3b3
作成者 | SHA1 | 日付 |
---|---|---|
n9k | b6cc4ea3b3 | |
n9k | 59d3ce73bd | |
n9k | 947a262f52 | |
n9k | 9bdfdfeb3a | |
n9k | d990ba3ead | |
n9k | 4935a480a0 |
|
@ -2,7 +2,7 @@ from quart import current_app, request, render_template, redirect, url_for, esca
|
||||||
|
|
||||||
from anonstream.captcha import get_random_captcha_digest_for
|
from anonstream.captcha import get_random_captcha_digest_for
|
||||||
from anonstream.chat import add_chat_message, Rejected
|
from anonstream.chat import add_chat_message, Rejected
|
||||||
from anonstream.stream import get_stream_title, get_stream_uptime, get_stream_viewership
|
from anonstream.stream import get_stream_title, get_stream_uptime_and_viewership
|
||||||
from anonstream.user import add_state, pop_state, try_change_appearance, get_users_by_presence, Presence, verify, deverify, BadCaptcha
|
from anonstream.user import add_state, pop_state, try_change_appearance, get_users_by_presence, Presence, verify, deverify, BadCaptcha
|
||||||
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
||||||
from anonstream.helpers.chat import get_scrollback
|
from anonstream.helpers.chat import get_scrollback
|
||||||
|
@ -16,11 +16,12 @@ USERS_BY_TOKEN = current_app.users_by_token
|
||||||
@current_app.route('/info.html')
|
@current_app.route('/info.html')
|
||||||
@with_user_from(request)
|
@with_user_from(request)
|
||||||
async def nojs_info(user):
|
async def nojs_info(user):
|
||||||
|
uptime, viewership = get_stream_uptime_and_viewership()
|
||||||
return await render_template(
|
return await render_template(
|
||||||
'nojs_info.html',
|
'nojs_info.html',
|
||||||
user=user,
|
user=user,
|
||||||
viewership=get_stream_viewership(),
|
viewership=viewership,
|
||||||
uptime=get_stream_uptime(),
|
uptime=uptime,
|
||||||
title=await get_stream_title(),
|
title=await get_stream_title(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,12 +46,13 @@ async def nojs_chat_redirect(user):
|
||||||
@with_user_from(request)
|
@with_user_from(request)
|
||||||
async def nojs_users(user):
|
async def nojs_users(user):
|
||||||
users_by_presence = get_users_by_presence()
|
users_by_presence = get_users_by_presence()
|
||||||
return await render_template(
|
return await render_template_with_etag(
|
||||||
'nojs_users.html',
|
'nojs_users.html',
|
||||||
user=user,
|
user=user,
|
||||||
get_default_name=get_default_name,
|
get_default_name=get_default_name,
|
||||||
users_watching=users_by_presence[Presence.WATCHING],
|
users_watching=users_by_presence[Presence.WATCHING],
|
||||||
users_notwatching=users_by_presence[Presence.NOTWATCHING],
|
users_notwatching=users_by_presence[Presence.NOTWATCHING],
|
||||||
|
timeout=CONFIG['THRESHOLD_NOJS_CHAT_TIMEOUT'],
|
||||||
)
|
)
|
||||||
|
|
||||||
@current_app.route('/chat/form.html')
|
@current_app.route('/chat/form.html')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* token */
|
/* token */
|
||||||
const token = document.body.dataset.token;
|
const TOKEN = document.body.dataset.token;
|
||||||
|
const TOKEN_HASH = document.body.dataset.tokenHash;
|
||||||
|
|
||||||
/* insert js-only markup */
|
/* insert js-only markup */
|
||||||
const jsmarkup_style_color = '<style id="style-color"></style>'
|
const jsmarkup_style_color = '<style id="style-color"></style>'
|
||||||
|
@ -7,10 +8,19 @@ const jsmarkup_style_tripcode_display = '<style id="style-tripcode-display"></st
|
||||||
const jsmarkup_style_tripcode_colors = '<style id="style-tripcode-colors"></style>'
|
const jsmarkup_style_tripcode_colors = '<style id="style-tripcode-colors"></style>'
|
||||||
const jsmarkup_info = '<div id="info_js" data-js="true"></div>';
|
const jsmarkup_info = '<div id="info_js" data-js="true"></div>';
|
||||||
const jsmarkup_info_float = '<aside id="info_js__float"></aside>';
|
const jsmarkup_info_float = '<aside id="info_js__float"></aside>';
|
||||||
|
const jsmarkup_info_float_button = '<button id="info_js__float__button">Reload stream</button>';
|
||||||
const jsmarkup_info_float_viewership = '<div id="info_js__float__viewership"></div>';
|
const jsmarkup_info_float_viewership = '<div id="info_js__float__viewership"></div>';
|
||||||
const jsmarkup_info_float_uptime = '<div id="info_js__float__uptime"></div>';
|
const jsmarkup_info_float_uptime = '<div id="info_js__float__uptime"></div>';
|
||||||
const jsmarkup_info_title = '<header id="info_js__title"></header>';
|
const jsmarkup_info_title = '<header id="info_js__title"></header>';
|
||||||
const jsmarkup_chat_messages = '<ol id="chat-messages_js" data-js="true"></ol>';
|
const jsmarkup_chat_messages = '<ol id="chat-messages_js" data-js="true"></ol>';
|
||||||
|
const jsmarkup_chat_users = `\
|
||||||
|
<article id="chat-users_js">
|
||||||
|
<h5 id="chat-users_js__watching-header"></h5>
|
||||||
|
<ul id="chat-users_js__watching"></ul>
|
||||||
|
<br>
|
||||||
|
<h5 id="chat-users_js__notwatching-header"></h5>
|
||||||
|
<ul id="chat-users_js__notwatching"></ul>
|
||||||
|
</article>`;
|
||||||
const jsmarkup_chat_form = `\
|
const jsmarkup_chat_form = `\
|
||||||
<form id="chat-form_js" data-js="true" action="/chat" method="post">
|
<form id="chat-form_js" data-js="true" action="/chat" method="post">
|
||||||
<input id="chat-form_js__nonce" type="hidden" name="nonce" value="">
|
<input id="chat-form_js__nonce" type="hidden" name="nonce" value="">
|
||||||
|
@ -46,6 +56,10 @@ const insert_jsmarkup = () => {jsmarkup_info_float_viewership
|
||||||
const parent = document.getElementById("info_js");
|
const parent = document.getElementById("info_js");
|
||||||
parent.insertAdjacentHTML("beforeend", jsmarkup_info_float);
|
parent.insertAdjacentHTML("beforeend", jsmarkup_info_float);
|
||||||
}
|
}
|
||||||
|
if (document.getElementById("info_js__float__button") === null) {
|
||||||
|
const parent = document.getElementById("info_js__float");
|
||||||
|
parent.insertAdjacentHTML("beforeend", jsmarkup_info_float_button);
|
||||||
|
}
|
||||||
if (document.getElementById("info_js__float__viewership") === null) {
|
if (document.getElementById("info_js__float__viewership") === null) {
|
||||||
const parent = document.getElementById("info_js__float");
|
const parent = document.getElementById("info_js__float");
|
||||||
parent.insertAdjacentHTML("beforeend", jsmarkup_info_float_viewership);
|
parent.insertAdjacentHTML("beforeend", jsmarkup_info_float_viewership);
|
||||||
|
@ -58,8 +72,12 @@ const insert_jsmarkup = () => {jsmarkup_info_float_viewership
|
||||||
const parent = document.getElementById("info_js");
|
const parent = document.getElementById("info_js");
|
||||||
parent.insertAdjacentHTML("beforeend", jsmarkup_info_title);
|
parent.insertAdjacentHTML("beforeend", jsmarkup_info_title);
|
||||||
}
|
}
|
||||||
|
if (document.getElementById("chat-users_js") === null) {
|
||||||
|
const parent = document.getElementById("chat__body__users");
|
||||||
|
parent.insertAdjacentHTML("beforeend", jsmarkup_chat_users);
|
||||||
|
}
|
||||||
if (document.getElementById("chat-messages_js") === null) {
|
if (document.getElementById("chat-messages_js") === null) {
|
||||||
const parent = document.getElementById("chat__messages");
|
const parent = document.getElementById("chat__body__messages");
|
||||||
parent.insertAdjacentHTML("beforeend", jsmarkup_chat_messages);
|
parent.insertAdjacentHTML("beforeend", jsmarkup_chat_messages);
|
||||||
}
|
}
|
||||||
if (document.getElementById("chat-form_js") === null) {
|
if (document.getElementById("chat-form_js") === null) {
|
||||||
|
@ -78,6 +96,10 @@ const info_title = document.getElementById("info_js__title");
|
||||||
const info_viewership = document.getElementById("info_js__float__viewership");
|
const info_viewership = document.getElementById("info_js__float__viewership");
|
||||||
const info_uptime = document.getElementById("info_js__float__uptime");
|
const info_uptime = document.getElementById("info_js__float__uptime");
|
||||||
const chat_messages = document.getElementById("chat-messages_js");
|
const chat_messages = document.getElementById("chat-messages_js");
|
||||||
|
const chat_users_watching = document.getElementById("chat-users_js__watching");
|
||||||
|
const chat_users_watching_header = document.getElementById("chat-users_js__watching-header");
|
||||||
|
const chat_users_notwatching = document.getElementById("chat-users_js__notwatching");
|
||||||
|
const chat_users_notwatching_header = document.getElementById("chat-users_js__notwatching-header");
|
||||||
|
|
||||||
const create_chat_message = (object) => {
|
const create_chat_message = (object) => {
|
||||||
const user = users[object.token_hash];
|
const user = users[object.token_hash];
|
||||||
|
@ -93,18 +115,11 @@ const create_chat_message = (object) => {
|
||||||
chat_message_time.title = `${object.date} ${object.time_seconds}`;
|
chat_message_time.title = `${object.date} ${object.time_seconds}`;
|
||||||
chat_message_time.innerText = object.time_minutes;
|
chat_message_time.innerText = object.time_minutes;
|
||||||
|
|
||||||
const chat_message_name = create_chat_message_name(user);
|
const [
|
||||||
|
chat_message_name,
|
||||||
const chat_message_tripcode_nbsp = document.createElement("span");
|
chat_message_tripcode_nbsp,
|
||||||
chat_message_tripcode_nbsp.classList.add("for-tripcode");
|
chat_message_tripcode,
|
||||||
chat_message_tripcode_nbsp.innerHTML = " ";
|
] = create_chat_user_components(user);
|
||||||
|
|
||||||
const chat_message_tripcode = document.createElement("span");
|
|
||||||
chat_message_tripcode.classList.add("tripcode");
|
|
||||||
chat_message_tripcode.classList.add("for-tripcode");
|
|
||||||
if (user.tripcode !== null) {
|
|
||||||
chat_message_tripcode.innerHTML = user.tripcode.digest;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chat_message_markup = document.createElement("span");
|
const chat_message_markup = document.createElement("span");
|
||||||
chat_message_markup.classList.add("chat-message__markup");
|
chat_message_markup.classList.add("chat-message__markup");
|
||||||
|
@ -120,18 +135,34 @@ const create_chat_message = (object) => {
|
||||||
|
|
||||||
return chat_message;
|
return chat_message;
|
||||||
}
|
}
|
||||||
const create_chat_message_name = (user) => {
|
const create_chat_user_name = (user) => {
|
||||||
const chat_message_name = document.createElement("span");
|
const chat_user_name = document.createElement("span");
|
||||||
chat_message_name.classList.add("chat-message__name");
|
chat_user_name.classList.add("chat-name");
|
||||||
chat_message_name.innerText = get_user_name({user});
|
chat_user_name.innerText = get_user_name({user});
|
||||||
//chat_message_name.dataset.color = user.color; // not working in any browser
|
//chat_user_name.dataset.color = user.color; // not working in any browser
|
||||||
if (!user.broadcaster && user.name === null) {
|
if (!user.broadcaster && user.name === null) {
|
||||||
const chat_message_name_tag = document.createElement("sup");
|
const chat_user_name_tag = document.createElement("sup");
|
||||||
chat_message_name_tag.classList.add("chat-message__name__tag");
|
chat_user_name_tag.classList.add("chat-name__tag");
|
||||||
chat_message_name_tag.innerText = user.tag;
|
chat_user_name_tag.innerText = user.tag;
|
||||||
chat_message_name.insertAdjacentElement("beforeend", chat_message_name_tag);
|
chat_user_name.insertAdjacentElement("beforeend", chat_user_name_tag);
|
||||||
}
|
}
|
||||||
return chat_message_name;
|
return chat_user_name;
|
||||||
|
}
|
||||||
|
const create_chat_user_components = (user) => {
|
||||||
|
const chat_user_name = create_chat_user_name(user);
|
||||||
|
|
||||||
|
const chat_user_tripcode_nbsp = document.createElement("span");
|
||||||
|
chat_user_tripcode_nbsp.classList.add("for-tripcode");
|
||||||
|
chat_user_tripcode_nbsp.innerHTML = " ";
|
||||||
|
|
||||||
|
const chat_user_tripcode = document.createElement("span");
|
||||||
|
chat_user_tripcode.classList.add("tripcode");
|
||||||
|
chat_user_tripcode.classList.add("for-tripcode");
|
||||||
|
if (user.tripcode !== null) {
|
||||||
|
chat_user_tripcode.innerHTML = user.tripcode.digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode];
|
||||||
}
|
}
|
||||||
const create_and_add_chat_message = (object) => {
|
const create_and_add_chat_message = (object) => {
|
||||||
const chat_message = create_chat_message(object);
|
const chat_message = create_chat_message(object);
|
||||||
|
@ -142,6 +173,8 @@ const create_and_add_chat_message = (object) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let users = {};
|
let users = {};
|
||||||
|
let stats = null;
|
||||||
|
let stats_received = null;
|
||||||
let default_name = {true: "Broadcaster", false: "Anonymous"};
|
let default_name = {true: "Broadcaster", false: "Anonymous"};
|
||||||
let max_chat_scrollback = 256;
|
let max_chat_scrollback = 256;
|
||||||
const tidy_stylesheet = ({stylesheet, selector_regex, ignore_condition}) => {
|
const tidy_stylesheet = ({stylesheet, selector_regex, ignore_condition}) => {
|
||||||
|
@ -172,7 +205,7 @@ const update_user_colors = (token_hash=null) => {
|
||||||
token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
||||||
const {to_delete, to_ignore} = tidy_stylesheet({
|
const {to_delete, to_ignore} = tidy_stylesheet({
|
||||||
stylesheet: stylesheet_color,
|
stylesheet: stylesheet_color,
|
||||||
selector_regex: /\.chat-message\[data-token-hash="([a-z2-7]{26})"\] > \.chat-message__name/,
|
selector_regex: /\[data-token-hash="([a-z2-7]{26})"\] > \.chat-name/,
|
||||||
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
||||||
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
||||||
const correct_color = equal(css_rule.style.color, this_user.color);
|
const correct_color = equal(css_rule.style.color, this_user.color);
|
||||||
|
@ -184,7 +217,7 @@ const update_user_colors = (token_hash=null) => {
|
||||||
if (!to_ignore.has(this_token_hash)) {
|
if (!to_ignore.has(this_token_hash)) {
|
||||||
const user = users[this_token_hash];
|
const user = users[this_token_hash];
|
||||||
stylesheet_color.insertRule(
|
stylesheet_color.insertRule(
|
||||||
`.chat-message[data-token-hash="${this_token_hash}"] > .chat-message__name { color: ${user.color}; }`,
|
`[data-token-hash="${this_token_hash}"] > .chat-name { color: ${user.color}; }`,
|
||||||
stylesheet_color.cssRules.length,
|
stylesheet_color.cssRules.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -204,8 +237,8 @@ const update_user_names = (token_hash=null) => {
|
||||||
const this_token_hash = chat_message.dataset.tokenHash;
|
const this_token_hash = chat_message.dataset.tokenHash;
|
||||||
if (token_hashes.includes(this_token_hash)) {
|
if (token_hashes.includes(this_token_hash)) {
|
||||||
const user = users[this_token_hash];
|
const user = users[this_token_hash];
|
||||||
const chat_message_name = chat_message.querySelector(".chat-message__name");
|
const chat_message_name = chat_message.querySelector(".chat-name");
|
||||||
chat_message_name.innerHTML = create_chat_message_name(user).innerHTML;
|
chat_message_name.innerHTML = create_chat_user_name(user).innerHTML;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +247,7 @@ const update_user_tripcodes = (token_hash=null) => {
|
||||||
token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
||||||
const {to_delete: to_delete_display, to_ignore: to_ignore_display} = tidy_stylesheet({
|
const {to_delete: to_delete_display, to_ignore: to_ignore_display} = tidy_stylesheet({
|
||||||
stylesheet: stylesheet_tripcode_display,
|
stylesheet: stylesheet_tripcode_display,
|
||||||
selector_regex: /\.chat-message\[data-token-hash="([a-z2-7]{26})"\] > \.for-tripcode/,
|
selector_regex: /\[data-token-hash="([a-z2-7]{26})"\] > \.for-tripcode/,
|
||||||
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
||||||
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
||||||
const correctly_hidden = this_user.tripcode === null && css_rule.style.display === "none";
|
const correctly_hidden = this_user.tripcode === null && css_rule.style.display === "none";
|
||||||
|
@ -224,7 +257,7 @@ const update_user_tripcodes = (token_hash=null) => {
|
||||||
});
|
});
|
||||||
const {to_delete: to_delete_colors, to_ignore: to_ignore_colors} = tidy_stylesheet({
|
const {to_delete: to_delete_colors, to_ignore: to_ignore_colors} = tidy_stylesheet({
|
||||||
stylesheet: stylesheet_tripcode_colors,
|
stylesheet: stylesheet_tripcode_colors,
|
||||||
selector_regex: /\.chat-message\[data-token-hash="([a-z2-7]{26})"\] > \.tripcode/,
|
selector_regex: /\[data-token-hash="([a-z2-7]{26})"\] > \.tripcode/,
|
||||||
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
ignore_condition: (this_token_hash, this_user, css_rule) => {
|
||||||
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
const irrelevant = ignore_other_token_hashes && this_token_hash !== token_hash;
|
||||||
const correctly_blank = (
|
const correctly_blank = (
|
||||||
|
@ -247,26 +280,26 @@ const update_user_tripcodes = (token_hash=null) => {
|
||||||
if (tripcode === null) {
|
if (tripcode === null) {
|
||||||
if (!to_ignore_display.has(token_hash)) {
|
if (!to_ignore_display.has(token_hash)) {
|
||||||
stylesheet_tripcode_display.insertRule(
|
stylesheet_tripcode_display.insertRule(
|
||||||
`.chat-message[data-token-hash="${this_token_hash}"] > .for-tripcode { display: none; }`,
|
`[data-token-hash="${this_token_hash}"] > .for-tripcode { display: none; }`,
|
||||||
stylesheet_tripcode_display.cssRules.length,
|
stylesheet_tripcode_display.cssRules.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!to_ignore_colors.has(token_hash)) {
|
if (!to_ignore_colors.has(token_hash)) {
|
||||||
stylesheet_tripcode_colors.insertRule(
|
stylesheet_tripcode_colors.insertRule(
|
||||||
`.chat-message[data-token-hash="${this_token_hash}"] > .tripcode { background-color: initial; color: initial; }`,
|
`[data-token-hash="${this_token_hash}"] > .tripcode { background-color: initial; color: initial; }`,
|
||||||
stylesheet_tripcode_colors.cssRules.length,
|
stylesheet_tripcode_colors.cssRules.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!to_ignore_display.has(token_hash)) {
|
if (!to_ignore_display.has(token_hash)) {
|
||||||
stylesheet_tripcode_display.insertRule(
|
stylesheet_tripcode_display.insertRule(
|
||||||
`.chat-message[data-token-hash="${this_token_hash}"] > .for-tripcode { display: inline; }`,
|
`[data-token-hash="${this_token_hash}"] > .for-tripcode { display: inline; }`,
|
||||||
stylesheet_tripcode_display.cssRules.length,
|
stylesheet_tripcode_display.cssRules.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!to_ignore_colors.has(token_hash)) {
|
if (!to_ignore_colors.has(token_hash)) {
|
||||||
stylesheet_tripcode_colors.insertRule(
|
stylesheet_tripcode_colors.insertRule(
|
||||||
`.chat-message[data-token-hash="${this_token_hash}"] > .tripcode { background-color: ${tripcode.background_color}; color: ${tripcode.foreground_color}; }`,
|
`[data-token-hash="${this_token_hash}"] > .tripcode { background-color: ${tripcode.background_color}; color: ${tripcode.foreground_color}; }`,
|
||||||
stylesheet_tripcode_colors.cssRules.length,
|
stylesheet_tripcode_colors.cssRules.length,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -330,7 +363,7 @@ const enable_captcha = (digest) => {
|
||||||
chat_form_captcha_answer.disabled = false;
|
chat_form_captcha_answer.disabled = false;
|
||||||
chat_form_comment.required = false;
|
chat_form_comment.required = false;
|
||||||
chat_form_captcha_image.removeAttribute("src");
|
chat_form_captcha_image.removeAttribute("src");
|
||||||
chat_form_captcha_image.src = `/captcha.jpg?token=${encodeURIComponent(token)}&digest=${encodeURIComponent(digest)}`;
|
chat_form_captcha_image.src = `/captcha.jpg?token=${encodeURIComponent(TOKEN)}&digest=${encodeURIComponent(digest)}`;
|
||||||
chat_form_submit.disabled = false;
|
chat_form_submit.disabled = false;
|
||||||
chat_form.dataset.captcha = "";
|
chat_form.dataset.captcha = "";
|
||||||
}
|
}
|
||||||
|
@ -353,20 +386,14 @@ const set_title = (title) => {
|
||||||
info_title.innerHTML = element.outerHTML;
|
info_title.innerHTML = element.outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
let frozen_uptime = null;
|
|
||||||
let frozen_uptime_received = null;
|
|
||||||
const set_frozen_uptime = (x) => {
|
|
||||||
frozen_uptime = x;
|
|
||||||
frozen_uptime_received = new Date();
|
|
||||||
}
|
|
||||||
const update_uptime = () => {
|
const update_uptime = () => {
|
||||||
if (frozen_uptime_received === null) {
|
if (stats_received === null) {
|
||||||
return;
|
return;
|
||||||
} else if (frozen_uptime === null) {
|
} else if (stats === null) {
|
||||||
info_uptime.innerText = "";
|
info_uptime.innerText = "";
|
||||||
} else {
|
} else {
|
||||||
const frozen_uptime_received_ago = (new Date() - frozen_uptime_received) / 1000;
|
const stats_received_ago = (new Date() - stats_received) / 1000;
|
||||||
const uptime = Math.round(frozen_uptime + frozen_uptime_received_ago);
|
const uptime = Math.round(stats.uptime + stats_received_ago);
|
||||||
|
|
||||||
const s = Math.round(uptime % 60);
|
const s = Math.round(uptime % 60);
|
||||||
const m = Math.floor(uptime / 60) % 60
|
const m = Math.floor(uptime / 60) % 60
|
||||||
|
@ -383,8 +410,75 @@ const update_uptime = () => {
|
||||||
}
|
}
|
||||||
setInterval(update_uptime, 1000); // always update uptime
|
setInterval(update_uptime, 1000); // always update uptime
|
||||||
|
|
||||||
const set_viewership = (n) => {
|
const update_viewership = () => {
|
||||||
info_viewership.innerText = n === null ? "" : `${n} viewers`;
|
info_viewership.innerText = stats === null ? "" : `${stats.viewership} viewers`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const update_stats = () => {
|
||||||
|
if (stats === null) {
|
||||||
|
update_viewership();
|
||||||
|
update_uptime();
|
||||||
|
} else {
|
||||||
|
update_uptime();
|
||||||
|
update_viewership();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const update_users_list = () => {
|
||||||
|
listed_watching = new Set();
|
||||||
|
listed_notwatching = new Set();
|
||||||
|
|
||||||
|
// remove no-longer-known users
|
||||||
|
for (const element of chat_users_watching.querySelectorAll('.chat-user')) {
|
||||||
|
const token_hash = element.dataset.tokenHash;
|
||||||
|
if (!Object.prototype.hasOwnProperty(users, token_hash)) {
|
||||||
|
element.remove();
|
||||||
|
} else {
|
||||||
|
listed_watching.add(token_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const element of chat_users_notwatching.querySelectorAll('.chat-user')) {
|
||||||
|
const token_hash = element.dataset.tokenHash;
|
||||||
|
if (!Object.prototype.hasOwnProperty(users, token_hash)) {
|
||||||
|
element.remove();
|
||||||
|
} else {
|
||||||
|
listed_notwatching.add(token_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add remaining watching/non-watching users
|
||||||
|
const insert = (user, token_hash, is_you, chat_users_sublist) => {
|
||||||
|
const chat_user_components = create_chat_user_components(user);
|
||||||
|
const chat_user = document.createElement("li");
|
||||||
|
chat_user.classList.add("chat-user");
|
||||||
|
chat_user.dataset.tokenHash = token_hash;
|
||||||
|
for (const chat_user_component of chat_user_components) {
|
||||||
|
chat_user.insertAdjacentElement("beforeend", chat_user_component);
|
||||||
|
}
|
||||||
|
if (is_you) {
|
||||||
|
const you = document.createElement("span");
|
||||||
|
you.innerText = " (You)";
|
||||||
|
chat_user.insertAdjacentElement("beforeend", you);
|
||||||
|
}
|
||||||
|
chat_users_sublist.insertAdjacentElement("beforeend", chat_user);
|
||||||
|
}
|
||||||
|
let watching = 0, notwatching = 0;
|
||||||
|
for (const token_hash of Object.keys(users)) {
|
||||||
|
const user = users[token_hash];
|
||||||
|
const is_you = token_hash === TOKEN_HASH;
|
||||||
|
if (user.watching === true && !listed_watching.has(token_hash)) {
|
||||||
|
insert(user, token_hash, is_you, chat_users_watching);
|
||||||
|
watching++;
|
||||||
|
}
|
||||||
|
if (user.watching === false && !listed_notwatching.has(token_hash)) {
|
||||||
|
insert(user, token_hash, is_you, chat_users_notwatching);
|
||||||
|
notwatching++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show correct numbers
|
||||||
|
chat_users_watching_header.innerText = `Watching (${watching})`;
|
||||||
|
chat_users_notwatching_header.innerText = `Not watching (${notwatching})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const on_websocket_message = (event) => {
|
const on_websocket_message = (event) => {
|
||||||
|
@ -402,12 +496,15 @@ const on_websocket_message = (event) => {
|
||||||
// set title
|
// set title
|
||||||
set_title(receipt.title);
|
set_title(receipt.title);
|
||||||
|
|
||||||
// set viewership
|
// update stats (uptime/viewership)
|
||||||
set_viewership(receipt.viewership);
|
stats = receipt.stats;
|
||||||
|
stats_received = new Date();
|
||||||
|
update_stats();
|
||||||
|
|
||||||
// set uptime
|
// stream reload button
|
||||||
set_frozen_uptime(receipt.uptime);
|
if (stats !== null && [stream.NETWORK_IDLE, stream.NETWORK_NO_SOURCE].includes(stream.networkState)) {
|
||||||
update_uptime();
|
info_button.dataset.visible = "";
|
||||||
|
}
|
||||||
|
|
||||||
// chat form nonce
|
// chat form nonce
|
||||||
chat_form_nonce.value = receipt.nonce;
|
chat_form_nonce.value = receipt.nonce;
|
||||||
|
@ -435,11 +532,12 @@ const on_websocket_message = (event) => {
|
||||||
default_name = receipt.default;
|
default_name = receipt.default;
|
||||||
max_chat_scrollback = receipt.scrollback;
|
max_chat_scrollback = receipt.scrollback;
|
||||||
|
|
||||||
// appearances
|
// update users
|
||||||
users = receipt.users;
|
users = receipt.users;
|
||||||
update_user_names();
|
update_user_names();
|
||||||
update_user_colors();
|
update_user_colors();
|
||||||
update_user_tripcodes();
|
update_user_tripcodes();
|
||||||
|
update_users_list()
|
||||||
|
|
||||||
// insert new messages
|
// insert new messages
|
||||||
const last = chat_messages.children.length == 0 ? null : chat_messages.children[chat_messages.children.length - 1];
|
const last = chat_messages.children.length == 0 ? null : chat_messages.children[chat_messages.children.length - 1];
|
||||||
|
@ -454,18 +552,24 @@ const on_websocket_message = (event) => {
|
||||||
|
|
||||||
case "info":
|
case "info":
|
||||||
console.log("ws info", receipt);
|
console.log("ws info", receipt);
|
||||||
|
|
||||||
|
// set title
|
||||||
if (receipt.title !== undefined) {
|
if (receipt.title !== undefined) {
|
||||||
set_title(receipt.title);
|
set_title(receipt.title);
|
||||||
}
|
}
|
||||||
if (receipt.uptime !== undefined) {
|
|
||||||
set_frozen_uptime(receipt.uptime);
|
// update stats (uptime/viewership)
|
||||||
update_uptime();
|
if (receipt.stats !== undefined) {
|
||||||
|
stats = receipt.stats;
|
||||||
|
stats_received = new Date();
|
||||||
|
update_stats();
|
||||||
}
|
}
|
||||||
if (receipt.viewership === 0 && frozen_uptime === null) {
|
|
||||||
set_viewership(null);
|
// stream reload button
|
||||||
} else if (receipt.viewership !== undefined) {
|
if (stats !== null && [stream.NETWORK_IDLE, stream.NETWORK_NO_SOURCE].includes(stream.networkState)) {
|
||||||
set_viewership(receipt.viewership);
|
info_button.dataset.visible = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ack":
|
case "ack":
|
||||||
|
@ -499,6 +603,7 @@ const on_websocket_message = (event) => {
|
||||||
update_user_names();
|
update_user_names();
|
||||||
update_user_colors();
|
update_user_colors();
|
||||||
update_user_tripcodes();
|
update_user_tripcodes();
|
||||||
|
update_users_list()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "rem-users":
|
case "rem-users":
|
||||||
|
@ -508,6 +613,7 @@ const on_websocket_message = (event) => {
|
||||||
}
|
}
|
||||||
update_user_colors();
|
update_user_colors();
|
||||||
update_user_tripcodes();
|
update_user_tripcodes();
|
||||||
|
update_users_list()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "captcha":
|
case "captcha":
|
||||||
|
@ -530,7 +636,7 @@ const connect_websocket = () => {
|
||||||
}
|
}
|
||||||
chat_live_ball.style.borderColor = "gold";
|
chat_live_ball.style.borderColor = "gold";
|
||||||
chat_live_status.innerHTML = "<span data-verbose='false'>Waiting...</span> <span data-verbose='true'>Connecting to chat...</span>";
|
chat_live_status.innerHTML = "<span data-verbose='false'>Waiting...</span> <span data-verbose='true'>Connecting to chat...</span>";
|
||||||
ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(token)}`);
|
ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(TOKEN)}`);
|
||||||
ws.addEventListener("open", (event) => {
|
ws.addEventListener("open", (event) => {
|
||||||
console.log("websocket open", event);
|
console.log("websocket open", event);
|
||||||
chat_form_submit.disabled = false;
|
chat_form_submit.disabled = false;
|
||||||
|
@ -569,6 +675,20 @@ const connect_websocket = () => {
|
||||||
|
|
||||||
connect_websocket();
|
connect_websocket();
|
||||||
|
|
||||||
|
/* stream reload button */
|
||||||
|
let stream_suspended = null;
|
||||||
|
const stream = document.getElementById("stream");
|
||||||
|
const info_button = document.getElementById("info_js__float__button");
|
||||||
|
info_button.addEventListener("click", (event) => {
|
||||||
|
stream.load();
|
||||||
|
info_button.removeAttribute("data-visible");
|
||||||
|
});
|
||||||
|
stream.addEventListener("error", (event) => {
|
||||||
|
if (stats !== null) {
|
||||||
|
info_button.dataset.visible = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* override js-only chat form */
|
/* override js-only chat form */
|
||||||
const chat_form = document.getElementById("chat-form_js");
|
const chat_form = document.getElementById("chat-form_js");
|
||||||
const chat_form_nonce = document.getElementById("chat-form_js__nonce");
|
const chat_form_nonce = document.getElementById("chat-form_js__nonce");
|
||||||
|
|
|
@ -63,6 +63,7 @@ noscript {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0.75ch 1.25ch;
|
padding: 0.75ch 1.25ch;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#info_js__float {
|
#info_js__float {
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -71,6 +72,9 @@ noscript {
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-gap: 2.5ch;
|
grid-gap: 2.5ch;
|
||||||
}
|
}
|
||||||
|
#info_js__float__button:not([data-visible]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
#info_js__float__uptime {
|
#info_js__float__uptime {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
@ -102,9 +106,9 @@ noscript {
|
||||||
left: calc(0.5rem + 4px);
|
left: calc(0.5rem + 4px);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
#chat__toggle:checked ~ #chat__messages,
|
#chat__toggle:checked ~ #chat__body > #chat__body__messages,
|
||||||
#chat__toggle:not(:checked) ~ #chat__users {
|
#chat__toggle:not(:checked) ~ #chat__body > #chat__body__users {
|
||||||
display: none;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
#chat__toggle:checked + #chat__header > #chat__header__button {
|
#chat__toggle:checked + #chat__header > #chat__header__button {
|
||||||
border-style: inset;
|
border-style: inset;
|
||||||
|
@ -149,9 +153,13 @@ noscript {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
}
|
}
|
||||||
#chat__messages {
|
#chat__body {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
#chat__body__messages {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
#chat-messages_js {
|
#chat-messages_js {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -181,13 +189,13 @@ noscript {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
.chat-message__name {
|
.chat-name {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
/* color: attr("data-color"); */
|
/* color: attr("data-color"); */
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.chat-message__name__tag {
|
.chat-name__tag {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
@ -203,6 +211,43 @@ noscript {
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
#chat__body__users {
|
||||||
|
background-color: #121214;
|
||||||
|
mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem));
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: min-content auto;
|
||||||
|
}
|
||||||
|
#chat-users-header {
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #2c2c30;
|
||||||
|
border-bottom: var(--chat-border);
|
||||||
|
}
|
||||||
|
#chat-users-header > h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#chat-users_js {
|
||||||
|
padding: 0.5rem 0.75rem 0.875rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
#chat-users_js__watching-header,
|
||||||
|
#chat-users_js__notwatching-header {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#chat-users_js__watching,
|
||||||
|
#chat-users_js__notwatching {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.chat-user {
|
||||||
|
line-height: 1.4375;
|
||||||
|
}
|
||||||
#chat-users_nojs {
|
#chat-users_nojs {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,16 +37,26 @@ def get_stream_uptime(rounded=True):
|
||||||
return uptime
|
return uptime
|
||||||
|
|
||||||
@with_timestamp
|
@with_timestamp
|
||||||
def get_stream_viewership(timestamp):
|
def get_raw_viewership(timestamp):
|
||||||
users = get_watching_users(timestamp)
|
users = get_watching_users(timestamp)
|
||||||
return max(
|
return max(
|
||||||
map(operator.itemgetter(0), zip(itertools.count(1), users)),
|
map(operator.itemgetter(0), zip(itertools.count(1), users)),
|
||||||
default=0,
|
default=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_stream_viewership_or_none(uptime):
|
def get_stream_uptime_and_viewership(for_websocket=False):
|
||||||
viewership = get_stream_viewership()
|
uptime = get_stream_uptime()
|
||||||
return uptime and viewership
|
if not for_websocket:
|
||||||
|
viewership = None if uptime is None else get_raw_viewership()
|
||||||
|
result = (uptime, viewership)
|
||||||
|
elif uptime is None:
|
||||||
|
result = None
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
'uptime': uptime,
|
||||||
|
'viewership': get_raw_viewership(),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
def is_online():
|
def is_online():
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -5,7 +5,7 @@ from functools import wraps
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
|
|
||||||
from anonstream.broadcast import broadcast, broadcast_users_update
|
from anonstream.broadcast import broadcast, broadcast_users_update
|
||||||
from anonstream.stream import is_online, get_stream_title, get_stream_uptime, get_stream_viewership_or_none
|
from anonstream.stream import is_online, get_stream_title, get_stream_uptime_and_viewership
|
||||||
from anonstream.user import get_sunsettable_users
|
from anonstream.user import get_sunsettable_users
|
||||||
from anonstream.wrappers import with_timestamp
|
from anonstream.wrappers import with_timestamp
|
||||||
|
|
||||||
|
@ -94,21 +94,22 @@ async def t_broadcast_users_update(iteration):
|
||||||
async def t_broadcast_stream_info_update(iteration):
|
async def t_broadcast_stream_info_update(iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
title = await get_stream_title()
|
title = await get_stream_title()
|
||||||
uptime = get_stream_uptime()
|
uptime, viewership = get_stream_uptime_and_viewership()
|
||||||
viewership = get_stream_viewership_or_none(uptime)
|
|
||||||
current_app.stream_title = title
|
current_app.stream_title = title
|
||||||
current_app.stream_uptime = uptime
|
current_app.stream_uptime = uptime
|
||||||
current_app.stream_viewership = viewership
|
current_app.stream_viewership = viewership
|
||||||
else:
|
else:
|
||||||
payload = {}
|
payload = {}
|
||||||
|
|
||||||
# Check if the stream title has changed
|
|
||||||
title = await get_stream_title()
|
title = await get_stream_title()
|
||||||
|
uptime, viewership = get_stream_uptime_and_viewership()
|
||||||
|
|
||||||
|
# Check if the stream title has changed
|
||||||
if current_app.stream_title != title:
|
if current_app.stream_title != title:
|
||||||
current_app.stream_title = title
|
current_app.stream_title = title
|
||||||
payload['title'] = title
|
payload['title'] = title
|
||||||
|
|
||||||
# Check if the stream uptime has changed more or less than expected
|
# Check if the stream uptime has changed unexpectedly
|
||||||
if current_app.stream_uptime is None:
|
if current_app.stream_uptime is None:
|
||||||
expected_uptime = None
|
expected_uptime = None
|
||||||
else:
|
else:
|
||||||
|
@ -116,20 +117,27 @@ async def t_broadcast_stream_info_update(iteration):
|
||||||
current_app.stream_uptime
|
current_app.stream_uptime
|
||||||
+ CONFIG['TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE']
|
+ CONFIG['TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE']
|
||||||
)
|
)
|
||||||
uptime = get_stream_uptime()
|
|
||||||
current_app.stream_uptime = uptime
|
current_app.stream_uptime = uptime
|
||||||
if uptime is None and expected_uptime is None:
|
if uptime is None and expected_uptime is None:
|
||||||
pass
|
stats_changed = False
|
||||||
elif uptime is None or expected_uptime is None:
|
elif uptime is None or expected_uptime is None:
|
||||||
payload['uptime'] = uptime
|
stats_changed = True
|
||||||
elif abs(uptime - expected_uptime) >= 0.0625:
|
else:
|
||||||
payload['uptime'] = uptime
|
stats_changed = abs(uptime - expected_uptime) >= 0.0625
|
||||||
|
|
||||||
# Check if viewership has changed
|
# Check if viewership has changed
|
||||||
viewership = get_stream_viewership_or_none(uptime)
|
|
||||||
if current_app.stream_viewership != viewership:
|
if current_app.stream_viewership != viewership:
|
||||||
current_app.stream_viewership = viewership
|
current_app.stream_viewership = viewership
|
||||||
payload['viewership'] = viewership
|
stats_changed = True
|
||||||
|
|
||||||
|
if stats_changed:
|
||||||
|
if uptime is None:
|
||||||
|
payload['stats'] = None
|
||||||
|
else:
|
||||||
|
payload['stats'] = {
|
||||||
|
'uptime': uptime,
|
||||||
|
'viewership': viewership,
|
||||||
|
}
|
||||||
|
|
||||||
if payload:
|
if payload:
|
||||||
broadcast(USERS, payload={'type': 'info', **payload})
|
broadcast(USERS, payload={'type': 'info', **payload})
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
<body id="both" data-token="{{ user.token }}">
|
<body id="both" data-token="{{ user.token }}" data-token-hash="{{ user.token_hash }}">
|
||||||
<video id="stream" src="{{ url_for('stream', token=user.token) }}" autoplay controls></video>
|
<video id="stream" src="{{ url_for('stream', token=user.token) }}" autoplay controls></video>
|
||||||
<article id="info">
|
<article id="info">
|
||||||
<noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token) }}" data-js="false"></iframe></noscript>
|
<noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||||
|
@ -15,11 +15,14 @@
|
||||||
<label id="chat__header__button" for="chat__toggle">Users</label>
|
<label id="chat__header__button" for="chat__toggle">Users</label>
|
||||||
<h3 id="chat__header__text">Stream chat</h3>
|
<h3 id="chat__header__text">Stream chat</h3>
|
||||||
</header>
|
</header>
|
||||||
<article id="chat__messages">
|
<article id="chat__body">
|
||||||
<noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat', token=user.token, _anchor='end') }}" data-js="false"></iframe></noscript>
|
<section id="chat__body__messages">
|
||||||
</article>
|
<noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat', token=user.token, _anchor='end') }}" data-js="false"></iframe></noscript>
|
||||||
<article id="chat__users">
|
</section>
|
||||||
<noscript><iframe id="chat-users_nojs" src="{{ url_for('nojs_users', token=user.token) }}" data-js="false"></iframe></noscript>
|
<section id="chat__body__users">
|
||||||
|
<header id="chat-users-header"><h4>Users in chat</h4></header>
|
||||||
|
<noscript><iframe id="chat-users_nojs" src="{{ url_for('nojs_users', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||||
|
</section>
|
||||||
</article>
|
</article>
|
||||||
<section id="chat__form">
|
<section id="chat__form">
|
||||||
<noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_form', token=user.token) }}" data-js="false"></iframe></noscript>
|
<noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_form', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transform: rotate(-180deg);
|
transform: rotate(-180deg);
|
||||||
}
|
}
|
||||||
#chat-timeout {
|
#timeout {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
|
@ -41,15 +41,15 @@
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
animation: appear 0s {{ timeout }}s forwards;
|
animation: appear 0s {{ timeout }}s forwards;
|
||||||
}
|
}
|
||||||
#chat-timeout header {
|
#timeout header {
|
||||||
font-size: 20pt;
|
font-size: 20pt;
|
||||||
}
|
}
|
||||||
#chat-timeout-dismiss {
|
#timeout-dismiss {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
width: calc(100% - 1rem);
|
width: calc(100% - 1rem);
|
||||||
}
|
}
|
||||||
#chat-timeout-dismiss > .button {
|
#timeout-dismiss > .button {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
height: 0;
|
height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -58,12 +58,12 @@
|
||||||
appear 0s {{ timeout }}s forwards,
|
appear 0s {{ timeout }}s forwards,
|
||||||
unskinny 0s {{ timeout }}s forwards;
|
unskinny 0s {{ timeout }}s forwards;
|
||||||
}
|
}
|
||||||
#chat-timeout-alt {
|
#timeout-alt {
|
||||||
padding: 4px 0 2px 0;
|
padding: 4px 0 2px 0;
|
||||||
}
|
}
|
||||||
#notimeout:target + #chat-timeout,
|
#notimeout:target + #timeout,
|
||||||
#notimeout:target ~ #chat-timeout-dismiss,
|
#notimeout:target ~ #timeout-dismiss,
|
||||||
#notimeout:not(:target) ~ #chat-timeout-alt {
|
#notimeout:not(:target) ~ #timeout-alt {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@keyframes appear {
|
@keyframes appear {
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="end"></div>
|
<div id="end"></div>
|
||||||
<div id="notimeout"></div>
|
<div id="notimeout"></div>
|
||||||
<aside id="chat-timeout">
|
<aside id="timeout">
|
||||||
<a class="button" href="{{ url_for('nojs_chat_redirect') }}">
|
<a class="button" href="{{ url_for('nojs_chat_redirect') }}">
|
||||||
<header>Timed out</header>
|
<header>Timed out</header>
|
||||||
<small>Click to refresh</small>
|
<small>Click to refresh</small>
|
||||||
|
@ -146,10 +146,10 @@
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
<aside id="chat-timeout-dismiss">
|
<aside id="timeout-dismiss">
|
||||||
<a class="button" href="#notimeout">Hide timeout notice</a>
|
<a class="button" href="#notimeout">Hide timeout notice</a>
|
||||||
</aside>
|
</aside>
|
||||||
<aside id="chat-timeout-alt">
|
<aside id="timeout-alt">
|
||||||
<a class="button" href="{{ url_for('nojs_chat_redirect') }}">Click to refresh</a>
|
<a class="button" href="{{ url_for('nojs_chat_redirect') }}">Click to refresh</a>
|
||||||
</aside>
|
</aside>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -3,28 +3,51 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="6">
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: linear-gradient(#121214 calc(100% - 0.625rem), #232327 calc(100% - 0.125rem));
|
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
#header {
|
#timeout {
|
||||||
padding: 0.5rem;
|
height: 0;
|
||||||
background-color: #27272a;
|
visibility: hidden;
|
||||||
border-bottom: 1px solid #4a4a4f;
|
animation: appear 0s {{ timeout }}s forwards;
|
||||||
}
|
}
|
||||||
#header > h4 {
|
#timeout > a {
|
||||||
margin: 0;
|
display: block;
|
||||||
font-weight: normal;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
background-color: #3674bf;
|
||||||
|
border: 4px outset #3584e4;
|
||||||
|
box-shadow: 0 0 5px #3584e4;
|
||||||
|
padding: 1.25ch 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: inherit;
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
animation: unskinny 0s {{ timeout }}s forwards;
|
||||||
|
}
|
||||||
|
#timeout header {
|
||||||
|
font-size: 20pt;
|
||||||
|
}
|
||||||
|
@keyframes appear {
|
||||||
|
to {
|
||||||
|
height: auto;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes unskinny {
|
||||||
|
to {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#main {
|
#main {
|
||||||
margin: 0.5rem 0.75rem;
|
margin: 0.5rem 0.75rem 0.875rem;
|
||||||
}
|
}
|
||||||
#main > h5 {
|
#main > h5 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -35,7 +58,7 @@
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
.user {
|
.user {
|
||||||
line-height: 1.5;
|
line-height: 1.4375;
|
||||||
}
|
}
|
||||||
.user__name {
|
.user__name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@ -49,7 +72,12 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="header"><h4>Users in chat</h4></header>
|
<aside id="timeout">
|
||||||
|
<a href="">
|
||||||
|
<header>Timed out</header>
|
||||||
|
<small>Click to refresh</small>
|
||||||
|
</a>
|
||||||
|
</aside>
|
||||||
<main id="main">
|
<main id="main">
|
||||||
<h5>Watching ({{ users_watching | length }})</h5>
|
<h5>Watching ({{ users_watching | length }})</h5>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import json
|
||||||
|
|
||||||
from quart import current_app, websocket
|
from quart import current_app, websocket
|
||||||
|
|
||||||
from anonstream.stream import get_stream_title, get_stream_uptime, get_stream_viewership_or_none
|
from anonstream.stream import get_stream_title, get_stream_uptime_and_viewership
|
||||||
from anonstream.captcha import get_random_captcha_digest_for
|
from anonstream.captcha import get_random_captcha_digest_for
|
||||||
from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected
|
from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected
|
||||||
from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha
|
from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha
|
||||||
|
@ -13,14 +13,11 @@ from anonstream.utils.websocket import parse_websocket_data, Malformed
|
||||||
CONFIG = current_app.config
|
CONFIG = current_app.config
|
||||||
|
|
||||||
async def websocket_outbound(queue, user):
|
async def websocket_outbound(queue, user):
|
||||||
uptime = get_stream_uptime()
|
|
||||||
viewership = get_stream_viewership_or_none(uptime)
|
|
||||||
payload = {
|
payload = {
|
||||||
'type': 'init',
|
'type': 'init',
|
||||||
'nonce': generate_nonce(),
|
'nonce': generate_nonce(),
|
||||||
'title': await get_stream_title(),
|
'title': await get_stream_title(),
|
||||||
'uptime': uptime,
|
'stats': get_stream_uptime_and_viewership(for_websocket=True),
|
||||||
'viewership': viewership,
|
|
||||||
'messages': get_all_messages_for_websocket(),
|
'messages': get_all_messages_for_websocket(),
|
||||||
'users': get_all_users_for_websocket(),
|
'users': get_all_users_for_websocket(),
|
||||||
'default': {
|
'default': {
|
||||||
|
|
読み込み中…
新しいイシューから参照