Read config.toml more organizedly

このコミットが含まれているのは:
n9k 2022-06-12 08:10:24 +00:00
コミット 7f2e75bc98
4個のファイルの変更156行の追加90行の削除

ファイルの表示

@ -1,94 +1,37 @@
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
# SPDX-License-Identifier: AGPL-3.0-or-later
import os
import secrets
import toml
from collections import OrderedDict
import toml
from quart_compress import Compress
from werkzeug.security import generate_password_hash
from anonstream.quart import Quart
from anonstream.config import update_flask_from_toml
from anonstream.utils.captcha import create_captcha_factory, create_captcha_signer
from anonstream.utils.colour import color_to_colour
from anonstream.utils.user import generate_token
from anonstream.quart import Quart
compress = Compress()
def create_app(config_file):
with open(config_file) as fp:
config = toml.load(fp)
app = Quart('anonstream')
app.jinja_options['trim_blocks'] = True
app.jinja_options['lstrip_blocks'] = True
auth_password = secrets.token_urlsafe(6)
auth_pwhash = generate_password_hash(auth_password)
print('Broadcaster username:', config['auth']['username'])
with open(config_file) as fp:
toml_config = toml.load(fp)
auth_password = update_flask_from_toml(toml_config, app.config)
print('Broadcaster username:', app.config['AUTH_USERNAME'])
print('Broadcaster password:', auth_password)
app = Quart('anonstream')
app.jinja_options.update({
'trim_blocks': True,
'lstrip_blocks': True,
})
# Compress some responses
compress.init_app(app)
app.config.update({
'SECRET_KEY_STRING': config['secret_key'],
'SECRET_KEY': config['secret_key'].encode(),
'AUTH_USERNAME': config['auth']['username'],
'AUTH_PWHASH': auth_pwhash,
'AUTH_TOKEN': generate_token(),
'SEGMENT_DIRECTORY': os.path.realpath(config['segments']['directory']),
'SEGMENT_PLAYLIST': os.path.join(os.path.realpath(config['segments']['directory']), config['segments']['playlist']),
'SEGMENT_PLAYLIST_CACHE_LIFETIME': config['segments']['playlist_cache_lifetime'],
'SEGMENT_PLAYLIST_STALE_THRESHOLD': config['segments']['playlist_stale_threshold'],
'SEGMENT_SEARCH_COOLDOWN': config['segments']['search_cooldown'],
'SEGMENT_SEARCH_TIMEOUT': config['segments']['search_timeout'],
'SEGMENT_STREAM_INITIAL_BUFFER': config['segments']['stream_initial_buffer'],
'STREAM_TITLE': config['title']['file'],
'STREAM_TITLE_CACHE_LIFETIME': config['title']['file_cache_lifetime'],
'DEFAULT_HOST_NAME': config['names']['broadcaster'],
'DEFAULT_ANON_NAME': config['names']['anonymous'],
'MAX_STATES': config['memory']['states'],
'MAX_CAPTCHAS': config['memory']['captchas'],
'MAX_CHAT_MESSAGES': config['memory']['chat_messages'],
'MAX_CHAT_SCROLLBACK': config['memory']['chat_scrollback'],
'TASK_PERIOD_ROTATE_USERS': config['tasks']['rotate_users'],
'TASK_PERIOD_ROTATE_CAPTCHAS': config['tasks']['rotate_captchas'],
'TASK_PERIOD_ROTATE_WEBSOCKETS': config['tasks']['rotate_websockets'],
'TASK_PERIOD_BROADCAST_PING': config['tasks']['broadcast_ping'],
'TASK_PERIOD_BROADCAST_USERS_UPDATE': config['tasks']['broadcast_users_update'],
'TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE': config['tasks']['broadcast_stream_info_update'],
'THRESHOLD_USER_NOTWATCHING': config['thresholds']['user_notwatching'],
'THRESHOLD_USER_TENTATIVE': config['thresholds']['user_tentative'],
'THRESHOLD_USER_ABSENT': config['thresholds']['user_absent'],
'THRESHOLD_NOJS_CHAT_TIMEOUT': config['thresholds']['nojs_chat_timeout'],
'CHAT_COMMENT_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_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
'CHAT_LEGACY_TRIPCODE_ALGORITHM': config['chat']['legacy_tripcode_algorithm'],
'FLOOD_MESSAGE_DURATION': config['flood']['messages']['duration'],
'FLOOD_MESSAGE_THRESHOLD': config['flood']['messages']['threshold'],
'FLOOD_LINE_DURATION': config['flood']['lines']['duration'],
'FLOOD_LINE_THRESHOLD': config['flood']['lines']['threshold'],
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
'CAPTCHA_FONTS': config['captcha']['fonts'],
'CAPTCHA_ALPHABET': config['captcha']['alphabet'],
'CAPTCHA_LENGTH': config['captcha']['length'],
'CAPTCHA_BACKGROUND_COLOUR': color_to_colour(config['captcha']['background_color']),
'CAPTCHA_FOREGROUND_COLOUR': color_to_colour(config['captcha']['foreground_color']),
"COMPRESS_MIN_SIZE": 2048,
"COMPRESS_LEVEL": 9,
})
assert app.config['MAX_STATES'] >= 0
assert app.config['MAX_CHAT_SCROLLBACK'] >= 0
assert (
app.config['MAX_CHAT_MESSAGES'] >= app.config['MAX_CHAT_SCROLLBACK']
)
assert (
app.config['THRESHOLD_USER_ABSENT']
>= app.config['THRESHOLD_USER_TENTATIVE']
>= app.config['THRESHOLD_USER_NOTWATCHING']
)
# Global state: messages, users, captchas
app.messages_by_id = OrderedDict()
app.messages = app.messages_by_id.values()
@ -110,7 +53,7 @@ def create_app(config_file):
@app.after_serving
async def shutdown():
# make all background tasks finish
# Force all background tasks to finish
for task in app.background_sleep:
task.cancel()
@ -119,11 +62,4 @@ def create_app(config_file):
import anonstream.routes
import anonstream.tasks
# Compress some responses
compress.init_app(app)
app.config.update({
"COMPRESS_MIN_SIZE": 2048,
"COMPRESS_LEVEL": 9,
})
return app

