Merge branch 'dev'
このコミットが含まれているのは:
コミット
e9e0862445
|
@ -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
|
||||||
|
```
|
49
README.md
49
README.md
|
@ -13,7 +13,8 @@ These mirrors also exist:
|
||||||
|
|
||||||
## Setup
|
## 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:
|
Clone the repo:
|
||||||
```sh
|
```sh
|
||||||
|
@ -28,12 +29,14 @@ source venv/bin/activate
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you run it you should edit [/config.toml][config], e.g. these
|
Before you run it you may want to edit the config ([/config.toml][config]).
|
||||||
options:
|
Most of the defaults are probably okay, but here are some that you might want
|
||||||
|
to know what they do:
|
||||||
|
|
||||||
* `secret_key`:
|
* `secret_key`:
|
||||||
used for cryptography, make it any long random string
|
used for cryptography, make it any long random string (e.g.
|
||||||
(e.g. `$ dd if=/dev/urandom bs=16 count=1 | base64`)
|
`$ dd if=/dev/urandom bs=16 count=1 | base64`), definitely set this
|
||||||
|
yourself before running in "production" (whatever that is for you)
|
||||||
|
|
||||||
* `segments/directory`:
|
* `segments/directory`:
|
||||||
directory containing stream segments, the default is `stream/` in
|
directory containing stream segments, the default is `stream/` in
|
||||||
|
@ -49,15 +52,16 @@ options:
|
||||||
|
|
||||||
Run it:
|
Run it:
|
||||||
```sh
|
```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
|
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
|
the site. When you started the webserver some credentials were printed
|
||||||
printed in the terminal; you can log in with those at
|
in the terminal; you can log in with those at
|
||||||
`http://localhost:5051/login` (requires cookies).
|
`http://localhost:5051/login`.
|
||||||
|
|
||||||
The only things left are (1) streaming, and (2) letting other people
|
The only things left are (1) streaming, and (2) letting other people
|
||||||
access your stream. [/STREAMING.md][streaming] has instructions for
|
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
|
different streaming software and put your stream on the Internet some
|
||||||
other way, read those instructions and copy the gist.
|
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
|
## Copying
|
||||||
|
|
||||||
anonstream is AGPL 3.0 or later, see
|
anonstream is AGPL 3.0 or later, see
|
||||||
|
@ -104,6 +132,7 @@ anonstream is AGPL 3.0 or later, see
|
||||||
([BSD 3-Clause][werkzeug])
|
([BSD 3-Clause][werkzeug])
|
||||||
|
|
||||||
[config]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/config.toml
|
[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
|
[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
|
[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
|
[streaming]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/STREAMING.md
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
# 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
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import toml
|
|
||||||
from quart_compress import Compress
|
from quart_compress import Compress
|
||||||
|
|
||||||
from anonstream.config import update_flask_from_toml
|
from anonstream.config import update_flask_from_toml
|
||||||
|
@ -12,15 +11,12 @@ from anonstream.quart import Quart
|
||||||
|
|
||||||
compress = Compress()
|
compress = Compress()
|
||||||
|
|
||||||
def create_app(config_file):
|
def create_app(toml_config):
|
||||||
app = Quart('anonstream')
|
app = Quart('anonstream')
|
||||||
app.jinja_options['trim_blocks'] = True
|
app.jinja_options['trim_blocks'] = True
|
||||||
app.jinja_options['lstrip_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)
|
auth_password = update_flask_from_toml(toml_config, app.config)
|
||||||
|
|
||||||
print('Broadcaster username:', app.config['AUTH_USERNAME'])
|
print('Broadcaster username:', app.config['AUTH_USERNAME'])
|
||||||
print('Broadcaster password:', auth_password)
|
print('Broadcaster password:', auth_password)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -1,4 +1,4 @@
|
||||||
# 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
|
||||||
|
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 secrets
|
import secrets
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 time
|
import time
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
class Exit(Exception):
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
class ControlSocketExit(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Fail(Exception):
|
class CommandFailed(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
from anonstream.control.spec import NoParse, Ambiguous, Parsed
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# 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.common import Str
|
||||||
from anonstream.control.spec.methods.chat import SPEC as SPEC_CHAT
|
from anonstream.control.spec.methods.chat import SPEC as SPEC_CHAT
|
||||||
from anonstream.control.spec.methods.exit import SPEC as SPEC_EXIT
|
from anonstream.control.spec.methods.exit import SPEC as SPEC_EXIT
|
||||||
|
@ -25,10 +28,7 @@ async def parse(request):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
spec, n_consumed, more_args = spec.consume(words, index)
|
spec, n_consumed, more_args = spec.consume(words, index)
|
||||||
except NoParse as e:
|
except ParseException as e:
|
||||||
normal, response = None, e.args[0] + '\n'
|
|
||||||
break
|
|
||||||
except Ambiguous as e:
|
|
||||||
normal, response = None, e.args[0] + '\n'
|
normal, response = None, e.args[0] + '\n'
|
||||||
break
|
break
|
||||||
except Parsed as e:
|
except Parsed as e:
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from anonstream.control.exceptions import Exit, Fail
|
from anonstream.control.exceptions import ControlSocketExit, CommandFailed
|
||||||
from anonstream.control.parse import parse
|
from anonstream.control.parse import parse
|
||||||
|
|
||||||
def start_control_server_at(address):
|
def start_control_server_at(address):
|
||||||
|
@ -15,9 +18,9 @@ async def serve_control_client(reader, writer):
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
normal, response = await parse(request)
|
normal, response = await parse(request)
|
||||||
except Fail as e:
|
except CommandFailed as e:
|
||||||
normal, response = None, e.args[0] + '\n'
|
normal, response = None, e.args[0] + '\n'
|
||||||
except Exit:
|
except ControlSocketExit:
|
||||||
writer.close()
|
writer.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
class NoParse(Exception):
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
class ParseException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Ambiguous(Exception):
|
class NoParse(ParseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Ambiguous(ParseException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BadArgument(ParseException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Parsed(Exception):
|
class Parsed(Exception):
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from anonstream.control.spec import Spec, NoParse, Ambiguous, Parsed
|
from anonstream.control.spec import Spec, NoParse, Ambiguous, Parsed
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from anonstream.chat import delete_chat_messages
|
from anonstream.chat import delete_chat_messages
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
from anonstream.control.spec.common import Str, End
|
from anonstream.control.spec.common import Str, End
|
||||||
from anonstream.control.exceptions import Exit
|
from anonstream.control.exceptions import ControlSocketExit
|
||||||
|
|
||||||
async def cmd_exit():
|
async def cmd_exit():
|
||||||
raise Exit
|
raise ControlSocketExit
|
||||||
|
|
||||||
async def cmd_exit_help():
|
async def cmd_exit_help():
|
||||||
normal = ['exit', 'help']
|
normal = ['exit', 'help']
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
from anonstream.control.spec.common import Str, End
|
from anonstream.control.spec.common import Str, End
|
||||||
|
|
||||||
async def cmd_help():
|
async def cmd_help():
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
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 import Spec, NoParse
|
||||||
from anonstream.control.spec.common import Str, End, ArgsJsonString
|
from anonstream.control.spec.common import Str, End, ArgsJsonString
|
||||||
from anonstream.control.spec.utils import get_item, json_dumps_contiguous
|
from anonstream.control.spec.utils import get_item, json_dumps_contiguous
|
||||||
|
@ -27,7 +30,7 @@ async def cmd_title_set(title):
|
||||||
try:
|
try:
|
||||||
await set_stream_title(title)
|
await set_stream_title(title)
|
||||||
except OSError as e:
|
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)]
|
normal = ['title', 'set', json_dumps_contiguous(title)]
|
||||||
response = ''
|
response = ''
|
||||||
return normal, response
|
return normal, response
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
|
|
||||||
from anonstream.control.exceptions import Fail
|
from anonstream.control.exceptions import CommandFailed
|
||||||
from anonstream.control.spec import NoParse
|
from anonstream.control.spec import BadArgument
|
||||||
from anonstream.control.spec.common import Str, End, ArgsInt, ArgsString, ArgsJson, ArgsJsonString
|
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.control.spec.utils import get_item, json_dumps_contiguous
|
||||||
from anonstream.utils.user import USER_WEBSOCKET_ATTRS
|
from anonstream.utils.user import USER_WEBSOCKET_ATTRS
|
||||||
|
@ -17,7 +20,7 @@ class ArgsJsonTokenUser(ArgsJsonString):
|
||||||
try:
|
try:
|
||||||
user = USERS_BY_TOKEN[token]
|
user = USERS_BY_TOKEN[token]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NoParse(f'no user with token {token!r}')
|
raise BadArgument(f'no user with token {token!r}')
|
||||||
return user
|
return user
|
||||||
|
|
||||||
class ArgsJsonHashUser(ArgsString):
|
class ArgsJsonHashUser(ArgsString):
|
||||||
|
@ -26,7 +29,7 @@ class ArgsJsonHashUser(ArgsString):
|
||||||
if user['token_hash'] == token_hash:
|
if user['token_hash'] == token_hash:
|
||||||
break
|
break
|
||||||
else:
|
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
|
return user
|
||||||
|
|
||||||
def ArgsUser(spec):
|
def ArgsUser(spec):
|
||||||
|
@ -69,11 +72,11 @@ async def cmd_user_get(user, attr):
|
||||||
try:
|
try:
|
||||||
value = user[attr]
|
value = user[attr]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise Fail('user has no such attribute') from e
|
raise CommandFailed('user has no such attribute') from e
|
||||||
try:
|
try:
|
||||||
value_json = json.dumps(value)
|
value_json = json.dumps(value)
|
||||||
except (TypeError, ValueError) as e:
|
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 = [
|
normal = [
|
||||||
'user',
|
'user',
|
||||||
'get',
|
'get',
|
||||||
|
@ -86,7 +89,7 @@ async def cmd_user_get(user, attr):
|
||||||
|
|
||||||
async def cmd_user_set(user, attr, value):
|
async def cmd_user_set(user, attr, value):
|
||||||
if attr not in user:
|
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
|
user[attr] = value
|
||||||
if attr in USER_WEBSOCKET_ATTRS:
|
if attr in USER_WEBSOCKET_ATTRS:
|
||||||
USERS_UPDATE_BUFFER.add(user['token'])
|
USERS_UPDATE_BUFFER.add(user['token'])
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
def get_item(index, words):
|
def get_item(index, words):
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 base64
|
import base64
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 hashlib
|
import hashlib
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 base64
|
import base64
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 hashlib
|
import hashlib
|
||||||
|
|
|
@ -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 <https://git.076.ne.jp/ninya9k>, AGPL 3.0 or any later
|
||||||
|
# version.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from werkzeug.wrappers import Response as WerkzeugResponse
|
from werkzeug.wrappers import Response as WerkzeugResponse
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 anonstream.routes.core
|
import anonstream.routes.core
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 math
|
import math
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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
|
||||||
|
|
||||||
from quart import current_app, request, render_template, redirect, url_for, escape, Markup
|
from quart import current_app, request, render_template, redirect, url_for, escape, Markup
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 asyncio
|
import asyncio
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 hashlib
|
import hashlib
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 asyncio
|
import asyncio
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 itertools
|
import itertools
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 asyncio
|
import asyncio
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 operator
|
import operator
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 hashlib
|
import hashlib
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 base64
|
import base64
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 re
|
import re
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 secrets
|
import secrets
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 base64
|
import base64
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 asyncio
|
import asyncio
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# 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 time
|
import time
|
||||||
|
|
11
app.py
11
app.py
|
@ -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)
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
||||||
|
# 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)
|
読み込み中…
新しいイシューから参照