Emotes: reorganize
このコミットが含まれているのは:
コミット
7fb8418018
|
@ -8,6 +8,7 @@ from collections import OrderedDict
|
||||||
from quart_compress import Compress
|
from quart_compress import Compress
|
||||||
|
|
||||||
from anonstream.config import update_flask_from_toml
|
from anonstream.config import update_flask_from_toml
|
||||||
|
from anonstream.emote import load_emote_schema
|
||||||
from anonstream.quart import Quart
|
from anonstream.quart import Quart
|
||||||
from anonstream.utils.captcha import create_captcha_factory, create_captcha_signer
|
from anonstream.utils.captcha import create_captcha_factory, create_captcha_signer
|
||||||
from anonstream.utils.chat import precompute_emote_regex
|
from anonstream.utils.chat import precompute_emote_regex
|
||||||
|
@ -49,10 +50,7 @@ def create_app(toml_config):
|
||||||
app.allowedness = generate_blank_allowedness()
|
app.allowedness = generate_blank_allowedness()
|
||||||
|
|
||||||
# Read emote schema
|
# Read emote schema
|
||||||
with open(app.config['EMOTE_SCHEMA']) as fp:
|
app.emotes = load_emote_schema(app.config['EMOTE_SCHEMA'])
|
||||||
emotes = json.load(fp)
|
|
||||||
precompute_emote_regex(emotes)
|
|
||||||
app.emotes = emotes
|
|
||||||
|
|
||||||
# State for tasks
|
# State for tasks
|
||||||
app.users_update_buffer = set()
|
app.users_update_buffer = set()
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
class BadEmoteName(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_emote_schema(filepath)
|
||||||
|
with open(filepath) as fp:
|
||||||
|
emotes = json.load(fp)
|
||||||
|
precompute_emote_regex(emotes)
|
||||||
|
return emotes
|
||||||
|
|
||||||
|
def precompute_emote_regex(schema):
|
||||||
|
for emote in schema:
|
||||||
|
if not emote['name']:
|
||||||
|
raise BadEmoteName('emote names cannot be empty')
|
||||||
|
if re.search(r'\s', emote['name']):
|
||||||
|
raise BadEmoteName(
|
||||||
|
f'whitespace is not allowed in emote names: {emote["name"]!r}'
|
||||||
|
)
|
||||||
|
for length in (emote['width'], emote['height']):
|
||||||
|
if length is not None or isinstance(length, int) and length >= 0:
|
||||||
|
raise BadEmoteName(
|
||||||
|
f'emote dimensions must be null or non-negative integers: '
|
||||||
|
f'{emote["name"]!r}'
|
||||||
|
)
|
||||||
|
# If the emote name begins with a word character [a-zA-Z0-9_],
|
||||||
|
# match only if preceded by a non-word character or the empty
|
||||||
|
# string. Similarly for the end of the emote name.
|
||||||
|
# Examples:
|
||||||
|
# * ":joy:" matches "abc :joy:~xyz" and "abc:joy:xyz"
|
||||||
|
# * "JoySi" matches "abc JoySi~xyz" but NOT "abcJoySiabc"
|
||||||
|
onset = r'(?:^|(?<=\W))' if re.fullmatch(r'\w', emote['name'][0]) else r''
|
||||||
|
finish = r'(?:$|(?=\W))' if re.fullmatch(r'\w', emote['name'][-1]) else r''
|
||||||
|
emote['regex'] = re.compile(''.join(
|
||||||
|
(onset, re.escape(escape(emote['name'])), finish)
|
||||||
|
))
|
|
@ -2,13 +2,10 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
import markupsafe
|
from quart import current_app
|
||||||
from quart import current_app, escape, url_for, Markup
|
|
||||||
|
|
||||||
CONFIG = current_app.config
|
CONFIG = current_app.config
|
||||||
EMOTES = current_app.emotes
|
|
||||||
|
|
||||||
def generate_nonce_hash(nonce):
|
def generate_nonce_hash(nonce):
|
||||||
parts = CONFIG['SECRET_KEY'] + b'nonce-hash\0' + nonce.encode()
|
parts = CONFIG['SECRET_KEY'] + b'nonce-hash\0' + nonce.encode()
|
||||||
|
@ -19,24 +16,3 @@ def get_scrollback(messages):
|
||||||
if len(messages) < n:
|
if len(messages) < n:
|
||||||
return messages
|
return messages
|
||||||
return list(messages)[-n:]
|
return list(messages)[-n:]
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def get_emote_markup(emote_name, emote_file, emote_width, emote_height):
|
|
||||||
emote_name_markup = escape(emote_name)
|
|
||||||
width = '' if emote_width is None else f'width="{escape(emote_width)}" '
|
|
||||||
height = '' if emote_height is None else f'height="{escape(emote_height)}" '
|
|
||||||
return Markup(
|
|
||||||
f'''<img class="emote" '''
|
|
||||||
f'''src="{escape(url_for('static', filename=emote_file))}" '''
|
|
||||||
f'''{width}{height}'''
|
|
||||||
f'''alt="{emote_name_markup}" title="{emote_name_markup}">'''
|
|
||||||
)
|
|
||||||
|
|
||||||
def insert_emotes(markup):
|
|
||||||
assert isinstance(markup, markupsafe.Markup)
|
|
||||||
for emote in EMOTES:
|
|
||||||
emote_markup = get_emote_markup(
|
|
||||||
emote['name'], emote['file'], emote['width'], emote['height'],
|
|
||||||
)
|
|
||||||
markup = emote['regex'].sub(emote_markup, markup)
|
|
||||||
return Markup(markup)
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import markupsafe
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from quart import current_app, escape, url_for, Markup
|
||||||
|
|
||||||
|
EMOTES = current_app.emotes
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_emote_markup(emote_name, emote_file, emote_width, emote_height):
|
||||||
|
emote_name_markup = escape(emote_name)
|
||||||
|
width = '' if emote_width is None else f'width="{escape(emote_width)}" '
|
||||||
|
height = '' if emote_height is None else f'height="{escape(emote_height)}" '
|
||||||
|
return Markup(
|
||||||
|
f'''<img class="emote" '''
|
||||||
|
f'''src="{escape(url_for('static', filename=emote_file))}" '''
|
||||||
|
f'''{width}{height}'''
|
||||||
|
f'''alt="{emote_name_markup}" title="{emote_name_markup}">'''
|
||||||
|
)
|
||||||
|
|
||||||
|
def insert_emotes(markup):
|
||||||
|
assert isinstance(markup, markupsafe.Markup)
|
||||||
|
for emote in EMOTES:
|
||||||
|
emote_markup = get_emote_markup(
|
||||||
|
emote['name'], emote['file'], emote['width'], emote['height'],
|
||||||
|
)
|
||||||
|
markup = emote['regex'].sub(emote_markup, markup)
|
||||||
|
return Markup(markup)
|
|
@ -30,23 +30,3 @@ def get_approx_linespan(text):
|
||||||
linespan = sum(map(height, text.splitlines()))
|
linespan = sum(map(height, text.splitlines()))
|
||||||
linespan = linespan if linespan > 0 else 1
|
linespan = linespan if linespan > 0 else 1
|
||||||
return linespan
|
return linespan
|
||||||
|
|
||||||
def precompute_emote_regex(schema):
|
|
||||||
for emote in schema:
|
|
||||||
assert emote['name'], 'emote names cannot be empty'
|
|
||||||
assert not re.search(r'\s', emote['name']), \
|
|
||||||
f'whitespace is not allowed in emote names: {emote["name"]!r}'
|
|
||||||
for length in (emote['width'], emote['height']):
|
|
||||||
assert length is None or isinstance(length, int) and length >= 0, \
|
|
||||||
f'emote dimensions must be null or non-negative integers: {emote["name"]!r}'
|
|
||||||
# If the emote name begins with a word character [a-zA-Z0-9_],
|
|
||||||
# match only if preceded by a non-word character or the empty
|
|
||||||
# string. Similarly for the end of the emote name.
|
|
||||||
# Examples:
|
|
||||||
# * ":joy:" matches "abc :joy:~xyz" and "abc:joy:xyz"
|
|
||||||
# * "JoySi" matches "abc JoySi~xyz" but NOT "abcJoySiabc"
|
|
||||||
onset = r'(?:^|(?<=\W))' if re.fullmatch(r'\w', emote['name'][0]) else r''
|
|
||||||
finish = r'(?:$|(?=\W))' if re.fullmatch(r'\w', emote['name'][-1]) else r''
|
|
||||||
emote['regex'] = re.compile(''.join(
|
|
||||||
(onset, re.escape(escape(emote['name'])), finish)
|
|
||||||
))
|
|
||||||
|
|
読み込み中…
新しいイシューから参照