From 753bfc9017ee1b9addd43587167ae7b1e9ba3a76 Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 00:48:27 +0000 Subject: [PATCH 1/8] README.md: wording formatting --- README.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 09d798d..cf0c8b4 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 @@ -52,12 +55,12 @@ Run it: python -m uvicorn app:app --port 5051 ``` -This will start a webserver listening on localhost port 5051. +This will start a webserver listening on the local host at port 5051. 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 From 758213109b05e9ba4fd1ebfcd7ea414b9ab147c6 Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 00:53:26 +0000 Subject: [PATCH 2/8] HACKING.md: document control and event sockets --- HACKING.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 7 ++++++ 2 files changed, 81 insertions(+) create mode 100644 HACKING.md 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 cf0c8b4..470f584 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,12 @@ 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. +## 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 @@ -107,6 +113,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 From 309b2ad54f57f9a9ac9ab2b408eccf32843a5516 Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 01:05:18 +0000 Subject: [PATCH 3/8] Control socket: minor redo exceptions --- anonstream/control/exceptions.py | 4 ++-- anonstream/control/parse.py | 7 ++----- anonstream/control/server.py | 6 +++--- anonstream/control/spec/__init__.py | 10 ++++++++-- anonstream/control/spec/methods/exit.py | 4 ++-- anonstream/control/spec/methods/title.py | 4 ++-- anonstream/control/spec/methods/user.py | 14 +++++++------- 7 files changed, 26 insertions(+), 23 deletions(-) diff --git a/anonstream/control/exceptions.py b/anonstream/control/exceptions.py index 8b2a5a0..f1fdba5 100644 --- a/anonstream/control/exceptions.py +++ b/anonstream/control/exceptions.py @@ -1,5 +1,5 @@ -class Exit(Exception): +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..7924b62 100644 --- a/anonstream/control/parse.py +++ b/anonstream/control/parse.py @@ -1,4 +1,4 @@ -from anonstream.control.spec import NoParse, Ambiguous, Parsed +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 +25,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..caca535 100644 --- a/anonstream/control/server.py +++ b/anonstream/control/server.py @@ -1,6 +1,6 @@ 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 +15,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..aa23898 100644 --- a/anonstream/control/spec/__init__.py +++ b/anonstream/control/spec/__init__.py @@ -1,7 +1,13 @@ -class NoParse(Exception): +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/methods/exit.py b/anonstream/control/spec/methods/exit.py index 2b57dda..a409d17 100644 --- a/anonstream/control/spec/methods/exit.py +++ b/anonstream/control/spec/methods/exit.py @@ -1,8 +1,8 @@ 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/title.py b/anonstream/control/spec/methods/title.py index 754ad87..f5d22bf 100644 --- a/anonstream/control/spec/methods/title.py +++ b/anonstream/control/spec/methods/title.py @@ -1,6 +1,6 @@ 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 +27,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..e7f70df 100644 --- a/anonstream/control/spec/methods/user.py +++ b/anonstream/control/spec/methods/user.py @@ -2,8 +2,8 @@ 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 +17,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 +26,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 +69,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 +86,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']) From 1c7818cc0bb7495425f9b59ce541829e2839321f Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 01:12:37 +0000 Subject: [PATCH 4/8] Licence headers formatting --- anonstream/__init__.py | 2 +- anonstream/broadcast.py | 2 +- anonstream/captcha.py | 2 +- anonstream/chat.py | 2 +- anonstream/helpers/captcha.py | 2 +- anonstream/helpers/chat.py | 2 +- anonstream/helpers/tripcode.py | 2 +- anonstream/helpers/user.py | 2 +- anonstream/routes/__init__.py | 2 +- anonstream/routes/core.py | 2 +- anonstream/routes/nojs.py | 2 +- anonstream/routes/websocket.py | 2 +- anonstream/routes/wrappers.py | 2 +- anonstream/segments.py | 2 +- anonstream/stream.py | 2 +- anonstream/tasks.py | 2 +- anonstream/user.py | 2 +- anonstream/utils/captcha.py | 2 +- anonstream/utils/chat.py | 2 +- anonstream/utils/colour.py | 2 +- anonstream/utils/security.py | 2 +- anonstream/utils/user.py | 2 +- anonstream/utils/websocket.py | 2 +- anonstream/websocket.py | 2 +- anonstream/wrappers.py | 2 +- app.py | 2 +- 26 files changed, 26 insertions(+), 26 deletions(-) diff --git a/anonstream/__init__.py b/anonstream/__init__.py index b853268..89d1918 100644 --- a/anonstream/__init__.py +++ b/anonstream/__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 from collections import OrderedDict 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/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/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 index 303f0b1..45f5b90 100644 --- a/app.py +++ b/app.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 os From fdf4713c71e4ea2b48f41cb607747bf21bf2da1b Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 01:23:11 +0000 Subject: [PATCH 5/8] Licence headers in every new file --- anonstream/config.py | 3 +++ anonstream/control/exceptions.py | 3 +++ anonstream/control/parse.py | 3 +++ anonstream/control/server.py | 3 +++ anonstream/control/spec/__init__.py | 3 +++ anonstream/control/spec/common.py | 3 +++ anonstream/control/spec/methods/chat.py | 3 +++ anonstream/control/spec/methods/exit.py | 3 +++ anonstream/control/spec/methods/help.py | 3 +++ anonstream/control/spec/methods/title.py | 3 +++ anonstream/control/spec/methods/user.py | 3 +++ anonstream/control/spec/utils.py | 3 +++ anonstream/events.py | 3 +++ anonstream/quart.py | 6 ++++++ 14 files changed, 45 insertions(+) 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 f1fdba5..a3676b7 100644 --- a/anonstream/control/exceptions.py +++ b/anonstream/control/exceptions.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + class ControlSocketExit(Exception): pass diff --git a/anonstream/control/parse.py b/anonstream/control/parse.py index 7924b62..f456b3d 100644 --- a/anonstream/control/parse.py +++ b/anonstream/control/parse.py @@ -1,3 +1,6 @@ +# 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 diff --git a/anonstream/control/server.py b/anonstream/control/server.py index caca535..181cf41 100644 --- a/anonstream/control/server.py +++ b/anonstream/control/server.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import asyncio from anonstream.control.exceptions import ControlSocketExit, CommandFailed diff --git a/anonstream/control/spec/__init__.py b/anonstream/control/spec/__init__.py index aa23898..4a6615c 100644 --- a/anonstream/control/spec/__init__.py +++ b/anonstream/control/spec/__init__.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + class ParseException(Exception): pass 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 a409d17..30d4236 100644 --- a/anonstream/control/spec/methods/exit.py +++ b/anonstream/control/spec/methods/exit.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 from anonstream.control.exceptions import ControlSocketExit 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 f5d22bf..d099ee9 100644 --- a/anonstream/control/spec/methods/title.py +++ b/anonstream/control/spec/methods/title.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json from anonstream.control.exceptions import CommandFailed diff --git a/anonstream/control/spec/methods/user.py b/anonstream/control/spec/methods/user.py index e7f70df..dc09359 100644 --- a/anonstream/control/spec/methods/user.py +++ b/anonstream/control/spec/methods/user.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2022 n9k +# SPDX-License-Identifier: AGPL-3.0-or-later + import json from quart import current_app 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/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 From 6746f7b85936161a37fac4b883bd021aede5fcaa Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 02:56:18 +0000 Subject: [PATCH 6/8] Simplify starting: create anonstream/__main__.py --- README.md | 23 +++++++++++++++++++++-- anonstream/__main__.py | 32 ++++++++++++++++++++++++++++++++ app.py | 11 ----------- asgi.py | 23 +++++++++++++++++++++++ 4 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 anonstream/__main__.py delete mode 100644 app.py create mode 100644 asgi.py diff --git a/README.md b/README.md index 470f584..685a8cb 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,11 @@ to know what they do: Run it: ```sh -python -m uvicorn app:app --port 5051 +python -m anonstream ``` -This will start a webserver listening on the local host at 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 @@ -68,6 +69,24 @@ 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 diff --git a/anonstream/__main__.py b/anonstream/__main__.py new file mode 100644 index 0000000..f3801bc --- /dev/null +++ b/anonstream/__main__.py @@ -0,0 +1,32 @@ +import argparse +import os + +import uvicorn + +from anonstream import create_app + +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 ' + '(default: $ANONSTREAM_CONFIG or ./config.toml)' + ), +) +parser.add_argument( + '--port', '-p', + type=int, + default=5051, + help='bind webserver to this port (default: 5051)', +) +args = parser.parse_args() + +app = create_app(args.config) +uvicorn.run(app, port=args.port) diff --git a/app.py b/app.py deleted file mode 100644 index 45f5b90..0000000 --- a/app.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2022 n9k -# 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..4034f64 --- /dev/null +++ b/asgi.py @@ -0,0 +1,23 @@ +# 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 anonstream + +config = os.environ.get( + 'ANONSTREAM_CONFIG', + os.path.join(os.path.dirname(__file__), 'config.toml'), +) + +def create_app(): + return anonstream.create_app(config) From 0e7bb622913289037a759da7bd8178f2449d6c1b Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 03:14:46 +0000 Subject: [PATCH 7/8] anonstream/__main__.py: tidy magic numbers --- anonstream/__main__.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/anonstream/__main__.py b/anonstream/__main__.py index f3801bc..922e04d 100644 --- a/anonstream/__main__.py +++ b/anonstream/__main__.py @@ -5,6 +5,22 @@ 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', @@ -17,14 +33,14 @@ parser.add_argument( default=os.environ.get('ANONSTREAM_CONFIG', 'config.toml'), help=( 'location of config.toml ' - '(default: $ANONSTREAM_CONFIG or ./config.toml)' + f'(default: $ANONSTREAM_CONFIG or {want_rel(DEFAULT_CONFIG)})' ), ) parser.add_argument( '--port', '-p', type=int, - default=5051, - help='bind webserver to this port (default: 5051)', + default=DEFAULT_PORT, + help=f'bind webserver to this port (default: {DEFAULT_PORT})', ) args = parser.parse_args() From 617a68714545e554269b65c56e4bf8b0a0d14ef8 Mon Sep 17 00:00:00 2001 From: n9k Date: Thu, 16 Jun 2022 03:15:21 +0000 Subject: [PATCH 8/8] Give `create_app` a dictionary, not a file location --- anonstream/__init__.py | 6 +----- anonstream/__main__.py | 5 ++++- asgi.py | 5 ++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/anonstream/__init__.py b/anonstream/__init__.py index 89d1918..58b318d 100644 --- a/anonstream/__init__.py +++ b/anonstream/__init__.py @@ -3,7 +3,6 @@ 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 index 922e04d..212bdce 100644 --- a/anonstream/__main__.py +++ b/anonstream/__main__.py @@ -1,6 +1,7 @@ import argparse import os +import toml import uvicorn from anonstream import create_app @@ -44,5 +45,7 @@ parser.add_argument( ) args = parser.parse_args() -app = create_app(args.config) +with open(args.config) as fp: + config = toml.load(fp) +app = create_app(config) uvicorn.run(app, port=args.port) diff --git a/asgi.py b/asgi.py index 4034f64..7fbc404 100644 --- a/asgi.py +++ b/asgi.py @@ -12,12 +12,15 @@ if __name__ == '__main__': exit(1) import os +import toml import anonstream -config = os.environ.get( +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)