Read config.toml more organizedly
このコミットが含まれているのは:
コミット
7f2e75bc98
|
@ -1,94 +1,37 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k]
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
|
||||||
import secrets
|
|
||||||
import toml
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import toml
|
||||||
from quart_compress import Compress
|
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.captcha import create_captcha_factory, create_captcha_signer
|
||||||
from anonstream.utils.colour import color_to_colour
|
from anonstream.quart import Quart
|
||||||
from anonstream.utils.user import generate_token
|
|
||||||
|
|
||||||
compress = Compress()
|
compress = Compress()
|
||||||
|
|
||||||
def create_app(config_file):
|
def create_app(config_file):
|
||||||
with open(config_file) as fp:
|
app = Quart('anonstream')
|
||||||
config = toml.load(fp)
|
app.jinja_options['trim_blocks'] = True
|
||||||
|
app.jinja_options['lstrip_blocks'] = True
|
||||||
|
|
||||||
auth_password = secrets.token_urlsafe(6)
|
with open(config_file) as fp:
|
||||||
auth_pwhash = generate_password_hash(auth_password)
|
toml_config = toml.load(fp)
|
||||||
print('Broadcaster username:', config['auth']['username'])
|
auth_password = update_flask_from_toml(toml_config, app.config)
|
||||||
|
|
||||||
|
print('Broadcaster username:', app.config['AUTH_USERNAME'])
|
||||||
print('Broadcaster password:', auth_password)
|
print('Broadcaster password:', auth_password)
|
||||||
|
|
||||||
app = Quart('anonstream')
|
# Compress some responses
|
||||||
app.jinja_options.update({
|
compress.init_app(app)
|
||||||
'trim_blocks': True,
|
|
||||||
'lstrip_blocks': True,
|
|
||||||
})
|
|
||||||
app.config.update({
|
app.config.update({
|
||||||
'SECRET_KEY_STRING': config['secret_key'],
|
"COMPRESS_MIN_SIZE": 2048,
|
||||||
'SECRET_KEY': config['secret_key'].encode(),
|
"COMPRESS_LEVEL": 9,
|
||||||
'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']),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
assert app.config['MAX_STATES'] >= 0
|
# Global state: messages, users, captchas
|
||||||
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']
|
|
||||||
)
|
|
||||||
|
|
||||||
app.messages_by_id = OrderedDict()
|
app.messages_by_id = OrderedDict()
|
||||||
app.messages = app.messages_by_id.values()
|
app.messages = app.messages_by_id.values()
|
||||||
|
|
||||||
|
@ -110,7 +53,7 @@ def create_app(config_file):
|
||||||
|
|
||||||
@app.after_serving
|
@app.after_serving
|
||||||
async def shutdown():
|
async def shutdown():
|
||||||
# make all background tasks finish
|
# Force all background tasks to finish
|
||||||
for task in app.background_sleep:
|
for task in app.background_sleep:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
|
@ -119,11 +62,4 @@ def create_app(config_file):
|
||||||
import anonstream.routes
|
import anonstream.routes
|
||||||
import anonstream.tasks
|
import anonstream.tasks
|
||||||
|
|
||||||
# Compress some responses
|
|
||||||
compress.init_app(app)
|
|
||||||
app.config.update({
|
|
||||||
"COMPRESS_MIN_SIZE": 2048,
|
|
||||||
"COMPRESS_LEVEL": 9,
|
|
||||||
})
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -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
|
return periodically
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_PERIOD_ROTATE_USERS'])
|
@with_period(CONFIG['TASK_ROTATE_USERS'])
|
||||||
@with_timestamp
|
@with_timestamp
|
||||||
async def t_sunset_users(timestamp, iteration):
|
async def t_sunset_users(timestamp, iteration):
|
||||||
if iteration == 0:
|
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):
|
async def t_expire_captchas(iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
|
@ -86,10 +86,10 @@ async def t_expire_captchas(iteration):
|
||||||
for digest in to_delete:
|
for digest in to_delete:
|
||||||
CAPTCHAS.pop(digest)
|
CAPTCHAS.pop(digest)
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_PERIOD_ROTATE_WEBSOCKETS'])
|
@with_period(CONFIG['TASK_ROTATE_WEBSOCKETS'])
|
||||||
@with_timestamp
|
@with_timestamp
|
||||||
async def t_close_websockets(timestamp, iteration):
|
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:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -100,21 +100,21 @@ async def t_close_websockets(timestamp, iteration):
|
||||||
if last_pong_ago > THRESHOLD:
|
if last_pong_ago > THRESHOLD:
|
||||||
queue.put_nowait({'type': 'close'})
|
queue.put_nowait({'type': 'close'})
|
||||||
|
|
||||||
@with_period(CONFIG['TASK_PERIOD_BROADCAST_PING'])
|
@with_period(CONFIG['TASK_BROADCAST_PING'])
|
||||||
async def t_broadcast_ping(iteration):
|
async def t_broadcast_ping(iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
broadcast(USERS, payload={'type': 'ping'})
|
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):
|
async def t_broadcast_users_update(iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
broadcast_users_update()
|
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):
|
async def t_broadcast_stream_info_update(iteration):
|
||||||
if iteration == 0:
|
if iteration == 0:
|
||||||
title = await get_stream_title()
|
title = await get_stream_title()
|
||||||
|
@ -139,7 +139,7 @@ async def t_broadcast_stream_info_update(iteration):
|
||||||
else:
|
else:
|
||||||
expected_uptime = (
|
expected_uptime = (
|
||||||
current_app.stream_uptime
|
current_app.stream_uptime
|
||||||
+ CONFIG['TASK_PERIOD_BROADCAST_STREAM_INFO_UPDATE']
|
+ CONFIG['TASK_BROADCAST_STREAM_INFO_UPDATE']
|
||||||
)
|
)
|
||||||
current_app.stream_uptime = uptime
|
current_app.stream_uptime = uptime
|
||||||
if uptime is None and expected_uptime is None:
|
if uptime is None and expected_uptime is None:
|
||||||
|
|
|
@ -30,7 +30,7 @@ async def websocket_outbound(queue, user):
|
||||||
},
|
},
|
||||||
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
||||||
'digest': get_random_captcha_digest_for(user),
|
'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(payload)
|
||||||
await websocket.send_json({'type': 'ping'})
|
await websocket.send_json({'type': 'ping'})
|
||||||
|
|
読み込み中…
新しいイシューから参照