anonstream/anonstream/utils/chat.py

83 行
2.7 KiB
Python

# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
# SPDX-License-Identifier: AGPL-3.0-or-later
import base64
import hashlib
import math
import re
import secrets
from functools import lru_cache
from quart import escape
class NonceReuse(Exception):
pass
def generate_nonce():
return secrets.token_urlsafe(16)
def get_message_for_websocket(user, message):
message_keys = ('seq', 'date', 'time_minutes', 'time_seconds', 'markup')
user_keys = ('token_hash',)
return {
**{key: message[key] for key in message_keys},
**{key: user[key] for key in user_keys},
}
def get_approx_linespan(text):
def height(line):
return math.ceil(len(line) / 48)
linespan = sum(map(height, text.splitlines()))
linespan = linespan if linespan > 0 else 1
return linespan
def schema_to_emotes(schema):
emotes = []
for name, coords in schema.items():
assert emote['name'], 'emote names cannot be empty'
assert not re.search(r'\s', name), \
'whitespace is not allowed in emote names'
name_markup = escape(name)
# 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', name[0]) else r''
finish = r'(?:$|(?=\W))' if re.fullmatch(r'\w', name[-1]) else r''
regex = re.compile(''.join((onset, re.escape(name_markup), finish)))
position, size = tuple(coords['position']), tuple(coords['size'])
emotes.append((name, regex, position, size))
return emotes
def escape_css_string(string):
'''
https://drafts.csswg.org/cssom/#common-serializing-idioms
'''
result = []
for char in string:
if char == '\0':
result.append('\ufffd')
elif char < '\u0020' or char == '\u007f':
result.append(f'\\{ord(char):x}')
elif char == '"' or char == '\\':
result.append(f'\\{char}')
else:
result.append(char)
return ''.join(result)
@lru_cache(maxsize=1)
def get_emotehash(emotes):
rules = []
for name, _regex, (x, y), (width, height) in sorted(emotes):
rule = (
f'[data-emote="{escape_css_string(name)}"] '
f'{{ background-position: {-x}px {-y}px; '
f'width: {width}px; height: {height}px; }}'
)
rules.append(rule.encode())
plaintext = b','.join(rules)
digest = hashlib.sha256(plaintext).digest()
return digest[:6].hex()