コミットを比較
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,
|
||||
})
|
||||
app.config.update({
|
||||
'SECRET_KEY_STRING': config['secret_key'],
|
||||
'SECRET_KEY': config['secret_key'].encode(),
|
||||
'AUTH_USERNAME': config['auth']['username'],
|
||||
'AUTH_PWHASH': auth_pwhash,
|
||||
|
@ -56,6 +57,7 @@ def create_app(config_file):
|
|||
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
||||
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
||||
'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_THRESHOLD': config['flood']['threshold'],
|
||||
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
||||
|
|
|
@ -11,19 +11,27 @@ CONFIG = current_app.config
|
|||
def _generate_tripcode_digest_legacy(password):
|
||||
hexdigest, _ = werkzeug.security._hash_internal(
|
||||
'pbkdf2:sha256:150000',
|
||||
CONFIG['SECRET_KEY'],
|
||||
CONFIG['SECRET_KEY_STRING'],
|
||||
password,
|
||||
)
|
||||
digest = bytes.fromhex(hexdigest)
|
||||
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()
|
||||
digest = hashlib.sha256(parts).digest()
|
||||
return base64.b64encode(digest)[:8].decode()
|
||||
|
||||
def generate_tripcode(password, generate_digest=generate_tripcode_digest):
|
||||
digest = generate_digest(password)
|
||||
def generate_tripcode_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(
|
||||
seed='tripcode-background\0' + digest,
|
||||
bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
|
||||
|
|
|
@ -25,15 +25,21 @@ const jsmarkup_chat_users = `\
|
|||
const jsmarkup_chat_form = `\
|
||||
<form id="chat-form_js" data-js="true" action="/chat" method="post">
|
||||
<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">
|
||||
<span id="chat-live__ball"></span>
|
||||
<span id="chat-live__status"><span>Not connected<span data-verbose='true'> to chat</span></span></span>
|
||||
</div>
|
||||
<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__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>`;
|
||||
|
||||
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_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 */
|
||||
const info_title = document.getElementById("info_js__title");
|
||||
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.addEventListener("click", (event) => {
|
||||
if (chat_form_captcha_image.dataset.reloadable === undefined) {
|
||||
return;
|
||||
event.preventDefault();
|
||||
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) => {
|
||||
chat_form_captcha_digest.value = digest;
|
||||
|
@ -594,6 +613,11 @@ const on_websocket_message = (event) => {
|
|||
|
||||
case "ack":
|
||||
console.log("ws ack", receipt);
|
||||
|
||||
if (receipt.notice !== null) {
|
||||
show_notice(receipt.notice);
|
||||
}
|
||||
|
||||
const existing_nonce = chat_form_nonce.value;
|
||||
if (receipt.clear && receipt.nonce === existing_nonce) {
|
||||
chat_form_comment.value = "";
|
||||
|
@ -603,6 +627,7 @@ const on_websocket_message = (event) => {
|
|||
chat_form_nonce.value = receipt.next;
|
||||
receipt.digest === null ? disable_captcha() : enable_captcha(receipt.digest);
|
||||
chat_form_submit.disabled = false;
|
||||
|
||||
break;
|
||||
|
||||
case "message":
|
||||
|
@ -709,7 +734,6 @@ stream.addEventListener("error", (event) => {
|
|||
});
|
||||
|
||||
/* 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_comment = document.getElementById("chat-form_js__comment");
|
||||
const chat_form_submit = document.getElementById("chat-form_js__submit");
|
||||
|
|
|
@ -107,9 +107,7 @@ noscript {
|
|||
#chat__toggle {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: calc(0.5rem + 1px);
|
||||
left: calc(0.5rem + 4px);
|
||||
margin: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
#chat__toggle:checked ~ #chat__body > #chat__body__messages,
|
||||
#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-rows: auto var(--button-height);
|
||||
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 {
|
||||
grid-column: 2 / span 1;
|
||||
|
@ -305,6 +304,33 @@ noscript {
|
|||
#chat-form_js:not([data-captcha]) > #chat-form_js__captcha-answer {
|
||||
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 {
|
||||
height: 13ch;
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
#notice h2 {
|
||||
margin: 0;
|
||||
|
@ -73,7 +74,7 @@
|
|||
grid-gap: 0.375rem;
|
||||
}
|
||||
#chat-form__exit,
|
||||
#appearance-form__exit,
|
||||
#appearance-form__buttons__exit,
|
||||
#appearance-form__label-name,
|
||||
#appearance-form__label-password {
|
||||
font-size: 11pt;
|
||||
|
@ -120,6 +121,7 @@
|
|||
}
|
||||
|
||||
#appearance-form {
|
||||
display: grid;
|
||||
grid-auto-rows: 1fr 1fr 2rem;
|
||||
grid-auto-columns: min-content 1fr min-content;
|
||||
}
|
||||
|
@ -172,46 +174,48 @@
|
|||
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;
|
||||
}
|
||||
#appearance:target ~ #appearance-form {
|
||||
display: grid;
|
||||
}
|
||||
#appearance:target ~ #chat-form {
|
||||
#toggle:checked ~ #chat-form,
|
||||
#toggle:not(:checked) ~ #appearance-form {
|
||||
display: none;
|
||||
}
|
||||
#chat:target ~ #appearance-form {
|
||||
#notice-radio:checked + #notice,
|
||||
#notice-radio:not(:checked) ~ #chat-form,
|
||||
#notice-radio:not(:checked) ~ #appearance-form {
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat"></div>
|
||||
<div id="appearance"></div>
|
||||
<input id="toggle" type="checkbox"{% if not prefer_chat_form %} checked{% endif %}>
|
||||
{% 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>
|
||||
<small>Click to dismiss</small>
|
||||
</a>
|
||||
</label>
|
||||
{% endif %}
|
||||
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token) }}" method="post">
|
||||
<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">
|
||||
<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 %}
|
||||
<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">
|
||||
|
@ -239,7 +243,7 @@
|
|||
<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="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">
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -45,6 +45,7 @@ max_comment_length = 512
|
|||
max_name_length = 24
|
||||
min_name_contrast = 3.0
|
||||
background_color = "#232327"
|
||||
legacy_tripcode_algorithm = false
|
||||
|
||||
[flood]
|
||||
duration = 20.0
|
||||
|
|
読み込み中…
新しいイシューから参照