130
anonstream/config.py ノーマルファイル
ファイルの表示

@ -0,0 +1,130 @@
import os
import secrets
from werkzeug.security import generate_password_hash
from anonstream.utils.colour import color_to_colour
from anonstream.utils.user import generate_token
def update_flask_from_toml(toml_config, flask_config):
auth_password = secrets.token_urlsafe(6)
auth_pwhash = generate_password_hash(auth_password)
flask_config.update({
'SECRET_KEY_STRING': toml_config['secret_key'],
'SECRET_KEY': toml_config['secret_key'].encode(),
'AUTH_USERNAME': toml_config['auth']['username'],
'AUTH_PWHASH': auth_pwhash,
'AUTH_TOKEN': generate_token(),
})
for flask_section in toml_to_flask_sections(toml_config):
flask_config.update(flask_section)
return auth_password
def toml_to_flask_sections(config):
TOML_TO_FLASK_SECTIONS = (
toml_to_flask_section_segments,
toml_to_flask_section_title,
toml_to_flask_section_names,
toml_to_flask_section_memory,
toml_to_flask_section_tasks,
toml_to_flask_section_thresholds,
toml_to_flask_section_chat,
toml_to_flask_section_flood,
toml_to_flask_section_captcha,
)
for toml_to_flask_section in TOML_TO_FLASK_SECTIONS:
yield toml_to_flask_section(config)
def toml_to_flask_section_segments(config):
cfg = config['segments']
return {
'SEGMENT_DIRECTORY': os.path.realpath(cfg['directory']),
'SEGMENT_PLAYLIST': os.path.join(
os.path.realpath(cfg['directory']),
cfg['playlist'],
),
'SEGMENT_PLAYLIST_CACHE_LIFETIME': cfg['playlist_cache_lifetime'],
'SEGMENT_PLAYLIST_STALE_THRESHOLD': cfg['playlist_stale_threshold'],
'SEGMENT_SEARCH_COOLDOWN': cfg['search_cooldown'],
'SEGMENT_SEARCH_TIMEOUT': cfg['search_timeout'],
'SEGMENT_STREAM_INITIAL_BUFFER': cfg['stream_initial_buffer'],
}
def toml_to_flask_section_title(config):
cfg = config['title']
return {
'STREAM_TITLE': cfg['file'],
'STREAM_TITLE_CACHE_LIFETIME': cfg['file_cache_lifetime'],
}
def toml_to_flask_section_names(config):
cfg = config['names']
return {
'DEFAULT_HOST_NAME': cfg['broadcaster'],
'DEFAULT_ANON_NAME': cfg['anonymous'],
}
def toml_to_flask_section_memory(config):
cfg = config['memory']
assert cfg['states'] >= 0
assert cfg['chat_scrollback'] >= 0
assert cfg['chat_messages'] >= cfg['chat_scrollback']
return {
'MAX_STATES': cfg['states'],
'MAX_CAPTCHAS': cfg['captchas'],
'MAX_CHAT_MESSAGES': cfg['chat_messages'],
'MAX_CHAT_SCROLLBACK': cfg['chat_scrollback'],
}
def toml_to_flask_section_tasks(config):
cfg = config['tasks']
return {
'TASK_ROTATE_USERS': cfg['rotate_users'],
'TASK_ROTATE_CAPTCHAS': cfg['rotate_captchas'],
'TASK_ROTATE_WEBSOCKETS': cfg['rotate_websockets'],
'TASK_BROADCAST_PING': cfg['broadcast_ping'],
'TASK_BROADCAST_USERS_UPDATE': cfg['broadcast_users_update'],
'TASK_BROADCAST_STREAM_INFO_UPDATE': cfg['broadcast_stream_info_update'],
}
def toml_to_flask_section_thresholds(config):
cfg = config['thresholds']
assert cfg['user_notwatching'] <= cfg['user_tentative'] <= cfg['user_absent']
return {
'THRESHOLD_USER_NOTWATCHING': cfg['user_notwatching'],
'THRESHOLD_USER_TENTATIVE': cfg['user_tentative'],
'THRESHOLD_USER_ABSENT': cfg['user_absent'],
'THRESHOLD_NOJS_CHAT_TIMEOUT': cfg['nojs_chat_timeout'],
}
def toml_to_flask_section_chat(config):
cfg = config['chat']
return {
'CHAT_COMMENT_MAX_LENGTH': cfg['max_name_length'],
'CHAT_NAME_MAX_LENGTH': cfg['max_name_length'],
'CHAT_NAME_MIN_CONTRAST': cfg['min_name_contrast'],
'CHAT_BACKGROUND_COLOUR': color_to_colour(cfg['background_color']),
'CHAT_LEGACY_TRIPCODE_ALGORITHM': cfg['legacy_tripcode_algorithm'],
}
def toml_to_flask_section_flood(config):
cfg = config['flood']
return {
'FLOOD_MESSAGE_DURATION': cfg['messages']['duration'],
'FLOOD_MESSAGE_THRESHOLD': cfg['messages']['threshold'],
'FLOOD_LINE_DURATION': cfg['lines']['duration'],
'FLOOD_LINE_THRESHOLD': cfg['lines']['threshold'],
}
def toml_to_flask_section_captcha(config):
cfg = config['captcha']
return {
'CAPTCHA_LIFETIME': cfg['lifetime'],
'CAPTCHA_FONTS': cfg['fonts'],
'CAPTCHA_ALPHABET': cfg['alphabet'],
'CAPTCHA_LENGTH': cfg['length'],
'CAPTCHA_BACKGROUND_COLOUR': color_to_colour(cfg['background_color']),
'CAPTCHA_FOREGROUND_COLOUR': color_to_colour(cfg['foreground_color']),
}

