コミットを比較

...

2 コミット

作成者 SHA1 メッセージ 日付
n9k 7fb8418018 Emotes: reorganize 2022-07-31 22:56:34 +00:00
n9k 41ba8fd026 Readme: typo 2022-07-31 22:53:53 +00:00
6個のファイルの変更68行の追加50行の削除

ファイルの表示

@ -96,7 +96,7 @@ using the `ANONSTREAM_CONFIG` environment variable.
anonstream has APIs for accessing internal state and hooking into anonstream has APIs for accessing internal state and hooking into
internal events. They can be used by humans and other programs. See internal events. They can be used by humans and other programs. See
[HACKING.md][/doc/HACKING.md]. [HACKING.md](/doc/HACKING.md).
## Copying ## Copying

ファイルの表示

@ -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()

37
anonstream/emote.py ノーマルファイル
ファイルの表示

@ -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)

27
anonstream/helpers/emote.py ノーマルファイル
ファイルの表示

@ -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)
))