diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 0000000..f2136b2 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,74 @@ +## Hacking + +By default anonstream has two APIs it exposes through two UNIX sockets: +the control socket `control.sock` and the event socket `event.sock`. If +the platform you are using does not support UNIX sockets, they can be +disabled in the config. + +### Control socket + +The control socket allows reading and modifying internal state, e.g. +setting the title or changing a user's name. Currently the control +socket has checks to see if what you're doing is sane, but they're not +comprehensive; you could craft commands that lead to undefined +behaviour. If you have `socat`, you can use the control socket +interactively like this: +```sh +rlwrap socat STDIN UNIX-CONNECT:control.sock +``` +`rlwrap` only adds line editing and is optional. If you don't have it +you can still get (inferior) line editing by doing: +```sh +socat READLINE UNIX-CONNECT:control.sock +``` +Once connected, type "help" and press enter to get a list of commands. + +### Event socket + +The event socket is a read-only socket that sends out internal events as +they happen. Currently the only supported event is a chat message being +added. The intended use is to hook into other applications that depend +on chat, e.g. text-to-speech or Twitch Plays Pokémon. + +View events like this: +```sh +socat UNIX-CONNECT:event.sock STDOUT +``` + +Sidenote, this will still read from stdin, and if you send anything on +stdin the event socket will close itself. If you want to ignore stdin, +I couldn't figure out how to get `socat` to do it so you can do it like +this: +```sh +cat > /dev/null | socat UNIX-CONNECT:event.sock STDOUT +``` +If you do this `cat` will not exit when the connection is closed so you +will probably have to interrupt it with `^C`. + +#### Examples + +If you have `jq` you can view prettified events like this: +```sh +socat UNIX-CONNECT:event.sock STDOUT | jq +``` +(On older versions of `jq` you have to say `jq .` when reading from +stdin.) + +Use this to get each new chat message on a new line: +```sh +socat UNIX-CONNECT:event.sock STDOUT | jq 'select(.type == "message") | .event.nomarkup' +``` + +##### Text-to-speech + +This command will take each new chat message with the prefix "!say ", +strip the prefix, and synthesize the rest of the message as speech using +`espeak`: +```sh +socat UNIX-CONNECT:event.sock STDOUT \ +| jq --unbuffered 'select(.type == "message") | .event.nomarkup' \ +| grep -E --line-buffered '^"!say ' \ +| sed -Eu 's/^"!say /"/' \ +| jq -r --unbuffered \ +| espeak +``` diff --git a/README.md b/README.md index 09d798d..685a8cb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ These mirrors also exist: ## Setup -You must have Python 3.10 at a minimum. +You must have Python 3.10 at a minimum. You can check your version of Python +with `python --version`. Clone the repo: ```sh @@ -28,12 +29,14 @@ source venv/bin/activate python -m pip install -r requirements.txt ``` -Before you run it you should edit [/config.toml][config], e.g. these -options: +Before you run it you may want to edit the config ([/config.toml][config]). +Most of the defaults are probably okay, but here are some that you might want +to know what they do: * `secret_key`: - used for cryptography, make it any long random string - (e.g. `$ dd if=/dev/urandom bs=16 count=1 | base64`) + used for cryptography, make it any long random string (e.g. + `$ dd if=/dev/urandom bs=16 count=1 | base64`), definitely set this + yourself before running in "production" (whatever that is for you) * `segments/directory`: directory containing stream segments, the default is `stream/` in @@ -49,15 +52,16 @@ options: Run it: ```sh -python -m uvicorn app:app --port 5051 +python -m anonstream ``` -This will start a webserver listening on localhost port 5051. +This will start a webserver listening on the local host at port 5051 (use +`--port PORT` to override). If you go to `http://localhost:5051` in a web browser now you should see -the site. When you started the webserver some credentials were -printed in the terminal; you can log in with those at -`http://localhost:5051/login` (requires cookies). +the site. When you started the webserver some credentials were printed +in the terminal; you can log in with those at +`http://localhost:5051/login`. The only things left are (1) streaming, and (2) letting other people access your stream. [/STREAMING.md][streaming] has instructions for @@ -65,6 +69,30 @@ setting up OBS Studio and a Tor onion service. If you want to use different streaming software and put your stream on the Internet some other way, read those instructions and copy the gist. +## Running + +Start anonstream like this: +```sh +python -m anonstream +``` +The default port is 5051. Append `--help` to see options. + +If you want to use a different ASGI server, point it to the app factory +at `asgi:create_app()`. For example with `uvicorn`: +```sh +python -m uvicorn asgi:create_app --factory --port 5051 +``` + +In either case you can explicitly set the location of the config file +using the `ANONSTREAM_CONFIG` environment variable. + + +## Hacking + +anonstream has APIs for accessing internal state and hooking into +internal events. They can be used by humans and other programs. See +[/HACKING.md][hacking]. + ## Copying anonstream is AGPL 3.0 or later, see @@ -104,6 +132,7 @@ anonstream is AGPL 3.0 or later, see ([BSD 3-Clause][werkzeug]) [config]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/config.toml +[hacking]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/HACKING.md [licence]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/LICENSES/AGPL-3.0-or-later.md [settings.svg]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/anonstream/static/settings.svg [streaming]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/STREAMING.md diff --git a/anonstream/__init__.py b/anonstream/__init__.py index b853268..58b318d 100644 --- a/anonstream/__init__.py +++ b/anonstream/__init__.py @@ -1,9 +1,8 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later from collections import OrderedDict -import toml from quart_compress import Compress from anonstream.config import update_flask_from_toml @@ -12,15 +11,12 @@ from anonstream.quart import Quart compress = Compress() -def create_app(config_file): +def create_app(toml_config): app = Quart('anonstream') app.jinja_options['trim_blocks'] = True app.jinja_options['lstrip_blocks'] = True - 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) diff --git a/anonstream/__main__.py b/anonstream/__main__.py new file mode 100644 index 0000000..212bdce --- /dev/null +++ b/anonstream/__main__.py @@ -0,0 +1,51 @@ +import argparse +import os + +import toml +import uvicorn + +from anonstream import create_app + +DEFAULT_PORT = 5051 +DEFAULT_CONFIG = 'config.toml' + +def want_rel(path): + ''' + Prepend './' to relative paths. + >>> want_rel('/some/abs/path') + '/some/abs/path' + >>> want_rel('config.toml') + './config.toml' + ''' + if os.path.isabs(path): + return path + else: + return os.path.join('.', path) + +formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=26) +parser = argparse.ArgumentParser( + 'python -m anonstream', + description='Start the anonstream webserver locally.', + formatter_class=formatter, +) +parser.add_argument( + '--config', '-c', + metavar='FILE', + default=os.environ.get('ANONSTREAM_CONFIG', 'config.toml'), + help=( + 'location of config.toml ' + f'(default: $ANONSTREAM_CONFIG or {want_rel(DEFAULT_CONFIG)})' + ), +) +parser.add_argument( + '--port', '-p', + type=int, + default=DEFAULT_PORT, + help=f'bind webserver to this port (default: {DEFAULT_PORT})', +) +args = parser.parse_args() + +with open(args.config) as fp: + config = toml.load(fp) +app = create_app(config) +uvicorn.run(app, port=args.port) diff --git a/anonstream/broadcast.py b/anonstream/broadcast.py index 63524a6..351d369 100644 --- a/anonstream/broadcast.py +++ b/anonstream/broadcast.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later from quart import current_app diff --git a/anonstream/captcha.py b/anonstream/captcha.py index 76badbb..fb1b0af 100644 --- a/anonstream/captcha.py +++ b/anonstream/captcha.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import secrets diff --git a/anonstream/chat.py b/anonstream/chat.py index 7f9986e..c30ce69 100644 --- a/anonstream/chat.py +++ b/anonstream/chat.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import time diff --git a/anonstream/config.py b/anonstream/config.py index 15ed3b0..0fdced9 100644 --- a/anonstream/config.py +++ b/anonstream/config.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import os import secrets diff --git a/anonstream/control/exceptions.py b/anonstream/control/exceptions.py index 8b2a5a0..a3676b7 100644 --- a/anonstream/control/exceptions.py +++ b/anonstream/control/exceptions.py @@ -1,5 +1,8 @@ -class Exit(Exception): +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + +class ControlSocketExit(Exception): pass -class Fail(Exception): +class CommandFailed(Exception): pass diff --git a/anonstream/control/parse.py b/anonstream/control/parse.py index 0f3c2fd..f456b3d 100644 --- a/anonstream/control/parse.py +++ b/anonstream/control/parse.py @@ -1,4 +1,7 @@ -from anonstream.control.spec import NoParse, Ambiguous, Parsed +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + +from anonstream.control.spec import ParseException, Parsed from anonstream.control.spec.common import Str from anonstream.control.spec.methods.chat import SPEC as SPEC_CHAT from anonstream.control.spec.methods.exit import SPEC as SPEC_EXIT @@ -25,10 +28,7 @@ async def parse(request): while True: try: spec, n_consumed, more_args = spec.consume(words, index) - except NoParse as e: - normal, response = None, e.args[0] + '\n' - break - except Ambiguous as e: + except ParseException as e: normal, response = None, e.args[0] + '\n' break except Parsed as e: diff --git a/anonstream/control/server.py b/anonstream/control/server.py index df8671b..181cf41 100644 --- a/anonstream/control/server.py +++ b/anonstream/control/server.py @@ -1,6 +1,9 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import asyncio -from anonstream.control.exceptions import Exit, Fail +from anonstream.control.exceptions import ControlSocketExit, CommandFailed from anonstream.control.parse import parse def start_control_server_at(address): @@ -15,9 +18,9 @@ async def serve_control_client(reader, writer): else: try: normal, response = await parse(request) - except Fail as e: + except CommandFailed as e: normal, response = None, e.args[0] + '\n' - except Exit: + except ControlSocketExit: writer.close() break diff --git a/anonstream/control/spec/__init__.py b/anonstream/control/spec/__init__.py index 34e02a2..4a6615c 100644 --- a/anonstream/control/spec/__init__.py +++ b/anonstream/control/spec/__init__.py @@ -1,7 +1,16 @@ -class NoParse(Exception): +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + +class ParseException(Exception): pass -class Ambiguous(Exception): +class NoParse(ParseException): + pass + +class Ambiguous(ParseException): + pass + +class BadArgument(ParseException): pass class Parsed(Exception): diff --git a/anonstream/control/spec/common.py b/anonstream/control/spec/common.py index 0ecd92f..7091dc0 100644 --- a/anonstream/control/spec/common.py +++ b/anonstream/control/spec/common.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json from anonstream.control.spec import Spec, NoParse, Ambiguous, Parsed diff --git a/anonstream/control/spec/methods/chat.py b/anonstream/control/spec/methods/chat.py index a565602..a77f822 100644 --- a/anonstream/control/spec/methods/chat.py +++ b/anonstream/control/spec/methods/chat.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import itertools from anonstream.chat import delete_chat_messages diff --git a/anonstream/control/spec/methods/exit.py b/anonstream/control/spec/methods/exit.py index 2b57dda..30d4236 100644 --- a/anonstream/control/spec/methods/exit.py +++ b/anonstream/control/spec/methods/exit.py @@ -1,8 +1,11 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + from anonstream.control.spec.common import Str, End -from anonstream.control.exceptions import Exit +from anonstream.control.exceptions import ControlSocketExit async def cmd_exit(): - raise Exit + raise ControlSocketExit async def cmd_exit_help(): normal = ['exit', 'help'] diff --git a/anonstream/control/spec/methods/help.py b/anonstream/control/spec/methods/help.py index 25f6367..03a118f 100644 --- a/anonstream/control/spec/methods/help.py +++ b/anonstream/control/spec/methods/help.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + from anonstream.control.spec.common import Str, End async def cmd_help(): diff --git a/anonstream/control/spec/methods/title.py b/anonstream/control/spec/methods/title.py index 754ad87..d099ee9 100644 --- a/anonstream/control/spec/methods/title.py +++ b/anonstream/control/spec/methods/title.py @@ -1,6 +1,9 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json -from anonstream.control.exceptions import Fail +from anonstream.control.exceptions import CommandFailed from anonstream.control.spec import Spec, NoParse from anonstream.control.spec.common import Str, End, ArgsJsonString from anonstream.control.spec.utils import get_item, json_dumps_contiguous @@ -27,7 +30,7 @@ async def cmd_title_set(title): try: await set_stream_title(title) except OSError as e: - raise Fail(f'could not set title: {e}') from e + raise CommandFailed(f'could not set title: {e}') from e normal = ['title', 'set', json_dumps_contiguous(title)] response = '' return normal, response diff --git a/anonstream/control/spec/methods/user.py b/anonstream/control/spec/methods/user.py index 147f066..dc09359 100644 --- a/anonstream/control/spec/methods/user.py +++ b/anonstream/control/spec/methods/user.py @@ -1,9 +1,12 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json from quart import current_app -from anonstream.control.exceptions import Fail -from anonstream.control.spec import NoParse +from anonstream.control.exceptions import CommandFailed +from anonstream.control.spec import BadArgument from anonstream.control.spec.common import Str, End, ArgsInt, ArgsString, ArgsJson, ArgsJsonString from anonstream.control.spec.utils import get_item, json_dumps_contiguous from anonstream.utils.user import USER_WEBSOCKET_ATTRS @@ -17,7 +20,7 @@ class ArgsJsonTokenUser(ArgsJsonString): try: user = USERS_BY_TOKEN[token] except KeyError: - raise NoParse(f'no user with token {token!r}') + raise BadArgument(f'no user with token {token!r}') return user class ArgsJsonHashUser(ArgsString): @@ -26,7 +29,7 @@ class ArgsJsonHashUser(ArgsString): if user['token_hash'] == token_hash: break else: - raise NoParse(f'no user with token_hash {token_hash!r}') + raise BadArgument(f'no user with token_hash {token_hash!r}') return user def ArgsUser(spec): @@ -69,11 +72,11 @@ async def cmd_user_get(user, attr): try: value = user[attr] except KeyError as e: - raise Fail('user has no such attribute') from e + raise CommandFailed('user has no such attribute') from e try: value_json = json.dumps(value) except (TypeError, ValueError) as e: - raise Fail('value is not representable in json') from e + raise CommandFailed('value is not representable in json') from e normal = [ 'user', 'get', @@ -86,7 +89,7 @@ async def cmd_user_get(user, attr): async def cmd_user_set(user, attr, value): if attr not in user: - raise Fail(f'user has no attribute {attr!r}') + raise CommandFailed(f'user has no attribute {attr!r}') user[attr] = value if attr in USER_WEBSOCKET_ATTRS: USERS_UPDATE_BUFFER.add(user['token']) diff --git a/anonstream/control/spec/utils.py b/anonstream/control/spec/utils.py index 6d9468a..8ecc994 100644 --- a/anonstream/control/spec/utils.py +++ b/anonstream/control/spec/utils.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json def get_item(index, words): diff --git a/anonstream/events.py b/anonstream/events.py index df24326..eb19fd6 100644 --- a/anonstream/events.py +++ b/anonstream/events.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import asyncio import json diff --git a/anonstream/helpers/captcha.py b/anonstream/helpers/captcha.py index 7006f95..7e9a809 100644 --- a/anonstream/helpers/captcha.py +++ b/anonstream/helpers/captcha.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import base64 diff --git a/anonstream/helpers/chat.py b/anonstream/helpers/chat.py index 1790a19..3feac2a 100644 --- a/anonstream/helpers/chat.py +++ b/anonstream/helpers/chat.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import hashlib diff --git a/anonstream/helpers/tripcode.py b/anonstream/helpers/tripcode.py index 353a7ce..9275bd8 100644 --- a/anonstream/helpers/tripcode.py +++ b/anonstream/helpers/tripcode.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import base64 diff --git a/anonstream/helpers/user.py b/anonstream/helpers/user.py index f24301f..f18561e 100644 --- a/anonstream/helpers/user.py +++ b/anonstream/helpers/user.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import hashlib diff --git a/anonstream/quart.py b/anonstream/quart.py index dfd5e16..c00fe86 100644 --- a/anonstream/quart.py +++ b/anonstream/quart.py @@ -1,3 +1,9 @@ +# This file is pretty much entirely based on a snippet from asgi.py in +# the Quart repository (MIT, see README.md). That means it takes on the +# MIT licence I guess(???) If not then it's the same as every other file +# by me: 2022 n9k , AGPL 3.0 or any later +# version. + import asyncio from werkzeug.wrappers import Response as WerkzeugResponse diff --git a/anonstream/routes/__init__.py b/anonstream/routes/__init__.py index 0d05bfc..2bc5259 100644 --- a/anonstream/routes/__init__.py +++ b/anonstream/routes/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import anonstream.routes.core diff --git a/anonstream/routes/core.py b/anonstream/routes/core.py index c4be7de..92425be 100644 --- a/anonstream/routes/core.py +++ b/anonstream/routes/core.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import math diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index 967f894..569269e 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later from quart import current_app, request, render_template, redirect, url_for, escape, Markup diff --git a/anonstream/routes/websocket.py b/anonstream/routes/websocket.py index 933c9c8..12eafd4 100644 --- a/anonstream/routes/websocket.py +++ b/anonstream/routes/websocket.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import asyncio diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index f191166..c5ac817 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import hashlib diff --git a/anonstream/segments.py b/anonstream/segments.py index cc4edca..84c5570 100644 --- a/anonstream/segments.py +++ b/anonstream/segments.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import asyncio diff --git a/anonstream/stream.py b/anonstream/stream.py index 0ee9e52..0d77d87 100644 --- a/anonstream/stream.py +++ b/anonstream/stream.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import itertools diff --git a/anonstream/tasks.py b/anonstream/tasks.py index 802bf40..565e4e1 100644 --- a/anonstream/tasks.py +++ b/anonstream/tasks.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import asyncio diff --git a/anonstream/user.py b/anonstream/user.py index 32ea0a7..0edfa56 100644 --- a/anonstream/user.py +++ b/anonstream/user.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import operator diff --git a/anonstream/utils/captcha.py b/anonstream/utils/captcha.py index f4655e4..d10b84b 100644 --- a/anonstream/utils/captcha.py +++ b/anonstream/utils/captcha.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import hashlib diff --git a/anonstream/utils/chat.py b/anonstream/utils/chat.py index 0b4d5f7..e0ac7fd 100644 --- a/anonstream/utils/chat.py +++ b/anonstream/utils/chat.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import base64 diff --git a/anonstream/utils/colour.py b/anonstream/utils/colour.py index d20880a..9ee03ad 100644 --- a/anonstream/utils/colour.py +++ b/anonstream/utils/colour.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import re diff --git a/anonstream/utils/security.py b/anonstream/utils/security.py index c1b1e62..92e3252 100644 --- a/anonstream/utils/security.py +++ b/anonstream/utils/security.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import secrets diff --git a/anonstream/utils/user.py b/anonstream/utils/user.py index 8a01228..ed2c12e 100644 --- a/anonstream/utils/user.py +++ b/anonstream/utils/user.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import base64 diff --git a/anonstream/utils/websocket.py b/anonstream/utils/websocket.py index 294b75f..c83e6f3 100644 --- a/anonstream/utils/websocket.py +++ b/anonstream/utils/websocket.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later from enum import Enum diff --git a/anonstream/websocket.py b/anonstream/websocket.py index 4878e1e..4a48132 100644 --- a/anonstream/websocket.py +++ b/anonstream/websocket.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import asyncio diff --git a/anonstream/wrappers.py b/anonstream/wrappers.py index 3a8ead1..50535fb 100644 --- a/anonstream/wrappers.py +++ b/anonstream/wrappers.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] +# SPDX-FileCopyrightText: 2022 n9k # SPDX-License-Identifier: AGPL-3.0-or-later import time diff --git a/app.py b/app.py deleted file mode 100644 index 303f0b1..0000000 --- a/app.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2022 n9k [https://git.076.ne.jp/ninya9k] -# SPDX-License-Identifier: AGPL-3.0-or-later - -import os -import anonstream - -config_file = os.path.join(os.path.dirname(__file__), 'config.toml') -app = anonstream.create_app(config_file) - -if __name__ == '__main__': - app.run(port=5051, debug=True) diff --git a/asgi.py b/asgi.py new file mode 100644 index 0000000..7fbc404 --- /dev/null +++ b/asgi.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + +if __name__ == '__main__': + import sys + message = ( + 'To start anonstream, run one of:\n' + ' $ python -m anonstream\n' + ' $ python -m uvicorn asgi:create_app --factory --port 5051\n' + ) + print(message, file=sys.stderr, end='') + exit(1) + +import os +import toml +import anonstream + +config_file = os.environ.get( + 'ANONSTREAM_CONFIG', + os.path.join(os.path.dirname(__file__), 'config.toml'), +) + +def create_app(): + with open(config_file) as fp: + config = toml.load(fp) + return anonstream.create_app(config)