ファイルの表示

@ -43,7 +43,7 @@ def with_period(period):
return periodically
@with_period(CONFIG['TASK_PERIOD_ROTATE_USERS'])
@with_period(CONFIG['TASK_ROTATE_USERS'])
@with_timestamp
async def t_sunset_users(timestamp, iteration):
if iteration == 0:
@ -69,7 +69,7 @@ async def t_sunset_users(timestamp, iteration):
},
)
@with_period(CONFIG['TASK_PERIOD_ROTATE_CAPTCHAS'])
@with_period(CONFIG['TASK_ROTATE_CAPTCHAS'])
async def t_expire_captchas(iteration):
if iteration == 0:
return
@ -86,10 +86,10 @@ async def t_expire_captchas(iteration):
for digest in to_delete:
CAPTCHAS.pop(digest)
@with_period(CONFIG['TASK_PERIOD_ROTATE_WEBSOCKETS'])
@with_period(CONFIG['TASK_ROTATE_WEBSOCKETS'])
@with_timestamp
async def t_close_websockets(timestamp, iteration):
THRESHOLD = CONFIG['TASK_PERIOD_BROADCAST_PING'] * 1.5 + 4.0
THRESHOLD = CONFIG['TASK_BROADCAST_PING'] * 1.5 + 4.0
if iteration == 0:
return
else:
@ -100,21 +100,21 @@ async def t_close_websockets(timestamp, iteration):
if last_pong_ago > THRESHOLD:
queue.put_nowait({'type': 'close'})
@with_period(CONFIG['TASK_PERIOD_BROADCAST_PING'])
@with_period(CONFIG['TASK_BROADCAST_PING'])
async def t_broadcast_ping(iteration):
if iteration == 0:
return
else:
broadcast(USERS, payload={'type': 'ping'})
@with_period(CONFIG['TASK_PERIOD_BROADCAST_USERS_UPDATE'])
@with_period(CONFIG['TASK_BROADCAST_USERS_UPDATE'])
async def t_broadcast_users_update(iteration):
if iteration == 0:
return
else:
broadcast_users_update()
@with_period(CONFIG['TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE'])
@with_period(CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE'])
async def t_broadcast_stream_info_update(iteration):
if iteration == 0:
title = await get_stream_title()
@ -139,7 +139,7 @@ async def t_broadcast_stream_info_update(iteration):
else:
expected_uptime = (
current_app.stream_uptime
+ CONFIG['TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE']
+ CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE']
)
current_app.stream_uptime = uptime
if uptime is None and expected_uptime is None:

ファイルの表示

@ -30,7 +30,7 @@ async def websocket_outbound(queue, user):
},
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
'digest': get_random_captcha_digest_for(user),
'pingpong': CONFIG['TASK_PERIOD_BROADCAST_PING'],
'pingpong': CONFIG['TASK_BROADCAST_PING'],
}
await websocket.send_json(payload)
await websocket.send_json({'type': 'ping'})