コミットを比較
5 コミット
f486ab6358
...
a93135e522
作成者 | SHA1 | 日付 |
---|---|---|
n9k | a93135e522 | |
n9k | e91a30a14d | |
n9k | b6e2715077 | |
n9k | 3246fbe198 | |
n9k | c0ba5eaff9 |
|
@ -25,6 +25,7 @@ def create_app(config_file):
|
||||||
'lstrip_blocks': True,
|
'lstrip_blocks': True,
|
||||||
})
|
})
|
||||||
app.config.update({
|
app.config.update({
|
||||||
|
'SECRET_KEY_STRING': config['secret_key'],
|
||||||
'SECRET_KEY': config['secret_key'].encode(),
|
'SECRET_KEY': config['secret_key'].encode(),
|
||||||
'AUTH_USERNAME': config['auth']['username'],
|
'AUTH_USERNAME': config['auth']['username'],
|
||||||
'AUTH_PWHASH': auth_pwhash,
|
'AUTH_PWHASH': auth_pwhash,
|
||||||
|
@ -56,6 +57,7 @@ def create_app(config_file):
|
||||||
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
||||||
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
||||||
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
||||||
|
'CHAT_LEGACY_TRIPCODE_ALGORITHM': config['chat']['legacy_tripcode_algorithm'],
|
||||||
'FLOOD_DURATION': config['flood']['duration'],
|
'FLOOD_DURATION': config['flood']['duration'],
|
||||||
'FLOOD_THRESHOLD': config['flood']['threshold'],
|
'FLOOD_THRESHOLD': config['flood']['threshold'],
|
||||||
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
||||||
|
|
|
@ -11,19 +11,27 @@ CONFIG = current_app.config
|
||||||
def _generate_tripcode_digest_legacy(password):
|
def _generate_tripcode_digest_legacy(password):
|
||||||
hexdigest, _ = werkzeug.security._hash_internal(
|
hexdigest, _ = werkzeug.security._hash_internal(
|
||||||
'pbkdf2:sha256:150000',
|
'pbkdf2:sha256:150000',
|
||||||
CONFIG['SECRET_KEY'],
|
CONFIG['SECRET_KEY_STRING'],
|
||||||
password,
|
password,
|
||||||
)
|
)
|
||||||
digest = bytes.fromhex(hexdigest)
|
digest = bytes.fromhex(hexdigest)
|
||||||
return base64.b64encode(digest)[:8].decode()
|
return base64.b64encode(digest)[:8].decode()
|
||||||
|
|
||||||
def generate_tripcode_digest(password):
|
def _generate_tripcode_digest(password):
|
||||||
parts = CONFIG['SECRET_KEY'] + b'tripcode\0' + password.encode()
|
parts = CONFIG['SECRET_KEY'] + b'tripcode\0' + password.encode()
|
||||||
digest = hashlib.sha256(parts).digest()
|
digest = hashlib.sha256(parts).digest()
|
||||||
return base64.b64encode(digest)[:8].decode()
|
return base64.b64encode(digest)[:8].decode()
|
||||||
|
|
||||||
def generate_tripcode(password, generate_digest=generate_tripcode_digest):
|
def generate_tripcode_digest(password):
|
||||||
digest = generate_digest(password)
|
algorithm = (
|
||||||
|
_generate_tripcode_digest_legacy
|
||||||
|
if CONFIG['CHAT_LEGACY_TRIPCODE_ALGORITHM'] else
|
||||||
|
_generate_tripcode_digest
|
||||||
|
)
|
||||||
|
return algorithm(password)
|
||||||
|
|
||||||
|
def generate_tripcode(password):
|
||||||
|
digest = generate_tripcode_digest(password)
|
||||||
background_colour = generate_colour(
|
background_colour = generate_colour(
|
||||||
seed='tripcode-background\0' + digest,
|
seed='tripcode-background\0' + digest,
|
||||||
bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
|
bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
|
||||||
|
|
|
@ -25,15 +25,21 @@ const jsmarkup_chat_users = `\
|
||||||
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="">
|
||||||
<textarea id="chat-form_js__comment" name="comment" maxlength="512" required placeholder="Send a message..." rows="1"></textarea>
|
<textarea id="chat-form_js__comment" name="comment" maxlength="512" required placeholder="Send a message..." rows="1" autofocus></textarea>
|
||||||
<div id="chat-live">
|
<div id="chat-live">
|
||||||
<span id="chat-live__ball"></span>
|
<span id="chat-live__ball"></span>
|
||||||
<span id="chat-live__status"><span>Not connected<span data-verbose='true'> to chat</span></span></span>
|
<span id="chat-live__status"><span>Not connected<span data-verbose='true'> to chat</span></span></span>
|
||||||
</div>
|
</div>
|
||||||
<input id="chat-form_js__captcha-digest" type="hidden" name="captcha-digest" disabled>
|
<input id="chat-form_js__captcha-digest" type="hidden" name="captcha-digest" disabled>
|
||||||
<img id="chat-form_js__captcha-image" width="72" height="30">
|
<input id="chat-form_js__captcha-image" type="image" width="72" height="30">
|
||||||
<input id="chat-form_js__captcha-answer" name="captcha-answer" placeholder="Captcha" disabled>
|
<input id="chat-form_js__captcha-answer" name="captcha-answer" placeholder="Captcha" disabled>
|
||||||
<input id="chat-form_js__submit" type="submit" value="Chat" accesskey="p" disabled>
|
<input id="chat-form_js__submit" type="submit" value="Chat" accesskey="p" disabled>
|
||||||
|
<article id="chat-form_js__notice">
|
||||||
|
<button id="chat-form_js__notice__button" type="button">
|
||||||
|
<header id="chat-form_js__notice__button__header"></header>
|
||||||
|
<small>Click to dismiss</small>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
</form>`;
|
</form>`;
|
||||||
|
|
||||||
const insert_jsmarkup = () => {jsmarkup_info_float_viewership
|
const insert_jsmarkup = () => {jsmarkup_info_float_viewership
|
||||||
|
@ -96,6 +102,19 @@ const stylesheet_color = document.styleSheets[1];
|
||||||
const stylesheet_tripcode_display = document.styleSheets[2];
|
const stylesheet_tripcode_display = document.styleSheets[2];
|
||||||
const stylesheet_tripcode_colors = document.styleSheets[3];
|
const stylesheet_tripcode_colors = document.styleSheets[3];
|
||||||
|
|
||||||
|
/* override chat form notice button */
|
||||||
|
const chat_form = document.getElementById("chat-form_js");
|
||||||
|
const chat_form_notice_button = document.getElementById("chat-form_js__notice__button");
|
||||||
|
const chat_form_notice_header = document.getElementById("chat-form_js__notice__button__header");
|
||||||
|
chat_form_notice_button.addEventListener("click", (event) => {
|
||||||
|
chat_form.removeAttribute("data-notice");
|
||||||
|
chat_form_notice_header.innerText = "";
|
||||||
|
});
|
||||||
|
const show_notice = (text) => {
|
||||||
|
chat_form_notice_header.innerText = text;
|
||||||
|
chat_form.dataset.notice = "";
|
||||||
|
}
|
||||||
|
|
||||||
/* create websocket */
|
/* create websocket */
|
||||||
const info_title = document.getElementById("info_js__title");
|
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");
|
||||||
|
@ -360,16 +379,16 @@ chat_form_captcha_image.addEventListener("error", (event) => {
|
||||||
chat_form_captcha_image.title = "Click for a new captcha";
|
chat_form_captcha_image.title = "Click for a new captcha";
|
||||||
});
|
});
|
||||||
chat_form_captcha_image.addEventListener("click", (event) => {
|
chat_form_captcha_image.addEventListener("click", (event) => {
|
||||||
if (chat_form_captcha_image.dataset.reloadable === undefined) {
|
event.preventDefault();
|
||||||
return;
|
if (chat_form_captcha_image.dataset.reloadable !== undefined) {
|
||||||
|
chat_form_submit.disabled = true;
|
||||||
|
chat_form_captcha_image.alt = "Waiting...";
|
||||||
|
chat_form_captcha_image.removeAttribute("title");
|
||||||
|
chat_form_captcha_image.removeAttribute("data-reloadable");
|
||||||
|
chat_form_captcha_image.removeAttribute("src");
|
||||||
|
const payload = {type: "captcha"};
|
||||||
|
ws.send(JSON.stringify(payload));
|
||||||
}
|
}
|
||||||
chat_form_submit.disabled = true;
|
|
||||||
chat_form_captcha_image.alt = "Waiting...";
|
|
||||||
chat_form_captcha_image.removeAttribute("title");
|
|
||||||
chat_form_captcha_image.removeAttribute("data-reloadable");
|
|
||||||
chat_form_captcha_image.removeAttribute("src");
|
|
||||||
const payload = {type: "captcha"};
|
|
||||||
ws.send(JSON.stringify(payload));
|
|
||||||
});
|
});
|
||||||
const enable_captcha = (digest) => {
|
const enable_captcha = (digest) => {
|
||||||
chat_form_captcha_digest.value = digest;
|
chat_form_captcha_digest.value = digest;
|
||||||
|
@ -594,6 +613,11 @@ const on_websocket_message = (event) => {
|
||||||
|
|
||||||
case "ack":
|
case "ack":
|
||||||
console.log("ws ack", receipt);
|
console.log("ws ack", receipt);
|
||||||
|
|
||||||
|
if (receipt.notice !== null) {
|
||||||
|
show_notice(receipt.notice);
|
||||||
|
}
|
||||||
|
|
||||||
const existing_nonce = chat_form_nonce.value;
|
const existing_nonce = chat_form_nonce.value;
|
||||||
if (receipt.clear && receipt.nonce === existing_nonce) {
|
if (receipt.clear && receipt.nonce === existing_nonce) {
|
||||||
chat_form_comment.value = "";
|
chat_form_comment.value = "";
|
||||||
|
@ -603,6 +627,7 @@ const on_websocket_message = (event) => {
|
||||||
chat_form_nonce.value = receipt.next;
|
chat_form_nonce.value = receipt.next;
|
||||||
receipt.digest === null ? disable_captcha() : enable_captcha(receipt.digest);
|
receipt.digest === null ? disable_captcha() : enable_captcha(receipt.digest);
|
||||||
chat_form_submit.disabled = false;
|
chat_form_submit.disabled = false;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "message":
|
case "message":
|
||||||
|
@ -709,7 +734,6 @@ stream.addEventListener("error", (event) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
/* override js-only chat form */
|
/* override js-only chat form */
|
||||||
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");
|
||||||
const chat_form_comment = document.getElementById("chat-form_js__comment");
|
const chat_form_comment = document.getElementById("chat-form_js__comment");
|
||||||
const chat_form_submit = document.getElementById("chat-form_js__submit");
|
const chat_form_submit = document.getElementById("chat-form_js__submit");
|
||||||
|
|
|
@ -107,9 +107,7 @@ noscript {
|
||||||
#chat__toggle {
|
#chat__toggle {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(0.5rem + 1px);
|
pointer-events: none;
|
||||||
left: calc(0.5rem + 4px);
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
#chat__toggle:checked ~ #chat__body > #chat__body__messages,
|
#chat__toggle:checked ~ #chat__body > #chat__body__messages,
|
||||||
#chat__toggle:not(:checked) ~ #chat__body > #chat__body__users {
|
#chat__toggle:not(:checked) ~ #chat__body > #chat__body__users {
|
||||||
|
@ -263,7 +261,8 @@ noscript {
|
||||||
grid-template-columns: 1fr min-content min-content 5rem;
|
grid-template-columns: 1fr min-content min-content 5rem;
|
||||||
grid-template-rows: auto var(--button-height);
|
grid-template-rows: auto var(--button-height);
|
||||||
grid-gap: 0.375rem;
|
grid-gap: 0.375rem;
|
||||||
margin: 0 0.5rem 0.5rem 0.5rem;
|
padding: 0 0.5rem 0.5rem 0.5rem;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#chat-form_js__submit {
|
#chat-form_js__submit {
|
||||||
grid-column: 2 / span 1;
|
grid-column: 2 / span 1;
|
||||||
|
@ -305,6 +304,33 @@ noscript {
|
||||||
#chat-form_js:not([data-captcha]) > #chat-form_js__captcha-answer {
|
#chat-form_js:not([data-captcha]) > #chat-form_js__captcha-answer {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#chat-form_js:not([data-notice]) > #chat-form_js__notice {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#chat-form_js__notice {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(#2323277f 25%, #232327);
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
#chat-form_js__notice__button {
|
||||||
|
color: inherit;
|
||||||
|
border-color: black;
|
||||||
|
background-color: #232327;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 0.375rem;
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
box-shadow: 0 0 12px black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
#chat-form_js__notice__button__header {
|
||||||
|
font-size: 14pt;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
#chat-form_nojs {
|
#chat-form_nojs {
|
||||||
height: 13ch;
|
height: 13ch;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
#notice h2 {
|
#notice h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -73,7 +74,7 @@
|
||||||
grid-gap: 0.375rem;
|
grid-gap: 0.375rem;
|
||||||
}
|
}
|
||||||
#chat-form__exit,
|
#chat-form__exit,
|
||||||
#appearance-form__exit,
|
#appearance-form__buttons__exit,
|
||||||
#appearance-form__label-name,
|
#appearance-form__label-name,
|
||||||
#appearance-form__label-password {
|
#appearance-form__label-password {
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
|
@ -120,6 +121,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#appearance-form {
|
#appearance-form {
|
||||||
|
display: grid;
|
||||||
grid-auto-rows: 1fr 1fr 2rem;
|
grid-auto-rows: 1fr 1fr 2rem;
|
||||||
grid-auto-columns: min-content 1fr min-content;
|
grid-auto-columns: min-content 1fr min-content;
|
||||||
}
|
}
|
||||||
|
@ -172,46 +174,48 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#appearance-form {
|
#toggle {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#chat-form__exit > label,
|
||||||
|
#appearance-form__buttons__exit > label {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
#toggle:focus-visible ~ #chat-form > #chat-form__exit > label,
|
||||||
|
#toggle:focus-visible ~ #appearance-form #appearance-form__buttons__exit > label {
|
||||||
|
padding: 0;
|
||||||
|
border: 1px dotted;
|
||||||
|
}
|
||||||
|
#notice-radio {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#appearance:target ~ #appearance-form {
|
#toggle:checked ~ #chat-form,
|
||||||
display: grid;
|
#toggle:not(:checked) ~ #appearance-form {
|
||||||
}
|
|
||||||
#appearance:target ~ #chat-form {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#chat:target ~ #appearance-form {
|
#notice-radio:checked + #notice,
|
||||||
|
#notice-radio:not(:checked) ~ #chat-form,
|
||||||
|
#notice-radio:not(:checked) ~ #appearance-form {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
{% if state.notice %}
|
|
||||||
#chat-form {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#chat:target ~ #chat-form {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
#chat:target ~ #notice,
|
|
||||||
#appearance:target ~ #notice {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="chat"></div>
|
<input id="toggle" type="checkbox"{% if not prefer_chat_form %} checked{% endif %}>
|
||||||
<div id="appearance"></div>
|
|
||||||
{% if state.notice %}
|
{% if state.notice %}
|
||||||
<a id="notice" {% if state.verbose %}class="verbose" {% endif %}{% if prefer_chat_form %}href="#chat"{% else %}href="#appearance"{% endif %}>
|
<input id="notice-radio" type="radio">
|
||||||
|
<label id="notice" for="notice-radio" {% if state.verbose %}class="verbose" {% endif %}>
|
||||||
<header><h2>{{ state.notice }}</h2></header>
|
<header><h2>{{ state.notice }}</h2></header>
|
||||||
<small>Click to dismiss</small>
|
<small>Click to dismiss</small>
|
||||||
</a>
|
</label>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token) }}" method="post">
|
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token) }}" method="post">
|
||||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||||
<textarea id="chat-form__comment" name="comment" maxlength="512" {% if digest is none %}required {% endif %} placeholder="Send a message..." rows="1" tabindex="1">{{ state.comment }}</textarea>
|
<textarea id="chat-form__comment" name="comment" maxlength="512" {% if digest is none %}required {% endif %} placeholder="Send a message..." rows="1" tabindex="1" autofocus>{{ state.comment }}</textarea>
|
||||||
<input id="chat-form__submit" type="submit" value="Chat" tabindex="4" accesskey="p">
|
<input id="chat-form__submit" type="submit" value="Chat" tabindex="4" accesskey="p">
|
||||||
<div id="chat-form__exit"><a href="#appearance">Settings</a></div>
|
<div id="chat-form__exit"><label for="toggle" class="pseudolink">Settings</label></div>
|
||||||
{% if digest %}
|
{% if digest %}
|
||||||
<input type="hidden" name="captcha-digest" value="{{ digest }}">
|
<input type="hidden" name="captcha-digest" value="{{ digest }}">
|
||||||
<input id="chat-form__captcha-image" type="image" formaction="{{ url_for('nojs_chat_form_redirect', token=user.token) }}" formnovalidate src="{{ url_for('captcha', token=user.token, digest=digest) }}" width="72" height="30" alt="Captcha failed to load" title="Click for a new captcha" tabindex="2">
|
<input id="chat-form__captcha-image" type="image" formaction="{{ url_for('nojs_chat_form_redirect', token=user.token) }}" formnovalidate src="{{ url_for('captcha', token=user.token, digest=digest) }}" width="72" height="30" alt="Captcha failed to load" title="Click for a new captcha" tabindex="2">
|
||||||
|
@ -239,7 +243,7 @@
|
||||||
<input id="appearance-form__password" name="password" type="password" placeholder="(tripcode password)" maxlength="1024">
|
<input id="appearance-form__password" name="password" type="password" placeholder="(tripcode password)" maxlength="1024">
|
||||||
<div id="hide-password"><label for="password-toggle" class="pseudolink x">✗</label></div>
|
<div id="hide-password"><label for="password-toggle" class="pseudolink x">✗</label></div>
|
||||||
<div id="appearance-form__buttons">
|
<div id="appearance-form__buttons">
|
||||||
<div id="appearance-form__exit"><a href="#chat">Return to chat</a></div>
|
<div id="appearance-form__buttons__exit"><label for="toggle" class="pseudolink">Return to chat</label></div>
|
||||||
<input type="submit" value="Update">
|
<input type="submit" value="Update">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -45,6 +45,7 @@ max_comment_length = 512
|
||||||
max_name_length = 24
|
max_name_length = 24
|
||||||
min_name_contrast = 3.0
|
min_name_contrast = 3.0
|
||||||
background_color = "#232327"
|
background_color = "#232327"
|
||||||
|
legacy_tripcode_algorithm = false
|
||||||
|
|
||||||
[flood]
|
[flood]
|
||||||
duration = 20.0
|
duration = 20.0
|
||||||
|
|
読み込み中…
新しいイシューから参照