コミットを比較
22 コミット
作成者 | SHA1 | 日付 |
---|---|---|
n9k | beafe88324 | |
n9k | a6c31179b6 | |
n9k | ad7cc1c5b1 | |
n9k | 022bebed73 | |
n9k | a97f3254bd | |
n9k | ea2a194c93 | |
n9k | 12338747de | |
n9k | 8426a3490a | |
n9k | 3fca390a30 | |
n9k | 26a86fac7a | |
n9k | 071edaef3a | |
n9k | 6e9ba1a5db | |
n9k | 78753f7e0c | |
n9k | d05c5fec31 | |
n9k | 2a67bee82c | |
n9k | cbd494e3bf | |
n9k | b9c29a6fdd | |
n9k | 4d192392c4 | |
n9k | 2599528ae3 | |
n9k | 72d5a0526c | |
n9k | d7b4717cf5 | |
n9k | 68d6efff4e |
|
@ -5,7 +5,7 @@ Recipe for livestreaming over Tor
|
||||||
## Repo
|
## Repo
|
||||||
|
|
||||||
The canonical location of this repo is
|
The canonical location of this repo is
|
||||||
<https://git.076.ne.jp/ninya9k/anonstream>.
|
<https://gitler.moe/ninya9k/anonstream>.
|
||||||
|
|
||||||
These mirrors also exist:
|
These mirrors also exist:
|
||||||
* <https://gitlab.com/ninya9k/anonstream>
|
* <https://gitlab.com/ninya9k/anonstream>
|
||||||
|
@ -18,7 +18,7 @@ Python with `python --version`.
|
||||||
|
|
||||||
Clone the repo:
|
Clone the repo:
|
||||||
```sh
|
```sh
|
||||||
git clone https://git.076.ne.jp/ninya9k/anonstream.git
|
git clone https://gitler.moe/ninya9k/anonstream.git
|
||||||
cd anonstream
|
cd anonstream
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -136,8 +136,8 @@ anonstream is AGPL 3.0 or later, see
|
||||||
* werkzeug <https://github.com/pallets/werkzeug>
|
* werkzeug <https://github.com/pallets/werkzeug>
|
||||||
([BSD 3-Clause][werkzeug])
|
([BSD 3-Clause][werkzeug])
|
||||||
|
|
||||||
[licence]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/LICENSES/AGPL-3.0-or-later.md
|
[licence]: https://gitler.moe/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://gitler.moe/ninya9k/anonstream/src/branch/master/anonstream/static/settings.svg
|
||||||
|
|
||||||
[aiofiles]: https://github.com/Tinche/aiofiles/blob/master/LICENSE
|
[aiofiles]: https://github.com/Tinche/aiofiles/blob/master/LICENSE
|
||||||
[captcha]: https://github.com/lepture/captcha/blob/master/LICENSE
|
[captcha]: https://github.com/lepture/captcha/blob/master/LICENSE
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -13,7 +13,7 @@ from anonstream.quart import Quart
|
||||||
from anonstream.utils.captcha import create_captcha_factory, create_captcha_signer
|
from anonstream.utils.captcha import create_captcha_factory, create_captcha_signer
|
||||||
from anonstream.utils.user import generate_blank_allowedness
|
from anonstream.utils.user import generate_blank_allowedness
|
||||||
|
|
||||||
__version__ = '1.6.6'
|
__version__ = '1.6.9'
|
||||||
|
|
||||||
def create_app(toml_config):
|
def create_app(toml_config):
|
||||||
app = Quart('anonstream', static_folder=None)
|
app = Quart('anonstream', static_folder=None)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
class ControlSocketExit(Exception):
|
class ControlSocketExit(Exception):
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
from anonstream.control.spec import ParseException, Parsed
|
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.allowedness import SPEC as SPEC_ALLOWEDNESS
|
from anonstream.control.spec.methods.allowedness import SPEC as SPEC_ALLOWEDNESS
|
||||||
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.config import SPEC as SPEC_CONFIG
|
||||||
from anonstream.control.spec.methods.emote import SPEC as SPEC_EMOTE
|
from anonstream.control.spec.methods.emote import SPEC as SPEC_EMOTE
|
||||||
from anonstream.control.spec.methods.help import SPEC as SPEC_HELP
|
from anonstream.control.spec.methods.help import SPEC as SPEC_HELP
|
||||||
from anonstream.control.spec.methods.quit import SPEC as SPEC_QUIT
|
from anonstream.control.spec.methods.quit import SPEC as SPEC_QUIT
|
||||||
from anonstream.control.spec.methods.title import SPEC as SPEC_TITLE
|
from anonstream.control.spec.methods.title import SPEC as SPEC_TITLE
|
||||||
|
from anonstream.control.spec.methods.tripcode import SPEC as SPEC_TRIPCODE
|
||||||
from anonstream.control.spec.methods.user import SPEC as SPEC_USER
|
from anonstream.control.spec.methods.user import SPEC as SPEC_USER
|
||||||
|
|
||||||
SPEC = Str({
|
SPEC = Str({
|
||||||
|
@ -19,6 +21,8 @@ SPEC = Str({
|
||||||
'user': SPEC_USER,
|
'user': SPEC_USER,
|
||||||
'allowednesss': SPEC_ALLOWEDNESS,
|
'allowednesss': SPEC_ALLOWEDNESS,
|
||||||
'emote': SPEC_EMOTE,
|
'emote': SPEC_EMOTE,
|
||||||
|
'config': SPEC_CONFIG,
|
||||||
|
'tripcode': SPEC_TRIPCODE,
|
||||||
})
|
})
|
||||||
|
|
||||||
async def parse(request):
|
async def parse(request):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
class ParseException(Exception):
|
class ParseException(Exception):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
|
|
||||||
|
from quart import current_app
|
||||||
|
|
||||||
from anonstream.chat import delete_chat_messages
|
from anonstream.chat import delete_chat_messages
|
||||||
from anonstream.control.exceptions import CommandFailed
|
from anonstream.control.exceptions import CommandFailed
|
||||||
|
@ -10,6 +13,8 @@ from anonstream.control.spec.common import Str, End, Args, ArgsJsonString, ArgsU
|
||||||
from anonstream.control.spec.utils import get_item, json_dumps_contiguous
|
from anonstream.control.spec.utils import get_item, json_dumps_contiguous
|
||||||
from anonstream.chat import add_chat_message, Rejected
|
from anonstream.chat import add_chat_message, Rejected
|
||||||
|
|
||||||
|
MESSAGES = current_app.messages
|
||||||
|
|
||||||
class ArgsSeqs(Args):
|
class ArgsSeqs(Args):
|
||||||
def consume(self, words, index):
|
def consume(self, words, index):
|
||||||
seqs = []
|
seqs = []
|
||||||
|
@ -33,13 +38,12 @@ class ArgsSeqs(Args):
|
||||||
async def cmd_chat_help():
|
async def cmd_chat_help():
|
||||||
normal = ['chat', 'help']
|
normal = ['chat', 'help']
|
||||||
response = (
|
response = (
|
||||||
'Usage: chat delete SEQS\n'
|
'Usage: chat {show | delete SEQS | add USER NONCE COMMENT}\n'
|
||||||
'Commands:\n'
|
'Commands:\n'
|
||||||
#' chat show [MESSAGES]...........show chat messages\n'
|
' chat show......................show all chat messages\n'
|
||||||
' chat delete SEQS...............delete chat messages\n'
|
' chat delete SEQS...............delete chat messages\n'
|
||||||
' chat add USER NONCE COMMENT....add chat message\n'
|
' chat add USER NONCE COMMENT....add chat message\n'
|
||||||
'Definitions:\n'
|
'Definitions:\n'
|
||||||
#' MESSAGES...................undefined\n'
|
|
||||||
' SEQS.......................=SEQ [SEQ...]\n'
|
' SEQS.......................=SEQ [SEQ...]\n'
|
||||||
' SEQ........................a chat message\'s seq, base-10 integer\n'
|
' SEQ........................a chat message\'s seq, base-10 integer\n'
|
||||||
' USER.......................={token TOKEN | hash HASH}\n'
|
' USER.......................={token TOKEN | hash HASH}\n'
|
||||||
|
@ -50,6 +54,11 @@ async def cmd_chat_help():
|
||||||
)
|
)
|
||||||
return normal, response
|
return normal, response
|
||||||
|
|
||||||
|
async def cmd_chat_show():
|
||||||
|
normal = ['chat', 'show']
|
||||||
|
response = json.dumps(tuple(MESSAGES), separators=(',', ':')) + '\n'
|
||||||
|
return normal, response
|
||||||
|
|
||||||
async def cmd_chat_delete(*seqs):
|
async def cmd_chat_delete(*seqs):
|
||||||
delete_chat_messages(seqs)
|
delete_chat_messages(seqs)
|
||||||
normal = ['chat', 'delete', *map(str, seqs)]
|
normal = ['chat', 'delete', *map(str, seqs)]
|
||||||
|
@ -74,6 +83,7 @@ async def cmd_chat_add(user, nonce, comment):
|
||||||
SPEC = Str({
|
SPEC = Str({
|
||||||
None: End(cmd_chat_help),
|
None: End(cmd_chat_help),
|
||||||
'help': End(cmd_chat_help),
|
'help': End(cmd_chat_help),
|
||||||
|
'show': End(cmd_chat_show),
|
||||||
'delete': ArgsSeqs(End(cmd_chat_delete)),
|
'delete': ArgsSeqs(End(cmd_chat_delete)),
|
||||||
'add': ArgsUser(ArgsJsonString(ArgsJsonString(End(cmd_chat_add)))),
|
'add': ArgsUser(ArgsJsonString(ArgsJsonString(End(cmd_chat_add)))),
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from quart import current_app
|
||||||
|
|
||||||
|
from anonstream.control.exceptions import CommandFailed
|
||||||
|
from anonstream.control.spec.common import Str, End, ArgsString
|
||||||
|
|
||||||
|
CONFIG = current_app.config
|
||||||
|
|
||||||
|
async def cmd_config_help():
|
||||||
|
normal = ['config', 'help']
|
||||||
|
response = (
|
||||||
|
'Usage: config show OPTION\n'
|
||||||
|
'Commands:\n'
|
||||||
|
' config show OPTION....show entry in app.config\n'
|
||||||
|
'Definitions:\n'
|
||||||
|
' OPTION................app.config key, re:[A-Z0-9_]+\n'
|
||||||
|
)
|
||||||
|
return normal, response
|
||||||
|
|
||||||
|
async def cmd_config_show(option):
|
||||||
|
if option in {'SECRET_KEY', 'SECRET_KEY_STRING'}:
|
||||||
|
raise CommandFailed('not going to show our secret key')
|
||||||
|
try:
|
||||||
|
value = CONFIG[option]
|
||||||
|
except KeyError:
|
||||||
|
raise CommandFailed(f'no config option with key {option!r}')
|
||||||
|
try:
|
||||||
|
value_json = json.dumps(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
raise CommandFailed(f'value is not json serializable')
|
||||||
|
normal = ['config', 'show']
|
||||||
|
response = value_json + '\n'
|
||||||
|
return normal, response
|
||||||
|
|
||||||
|
SPEC = Str({
|
||||||
|
None: End(cmd_config_help),
|
||||||
|
'help': End(cmd_config_help),
|
||||||
|
'show': ArgsString(End(cmd_config_show)),
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -16,7 +16,7 @@ EMOTES = current_app.emotes
|
||||||
async def cmd_emote_help():
|
async def cmd_emote_help():
|
||||||
normal = ['emote', 'help']
|
normal = ['emote', 'help']
|
||||||
response = (
|
response = (
|
||||||
'Usage: emote [show | reload]\n'
|
'Usage: emote {show | reload}\n'
|
||||||
'Commands:\n'
|
'Commands:\n'
|
||||||
' emote show........show all current emotes\n'
|
' emote show........show all current emotes\n'
|
||||||
' emote reload......try to reload the emote schema (existing messages are not modified)\n'
|
' emote reload......try to reload the emote schema (existing messages are not modified)\n'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
from anonstream.control.spec.common import Str, End
|
from anonstream.control.spec.common import Str, End
|
||||||
|
@ -19,15 +19,17 @@ async def cmd_help():
|
||||||
' user eyes USER [show]..........show a list of active video responses\n'
|
' user eyes USER [show]..........show a list of active video responses\n'
|
||||||
' user eyes USER delete EYES_ID..end a video response\n'
|
' user eyes USER delete EYES_ID..end a video response\n'
|
||||||
' user add VERIFIED TOKEN........add new user\n'
|
' user add VERIFIED TOKEN........add new user\n'
|
||||||
#' chat show MESSAGES.............show a list of messages\n'
|
' chat show......................show a list of all chat messages\n'
|
||||||
' chat delete SEQS...............delete a set of messages\n'
|
' chat delete SEQS...............delete a set of chat messages\n'
|
||||||
' chat add USER NONCE COMMENT....add chat message\n'
|
' chat add USER NONCE COMMENT....add a chat message\n'
|
||||||
' allowedness [show].............show the current allowedness\n'
|
' allowedness [show].............show the current allowedness\n'
|
||||||
' allowedness setdefault BOOLEAN.set the default allowedness\n'
|
' allowedness setdefault BOOLEAN.set the default allowedness\n'
|
||||||
' allowedness add SET STRING.....add to the blacklist/whitelist\n'
|
' allowedness add SET STRING.....add to the blacklist/whitelist\n'
|
||||||
' allowedness remove SET STRING..remove from the blacklist/whitelist\n'
|
' allowedness remove SET STRING..remove from the blacklist/whitelist\n'
|
||||||
' emote show.....................show all current emotes\n'
|
' emote show.....................show all current emotes\n'
|
||||||
' emote reload...................try reloading the emote schema\n'
|
' emote reload...................try reloading the emote schema\n'
|
||||||
|
' config show OPTION.............show app config option\n'
|
||||||
|
' tripcode generate PASSWORD.....show tripcode for given password\n'
|
||||||
)
|
)
|
||||||
return normal, response
|
return normal, response
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
from anonstream.control.spec.common import Str, End
|
from anonstream.control.spec.common import Str, End
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -12,7 +12,7 @@ from anonstream.stream import get_stream_title, set_stream_title
|
||||||
async def cmd_title_help():
|
async def cmd_title_help():
|
||||||
normal = ['title', 'help']
|
normal = ['title', 'help']
|
||||||
response = (
|
response = (
|
||||||
'Usage: title [show | set TITLE]\n'
|
'Usage: title {show | set TITLE}\n'
|
||||||
'Commands:\n'
|
'Commands:\n'
|
||||||
' title [show].......show the stream title\n'
|
' title [show].......show the stream title\n'
|
||||||
' title set TITLE....set the stream title to TITLE\n'
|
' title set TITLE....set the stream title to TITLE\n'
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from quart import current_app
|
||||||
|
|
||||||
|
from anonstream.control.exceptions import CommandFailed
|
||||||
|
from anonstream.control.spec.common import Str, End, ArgsJsonString
|
||||||
|
from anonstream.control.spec.utils import json_dumps_contiguous
|
||||||
|
from anonstream.helpers.tripcode import generate_tripcode
|
||||||
|
|
||||||
|
CONFIG = current_app.config
|
||||||
|
|
||||||
|
async def cmd_tripcode_help():
|
||||||
|
normal = ['tripcode', 'help']
|
||||||
|
response = (
|
||||||
|
'Usage: tripcode generate PASSWORD\n'
|
||||||
|
'Commands:\n'
|
||||||
|
' tripcode generate PASSWORD....show tripcode for given password\n'
|
||||||
|
'Definitions:\n'
|
||||||
|
' PASSWORD................json string, max length in config.toml (`chat.max_tripcode_password_length`)\n'
|
||||||
|
)
|
||||||
|
return normal, response
|
||||||
|
|
||||||
|
async def cmd_tripcode_generate(password):
|
||||||
|
if len(password) > CONFIG['CHAT_TRIPCODE_PASSWORD_MAX_LENGTH']:
|
||||||
|
raise CommandFailed(
|
||||||
|
f'password exceeded maximum configured length of '
|
||||||
|
f'{CONFIG["CHAT_TRIPCODE_PASSWORD_MAX_LENGTH"]} '
|
||||||
|
f'characters'
|
||||||
|
)
|
||||||
|
tripcode = generate_tripcode(password)
|
||||||
|
normal = ['tripcode', 'generate', json_dumps_contiguous(password)]
|
||||||
|
response = json.dumps(tripcode) + '\n'
|
||||||
|
return normal, response
|
||||||
|
|
||||||
|
SPEC = Str({
|
||||||
|
None: End(cmd_tripcode_help),
|
||||||
|
'help': End(cmd_tripcode_help),
|
||||||
|
'generate': ArgsJsonString(End(cmd_tripcode_generate)),
|
||||||
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -58,10 +58,8 @@ async def cmd_user_get(user, attr):
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
raise CommandFailed('value is not representable in json') from e
|
raise CommandFailed('value is not representable in json') from e
|
||||||
normal = [
|
normal = [
|
||||||
'user',
|
'user', 'get',
|
||||||
'get',
|
'token', json_dumps_contiguous(user['token']),
|
||||||
'token',
|
|
||||||
json_dumps_contiguous(user['token']),
|
|
||||||
attr,
|
attr,
|
||||||
]
|
]
|
||||||
response = value_json + '\n'
|
response = value_json + '\n'
|
||||||
|
@ -74,10 +72,8 @@ async def cmd_user_set(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'])
|
||||||
normal = [
|
normal = [
|
||||||
'user',
|
'user', 'set',
|
||||||
'set',
|
'token', json_dumps_contiguous(user['token']),
|
||||||
'token',
|
|
||||||
json_dumps_contiguous(user['token']),
|
|
||||||
attr,
|
attr,
|
||||||
json_dumps_contiguous(value),
|
json_dumps_contiguous(value),
|
||||||
]
|
]
|
||||||
|
@ -86,11 +82,9 @@ async def cmd_user_set(user, attr, value):
|
||||||
|
|
||||||
async def cmd_user_eyes_show(user):
|
async def cmd_user_eyes_show(user):
|
||||||
normal = [
|
normal = [
|
||||||
'user',
|
'user', 'eyes',
|
||||||
'eyes',
|
'token', json_dumps_contiguous(user['token']),
|
||||||
'token',
|
'show',
|
||||||
json_dumps_contiguous(user['token']),
|
|
||||||
'show'
|
|
||||||
]
|
]
|
||||||
response = json.dumps(user['eyes']['current']) + '\n'
|
response = json.dumps(user['eyes']['current']) + '\n'
|
||||||
return normal, response
|
return normal, response
|
||||||
|
@ -101,12 +95,9 @@ async def cmd_user_eyes_delete(user, eyes_id):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
normal = [
|
normal = [
|
||||||
'user',
|
'user', 'eyes',
|
||||||
'eyes',
|
'token', json_dumps_contiguous(user['token']),
|
||||||
'token',
|
'delete', str(eyes_id),
|
||||||
json_dumps_contiguous(user['token']),
|
|
||||||
'delete',
|
|
||||||
str(eyes_id),
|
|
||||||
]
|
]
|
||||||
response = ''
|
response = ''
|
||||||
return normal, response
|
return normal, response
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/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://gitler.moe/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://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is pretty much entirely based on a snippet from asgi.py in
|
# 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
|
# 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
|
# 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
|
# by me: 2022 n9k <https://gitler.moe/ninya9k>, AGPL 3.0 or any later
|
||||||
# version.
|
# version.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import anonstream.routes.error
|
import anonstream.routes.error
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from quart import current_app, request, render_template, abort, make_response, redirect, url_for, send_from_directory
|
from quart import current_app, request, render_template, abort, make_response, redirect, url_for, send_from_directory
|
||||||
from werkzeug.exceptions import Forbidden, NotFound, TooManyRequests
|
from werkzeug.exceptions import Forbidden, NotFound, TooManyRequests
|
||||||
|
@ -22,7 +23,12 @@ CAPTCHA_SIGNER = current_app.captcha_signer
|
||||||
STATIC_DIRECTORY = current_app.root_path / 'static'
|
STATIC_DIRECTORY = current_app.root_path / 'static'
|
||||||
|
|
||||||
@current_app.route('/')
|
@current_app.route('/')
|
||||||
@with_user_from(request, fallback_to_token=True, ignore_allowedness=True)
|
@with_user_from(
|
||||||
|
request,
|
||||||
|
fallback_to_token=True,
|
||||||
|
ignore_allowedness=True,
|
||||||
|
redundant_token_redirect=True,
|
||||||
|
)
|
||||||
async def home(timestamp, user_or_token):
|
async def home(timestamp, user_or_token):
|
||||||
match user_or_token:
|
match user_or_token:
|
||||||
case str() | None as token:
|
case str() | None as token:
|
||||||
|
@ -128,12 +134,13 @@ async def access(timestamp, user_or_token):
|
||||||
failure_id = None
|
failure_id = None
|
||||||
user = generate_and_add_user(timestamp, token, verified=True)
|
user = generate_and_add_user(timestamp, token, verified=True)
|
||||||
if failure_id is not None:
|
if failure_id is not None:
|
||||||
url = url_for('home', token=token, failure=failure_id)
|
response = redirect(url_for('home', token=token, failure=failure_id), 303)
|
||||||
raise abort(redirect(url, 303))
|
else:
|
||||||
|
response = redirect(url_for('home', token=user['token']), 303)
|
||||||
|
response.headers['Set-Cookie'] = f'token={quote(user["token"])}; path=/'
|
||||||
case dict() as user:
|
case dict() as user:
|
||||||
pass
|
response = redirect(url_for('home', token=user['token']), 303)
|
||||||
url = url_for('home', token=user['token'])
|
return response
|
||||||
return redirect(url, 303)
|
|
||||||
|
|
||||||
@current_app.route('/static/<path:filename>')
|
@current_app.route('/static/<path:filename>')
|
||||||
@with_user_from(request)
|
@with_user_from(request)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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
|
||||||
|
@ -10,11 +10,12 @@ from anonstream.user import add_state, pop_state, try_change_appearance, update_
|
||||||
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
||||||
from anonstream.helpers.chat import get_scrollback
|
from anonstream.helpers.chat import get_scrollback
|
||||||
from anonstream.helpers.user import get_default_name
|
from anonstream.helpers.user import get_default_name
|
||||||
from anonstream.utils.chat import generate_nonce
|
from anonstream.utils.chat import generate_nonce, should_show_initial_date
|
||||||
from anonstream.utils.security import generate_csp
|
from anonstream.utils.security import generate_csp
|
||||||
from anonstream.utils.user import concatenate_for_notice
|
from anonstream.utils.user import concatenate_for_notice
|
||||||
|
|
||||||
CONFIG = current_app.config
|
CONFIG = current_app.config
|
||||||
|
MESSAGES = current_app.messages
|
||||||
USERS_BY_TOKEN = current_app.users_by_token
|
USERS_BY_TOKEN = current_app.users_by_token
|
||||||
|
|
||||||
@current_app.route('/stream.html')
|
@current_app.route('/stream.html')
|
||||||
|
@ -47,15 +48,17 @@ async def nojs_info(timestamp, user):
|
||||||
@with_user_from(request)
|
@with_user_from(request)
|
||||||
async def nojs_chat_messages(timestamp, user):
|
async def nojs_chat_messages(timestamp, user):
|
||||||
reading(user)
|
reading(user)
|
||||||
|
messages = get_scrollback(MESSAGES)
|
||||||
return await render_template_with_etag(
|
return await render_template_with_etag(
|
||||||
'nojs_chat_messages.html',
|
'nojs_chat_messages.html',
|
||||||
{'csp': generate_csp()},
|
{'csp': generate_csp()},
|
||||||
refresh=CONFIG['NOJS_REFRESH_MESSAGES'],
|
refresh=CONFIG['NOJS_REFRESH_MESSAGES'],
|
||||||
user=user,
|
user=user,
|
||||||
users_by_token=USERS_BY_TOKEN,
|
users_by_token=USERS_BY_TOKEN,
|
||||||
messages=get_scrollback(current_app.messages),
|
messages=messages,
|
||||||
timeout=CONFIG['NOJS_TIMEOUT_CHAT'],
|
timeout=CONFIG['NOJS_TIMEOUT_CHAT'],
|
||||||
get_default_name=get_default_name,
|
get_default_name=get_default_name,
|
||||||
|
show_initial_date=should_show_initial_date(timestamp, messages),
|
||||||
)
|
)
|
||||||
|
|
||||||
@current_app.route('/chat/messages')
|
@current_app.route('/chat/messages')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
@ -8,7 +8,7 @@ import string
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from urllib.parse import quote, unquote
|
from urllib.parse import quote, unquote
|
||||||
|
|
||||||
from quart import current_app, request, make_response, render_template, request, url_for, Markup
|
from quart import current_app, request, make_response, render_template, redirect, url_for, Markup
|
||||||
from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden
|
from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
|
@ -87,7 +87,12 @@ def generate_and_add_user(
|
||||||
USERS_UPDATE_BUFFER.add(token)
|
USERS_UPDATE_BUFFER.add(token)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
def with_user_from(
|
||||||
|
context,
|
||||||
|
fallback_to_token=False,
|
||||||
|
ignore_allowedness=False,
|
||||||
|
redundant_token_redirect=False,
|
||||||
|
):
|
||||||
def with_user_from_context(f):
|
def with_user_from_context(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
async def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
|
@ -129,6 +134,18 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
||||||
f"terminal when they started anonstream."
|
f"terminal when they started anonstream."
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# If token from the client's cookie is same as the token in the URL
|
||||||
|
# query string, the client supports cookies. If we want, we can
|
||||||
|
# redirect the client to this same URL path but with the token
|
||||||
|
# parameter removed, since we'll pick up their token from their
|
||||||
|
# cookie anyway.
|
||||||
|
if (
|
||||||
|
redundant_token_redirect
|
||||||
|
and token_from_context is not None
|
||||||
|
and token_from_args == token_from_cookie
|
||||||
|
):
|
||||||
|
return redirect(context.path, 303)
|
||||||
|
|
||||||
# Create response
|
# Create response
|
||||||
user = USERS_BY_TOKEN.get(token)
|
user = USERS_BY_TOKEN.get(token)
|
||||||
if CONFIG['ACCESS_CAPTCHA'] and not broadcaster:
|
if CONFIG['ACCESS_CAPTCHA'] and not broadcaster:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
|
@ -189,6 +189,7 @@ const create_chat_message = (object) => {
|
||||||
chat_message.classList.add("chat-message");
|
chat_message.classList.add("chat-message");
|
||||||
chat_message.dataset.seq = object.seq;
|
chat_message.dataset.seq = object.seq;
|
||||||
chat_message.dataset.tokenHash = object.token_hash;
|
chat_message.dataset.tokenHash = object.token_hash;
|
||||||
|
chat_message.dataset.date = object.date;
|
||||||
|
|
||||||
const chat_message_time = document.createElement("time");
|
const chat_message_time = document.createElement("time");
|
||||||
chat_message_time.classList.add("chat-message__time");
|
chat_message_time.classList.add("chat-message__time");
|
||||||
|
@ -256,23 +257,65 @@ const create_chat_user_components = (user) => {
|
||||||
result.push(...[chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode]);
|
result.push(...[chat_user_name, chat_user_tripcode_nbsp, chat_user_tripcode]);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
const zeropad = (n) => ("0" + n).slice(-2);
|
||||||
|
const datestamp = () => {
|
||||||
|
const date = new Date();
|
||||||
|
return `${date.getUTCFullYear()}-${zeropad(date.getUTCMonth() + 1)}-${zeropad(date.getUTCDate())}`;
|
||||||
|
}
|
||||||
const create_and_add_chat_message = (object) => {
|
const create_and_add_chat_message = (object) => {
|
||||||
|
// date
|
||||||
|
last_chat_message = chat_messages.querySelector(".chat-message:last-of-type");
|
||||||
|
if (last_chat_message === null || last_chat_message.dataset.date !== object.date) {
|
||||||
|
const chat_date = document.createElement("li");
|
||||||
|
chat_date.classList.add("chat-date");
|
||||||
|
chat_date.dataset.date = object.date;
|
||||||
|
|
||||||
|
const chat_date_hr = document.createElement("hr");
|
||||||
|
const chat_date_div = document.createElement("div");
|
||||||
|
|
||||||
|
const chat_date_div_time = document.createElement("time");
|
||||||
|
chat_date_div_time.datetime = object.date;
|
||||||
|
chat_date_div_time.innerText = object.date;
|
||||||
|
|
||||||
|
chat_date_div.insertAdjacentElement("beforeend", chat_date_div_time);
|
||||||
|
chat_date.insertAdjacentElement("beforeend", chat_date_hr);
|
||||||
|
chat_date.insertAdjacentElement("beforeend", chat_date_div);
|
||||||
|
if (last_chat_message === null && object.date === datestamp())
|
||||||
|
chat_date.dataset.hidden = "";
|
||||||
|
chat_messages.insertAdjacentElement("beforeend", chat_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
// message
|
||||||
const chat_message = create_chat_message(object);
|
const chat_message = create_chat_message(object);
|
||||||
chat_messages.insertAdjacentElement("beforeend", chat_message);
|
chat_messages.insertAdjacentElement("beforeend", chat_message);
|
||||||
while (chat_messages.children.length > max_chat_scrollback) {
|
const first_chat_message = chat_messages.querySelector(".chat-message");
|
||||||
chat_messages.children[0].remove();
|
if (first_chat_message !== null) {
|
||||||
|
const first_chat_date = chat_messages.querySelector(".chat-date");
|
||||||
|
if (first_chat_date !== null && first_chat_date.hasAttribute("data-hidden") && (object.date !== first_chat_message.dataset.date || object.date !== datestamp()))
|
||||||
|
first_chat_date.removeAttribute("data-hidden");
|
||||||
}
|
}
|
||||||
|
const string_seqs = new Set();
|
||||||
|
for (const this_chat_message of chat_messages.querySelectorAll(".chat-message")) {
|
||||||
|
if (chat_messages.querySelectorAll(".chat-message").length - string_seqs.size > max_chat_scrollback)
|
||||||
|
string_seqs.add(this_chat_message.dataset.seq);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delete_chat_messages({string_seqs});
|
||||||
}
|
}
|
||||||
const delete_chat_messages = (seqs) => {
|
const delete_chat_messages = ({string_seqs, keep=false}) => {
|
||||||
string_seqs = new Set(seqs.map(n => n.toString()));
|
const keep_dates = new Set();
|
||||||
to_delete = [];
|
for (const chat_message of chat_messages.querySelectorAll(".chat-message")) {
|
||||||
for (const chat_message of chat_messages.children) {
|
if (string_seqs.has(chat_message.dataset.seq) === keep)
|
||||||
if (string_seqs.has(chat_message.dataset.seq))
|
keep_dates.add(chat_message.dataset.date);
|
||||||
to_delete.push(chat_message);
|
|
||||||
}
|
}
|
||||||
for (const chat_message of to_delete) {
|
const to_delete = [];
|
||||||
chat_message.remove();
|
for (const child of chat_messages.children) {
|
||||||
|
if (child.classList.contains("chat-date") && !keep_dates.has(child.dataset.date) || child.classList.contains("chat-message") && string_seqs.has(child.dataset.seq) !== keep)
|
||||||
|
to_delete.push(child);
|
||||||
}
|
}
|
||||||
|
for (const element of to_delete)
|
||||||
|
element.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
let users = {};
|
let users = {};
|
||||||
|
@ -340,7 +383,7 @@ const get_user_name = ({user=null, token_hash}) => {
|
||||||
}
|
}
|
||||||
const update_user_names = (token_hash=null) => {
|
const update_user_names = (token_hash=null) => {
|
||||||
const token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
const token_hashes = token_hash === null ? Object.keys(users) : [token_hash];
|
||||||
for (const chat_message of chat_messages.children) {
|
for (const chat_message of chat_messages.querySelectorAll(".chat-message")) {
|
||||||
const this_token_hash = chat_message.dataset.tokenHash;
|
const this_token_hash = chat_message.dataset.tokenHash;
|
||||||
if (token_hashes.includes(this_token_hash)) {
|
if (token_hashes.includes(this_token_hash)) {
|
||||||
const user = users[this_token_hash];
|
const user = users[this_token_hash];
|
||||||
|
@ -422,7 +465,7 @@ const update_user_tripcodes = (token_hash=null) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update inner texts
|
// update inner texts
|
||||||
for (const chat_message of chat_messages.children) {
|
for (const chat_message of chat_messages.querySelectorAll(".chat-message")) {
|
||||||
const this_token_hash = chat_message.dataset.tokenHash;
|
const this_token_hash = chat_message.dataset.tokenHash;
|
||||||
const tripcode = users[this_token_hash].tripcode;
|
const tripcode = users[this_token_hash].tripcode;
|
||||||
if (token_hashes.includes(this_token_hash)) {
|
if (token_hashes.includes(this_token_hash)) {
|
||||||
|
@ -622,6 +665,11 @@ const on_websocket_message = async (event) => {
|
||||||
info_button.dataset.visible = "";
|
info_button.dataset.visible = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// form input maxlengths
|
||||||
|
chat_form_comment.maxLength = receipt.maxlength.comment;
|
||||||
|
chat_appearance_form_name.maxLength = receipt.maxlength.name;
|
||||||
|
chat_appearance_form_password.maxLength = receipt.maxlength.password;
|
||||||
|
|
||||||
// chat form nonce
|
// chat form nonce
|
||||||
chat_form_nonce.value = receipt.nonce;
|
chat_form_nonce.value = receipt.nonce;
|
||||||
|
|
||||||
|
@ -632,17 +680,8 @@ const on_websocket_message = async (event) => {
|
||||||
chat_form_submit.disabled = false;
|
chat_form_submit.disabled = false;
|
||||||
|
|
||||||
// remove messages the server isn't acknowledging the existence of
|
// remove messages the server isn't acknowledging the existence of
|
||||||
const seqs = new Set(receipt.messages.map((message) => {return message.seq;}));
|
const string_seqs = new Set(receipt.messages.map(message => message.seq.toString()));
|
||||||
const to_delete = [];
|
delete_chat_messages({string_seqs, keep: true});
|
||||||
for (const chat_message of chat_messages.children) {
|
|
||||||
const chat_message_seq = parseInt(chat_message.dataset.seq);
|
|
||||||
if (!seqs.has(chat_message_seq)) {
|
|
||||||
to_delete.push(chat_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const chat_message of to_delete) {
|
|
||||||
chat_message.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
default_name = receipt.default;
|
default_name = receipt.default;
|
||||||
|
@ -665,7 +704,7 @@ const on_websocket_message = async (event) => {
|
||||||
left: 0,
|
left: 0,
|
||||||
top: chat_messages.scrollTopMax,
|
top: chat_messages.scrollTopMax,
|
||||||
behavior: "instant",
|
behavior: "instant",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// appearance form default values
|
// appearance form default values
|
||||||
|
@ -677,7 +716,8 @@ const on_websocket_message = async (event) => {
|
||||||
chat_appearance_form_color.setAttribute("value", user.color);
|
chat_appearance_form_color.setAttribute("value", user.color);
|
||||||
|
|
||||||
// insert new messages
|
// insert new messages
|
||||||
const last = chat_messages.children.length == 0 ? null : chat_messages.children[chat_messages.children.length - 1];
|
const chat_messages_messages = chat_messages.querySelectorAll(".chat-message");
|
||||||
|
const last = chat_messages_messages.length == 0 ? null : chat_messages_messages[chat_messages_messages.length - 1];
|
||||||
const last_seq = last === null ? null : parseInt(last.dataset.seq);
|
const last_seq = last === null ? null : parseInt(last.dataset.seq);
|
||||||
for (const message of receipt.messages) {
|
for (const message of receipt.messages) {
|
||||||
if (message.seq > last_seq) {
|
if (message.seq > last_seq) {
|
||||||
|
@ -744,7 +784,7 @@ const on_websocket_message = async (event) => {
|
||||||
|
|
||||||
case "delete":
|
case "delete":
|
||||||
console.log("ws delete", receipt);
|
console.log("ws delete", receipt);
|
||||||
delete_chat_messages(receipt.seqs);
|
delete_chat_messages({string_seqs: new Set(receipt.seqs.map(n => n.toString()))});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "set-users":
|
case "set-users":
|
||||||
|
@ -901,7 +941,7 @@ info_button.addEventListener("click", (event) => {
|
||||||
info_button.removeAttribute("data-visible");
|
info_button.removeAttribute("data-visible");
|
||||||
});
|
});
|
||||||
video.addEventListener("error", (event) => {
|
video.addEventListener("error", (event) => {
|
||||||
if (video.error !== null && video.error.message === "404: Not Found") {
|
if (video.error !== null && video.networkState === video.NETWORK_NO_SOURCE) {
|
||||||
show_offline_screen();
|
show_offline_screen();
|
||||||
}
|
}
|
||||||
if (stats !== null) {
|
if (stats !== null) {
|
||||||
|
@ -969,6 +1009,14 @@ chat_messages_unlock.addEventListener("click", (event) => {
|
||||||
chat_messages.scrollTop = chat_messages.scrollTopMax;
|
chat_messages.scrollTop = chat_messages.scrollTopMax;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* show initial chat date if a day has passed */
|
||||||
|
const show_initial_date = () => {
|
||||||
|
const chat_date = chat_messages.querySelector(".chat-date:first-child");
|
||||||
|
if (chat_date !== null && chat_date.hasAttribute("data-hidden") && chat_date.dataset.date !== datestamp())
|
||||||
|
chat_date.removeAttribute("data-hidden");
|
||||||
|
}
|
||||||
|
setInterval(show_initial_date, 30000);
|
||||||
|
|
||||||
/* close websocket after prolonged absence of pings */
|
/* close websocket after prolonged absence of pings */
|
||||||
|
|
||||||
const rotate_websocket = () => {
|
const rotate_websocket = () => {
|
||||||
|
|
|
@ -273,6 +273,29 @@ noscript {
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
.chat-date {
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #b2b2b3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.chat-date[data-hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.chat-date > hr {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.chat-date > :not(hr) > time {
|
||||||
|
padding: 0 1ch;
|
||||||
|
background-color: #232327;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
#chat__body__users {
|
#chat__body__users {
|
||||||
background-color: #121214;
|
background-color: #121214;
|
||||||
mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem));
|
mask-image: linear-gradient(black calc(100% - 0.625rem), transparent calc(100% - 0.125rem));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: calc(50% - 4rem) 1fr;
|
grid-template-rows: calc(50% - 10vh + 2rem) 1fr;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
@ -37,20 +37,23 @@
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
padding: 4px 5px;
|
padding: 5px 6px;
|
||||||
width: 10ch;
|
width: 10ch;
|
||||||
}
|
|
||||||
input[name="answer"]:hover {
|
|
||||||
background-color: #37373a;
|
|
||||||
transition: 0.25s;
|
transition: 0.25s;
|
||||||
}
|
}
|
||||||
input[type="submit"] {
|
input[name="answer"]:focus {
|
||||||
|
background-color: black;
|
||||||
|
border-color: #3584e4;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
padding-left: 8px;
|
||||||
p {
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<a href="#chat">chat</a>
|
<a href="#chat">chat</a>
|
||||||
<a href="#both">both</a>
|
<a href="#both">both</a>
|
||||||
</nav>
|
</nav>
|
||||||
<footer>anonstream {{ version }} — <a href="https://git.076.ne.jp/ninya9k/anonstream" target="_blank">source</a></footer>
|
<footer>anonstream {{ version }} — <a href="https://gitler.moe/ninya9k/anonstream" target="_blank">source</a></footer>
|
||||||
<script src="{{ url_for('static', filename='anonstream.js') }}" type="text/javascript"></script>
|
<script src="{{ url_for('static', filename='anonstream.js') }}" type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -144,6 +144,27 @@
|
||||||
font-size: 9pt;
|
font-size: 9pt;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
.chat-date {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #b2b2b3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.chat-date > hr {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.chat-date > :not(hr) > time {
|
||||||
|
padding: 0 1ch;
|
||||||
|
background-color: #232327;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
{% for token in messages | map(attribute='token') | list | unique %}
|
{% for token in messages | map(attribute='token') | list | unique %}
|
||||||
{% with this_user = users_by_token[token] %}
|
{% with this_user = users_by_token[token] %}
|
||||||
|
@ -172,7 +193,7 @@
|
||||||
<ol id="chat-messages">
|
<ol id="chat-messages">
|
||||||
{% for message in messages | reverse %}
|
{% for message in messages | reverse %}
|
||||||
{% with this_user = users_by_token[message.token] %}
|
{% with this_user = users_by_token[message.token] %}
|
||||||
<li class="chat-message" data-seq="{{ message.seq }}" data-token-hash="{{ this_user.token_hash }}">
|
<li class="chat-message" data-seq="{{ message.seq }}" data-token-hash="{{ this_user.token_hash }}" data-date="{{ message.date }}">
|
||||||
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
<time class="chat-message__time" datetime="{{ message.date }}T{{ message.time_seconds }}Z" title="{{ message.date }} {{ message.time_seconds }}">{{ message.time_minutes }}</time>
|
||||||
{{- ' ' | safe -}}
|
{{- ' ' | safe -}}
|
||||||
{{ appearance(this_user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
{{ appearance(this_user, insignia_class='chat-message__insignia', name_class='chat-message__name', tag_class='chat-message__name__tag') }}
|
||||||
|
@ -180,6 +201,15 @@
|
||||||
<span class="chat-message__markup">{{ message.markup }}</span>
|
<span class="chat-message__markup">{{ message.markup }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
{%
|
||||||
|
if loop.nextitem is defined and loop.nextitem.date != message.date
|
||||||
|
or loop.nextitem is not defined and show_initial_date
|
||||||
|
%}
|
||||||
|
<li class="chat-date" data-date="{{ message.date }}">
|
||||||
|
<hr>
|
||||||
|
<div><time datetime="{{ message.date }}">{{ message.date }}</time></div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
<aside id="timeout-dismiss">
|
<aside id="timeout-dismiss">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import operator
|
import operator
|
||||||
|
@ -8,6 +8,7 @@ from math import inf
|
||||||
|
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
|
|
||||||
|
from anonstream.events import notify_event_sockets
|
||||||
from anonstream.wrappers import try_except_log, with_timestamp, get_timestamp
|
from anonstream.wrappers import try_except_log, with_timestamp, get_timestamp
|
||||||
from anonstream.helpers.user import get_default_name, get_presence, Presence
|
from anonstream.helpers.user import get_default_name, get_presence, Presence
|
||||||
from anonstream.helpers.captcha import check_captcha_digest, Answer
|
from anonstream.helpers.captcha import check_captcha_digest, Answer
|
||||||
|
@ -98,6 +99,21 @@ def try_change_appearance(user, name, color, password, want_tripcode):
|
||||||
# Add to the users update buffer
|
# Add to the users update buffer
|
||||||
USERS_UPDATE_BUFFER.add(user['token'])
|
USERS_UPDATE_BUFFER.add(user['token'])
|
||||||
|
|
||||||
|
# Notify event sockets that a user's appearance was set
|
||||||
|
# NOTE: Changing appearance is currently NOT ratelimited.
|
||||||
|
# Applications using the event socket API should buffer these
|
||||||
|
# events or do something else to a prevent a potential denial of
|
||||||
|
# service.
|
||||||
|
notify_event_sockets({
|
||||||
|
'type': 'appearance',
|
||||||
|
'event': {
|
||||||
|
'token': user['token'],
|
||||||
|
'name': user['name'],
|
||||||
|
'color': user['color'],
|
||||||
|
'tripcode': user['tripcode'],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def change_name(user, name, dry_run=False):
|
def change_name(user, name, dry_run=False):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
@ -6,6 +6,7 @@ import hashlib
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
from datetime import datetime
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from quart import escape
|
from quart import escape
|
||||||
|
@ -30,3 +31,15 @@ def get_approx_linespan(text):
|
||||||
linespan = sum(map(height, text.splitlines()))
|
linespan = sum(map(height, text.splitlines()))
|
||||||
linespan = linespan if linespan > 0 else 1
|
linespan = linespan if linespan > 0 else 1
|
||||||
return linespan
|
return linespan
|
||||||
|
|
||||||
|
def should_show_initial_date(timestamp, messages):
|
||||||
|
try:
|
||||||
|
first_message = next(iter(messages))
|
||||||
|
except StopIteration:
|
||||||
|
return False
|
||||||
|
if any(message['date'] != first_message['date'] for message in messages):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
latest_date = max(map(lambda message: message['date'], messages))
|
||||||
|
date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
|
||||||
|
return date != latest_date
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/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://gitler.moe/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://gitler.moe/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://gitler.moe/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://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -36,6 +36,11 @@ async def websocket_outbound(queue, user):
|
||||||
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
||||||
'digest': get_random_captcha_digest_for(user),
|
'digest': get_random_captcha_digest_for(user),
|
||||||
'pingpong': CONFIG['TASK_BROADCAST_PING'],
|
'pingpong': CONFIG['TASK_BROADCAST_PING'],
|
||||||
|
'maxlength': {
|
||||||
|
'comment': CONFIG['CHAT_COMMENT_MAX_LENGTH'],
|
||||||
|
'name': CONFIG['CHAT_NAME_MAX_LENGTH'],
|
||||||
|
'password': CONFIG['CHAT_TRIPCODE_PASSWORD_MAX_LENGTH'],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
while True:
|
while True:
|
||||||
payload = await queue.get()
|
payload = await queue.get()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
2
asgi.py
2
asgi.py
|
@ -1,4 +1,4 @@
|
||||||
# SPDX-FileCopyrightText: 2022 n9k <https://git.076.ne.jp/ninya9k>
|
# SPDX-FileCopyrightText: 2022 n9k <https://gitler.moe/ninya9k>
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -91,4 +91,4 @@ stream/stream.m3u8
|
||||||
```
|
```
|
||||||
|
|
||||||
[hwaccel]: https://trac.ffmpeg.org/wiki/HWAccelIntro
|
[hwaccel]: https://trac.ffmpeg.org/wiki/HWAccelIntro
|
||||||
[plaintext]: https://git.076.ne.jp/ninya9k/anonstream/raw/branch/master/doc/guide/OBS.md
|
[plaintext]: https://gitler.moe/ninya9k/anonstream/raw/branch/master/doc/guide/OBS.md
|
||||||
|
|
|
@ -128,6 +128,6 @@ systemd you can alternatively do `# systemctl reload tor`. If
|
||||||
everything went well, the directory will have been created and your
|
everything went well, the directory will have been created and your
|
||||||
onion address will be in `$HIDDEN_SERVICE_DIR/hostname`.
|
onion address will be in `$HIDDEN_SERVICE_DIR/hostname`.
|
||||||
|
|
||||||
[readme]: https://git.076.ne.jp/ninya9k/anonstream/src/branch/master/README.md#setup
|
[readme]: https://gitler.moe/ninya9k/anonstream/src/branch/master/README.md#setup
|
||||||
[tor]: https://gitlab.torproject.org/tpo/core/tor
|
[tor]: https://gitlab.torproject.org/tpo/core/tor
|
||||||
[torrc]: https://support.torproject.org/#tbb-editing-torrc
|
[torrc]: https://support.torproject.org/#tbb-editing-torrc
|
||||||
|
|
読み込み中…
新しいイシューから参照