mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2025-04-20 17:46:43 +00:00
Compare commits
No commits in common. "dec378fbfc823e0170582ffa6ae9410ab4955118" and "2e373eddc94f09ef26070a231e21fd32fa035800" have entirely different histories.
dec378fbfc
...
2e373eddc9
21 changed files with 1071 additions and 1143 deletions
|
@ -27,7 +27,7 @@ dependencies = [
|
||||||
"aiohttp >= 3.9.5",
|
"aiohttp >= 3.9.5",
|
||||||
"argon2-cffi == 23.1.0",
|
"argon2-cffi == 23.1.0",
|
||||||
"barkshark-lib >= 0.2.3, < 0.3.0",
|
"barkshark-lib >= 0.2.3, < 0.3.0",
|
||||||
"barkshark-sql >= 0.2.5, < 0.3.0",
|
"barkshark-sql >= 0.2.0, < 0.3.0",
|
||||||
"click == 8.1.2",
|
"click == 8.1.2",
|
||||||
"docstring-parser == 0.16",
|
"docstring-parser == 0.16",
|
||||||
"hamlish == 0.4.0",
|
"hamlish == 0.4.0",
|
||||||
|
@ -63,7 +63,6 @@ dev = [
|
||||||
"flake8 == 7.1.1",
|
"flake8 == 7.1.1",
|
||||||
"mypy == 1.13.0",
|
"mypy == 1.13.0",
|
||||||
"pyinstaller == 6.10.0",
|
"pyinstaller == 6.10.0",
|
||||||
"typing-extensions == 4.12.2; python_version < '3.11'",
|
|
||||||
]
|
]
|
||||||
docs = [
|
docs = [
|
||||||
"furo == 2024.1.29",
|
"furo == 2024.1.29",
|
||||||
|
@ -73,6 +72,11 @@ docs = [
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
zip-safe = false
|
zip-safe = false
|
||||||
|
packages = [
|
||||||
|
"relay",
|
||||||
|
"relay.database",
|
||||||
|
"relay.views",
|
||||||
|
]
|
||||||
include-package-data = true
|
include-package-data = true
|
||||||
license-files = [
|
license-files = [
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
|
@ -90,9 +94,6 @@ relay = [
|
||||||
[tool.setuptools.dynamic.version]
|
[tool.setuptools.dynamic.version]
|
||||||
attr = "relay.__version__"
|
attr = "relay.__version__"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
|
||||||
include = ["relay*"]
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
show_traceback = true
|
show_traceback = true
|
||||||
install_types = true
|
install_types = true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from relay.cli import main
|
from relay.manage import main
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -18,7 +18,7 @@ from datetime import datetime, timedelta
|
||||||
from mimetypes import guess_type
|
from mimetypes import guess_type
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from . import logger as logging
|
from . import logger as logging
|
||||||
from .cache import Cache, get_cache
|
from .cache import Cache, get_cache
|
||||||
|
@ -30,13 +30,6 @@ from .misc import JSON_PATHS, TOKEN_PATHS, Message, Response
|
||||||
from .template import Template
|
from .template import Template
|
||||||
from .workers import PushWorkers
|
from .workers import PushWorkers
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
try:
|
|
||||||
from typing import Self
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
def get_csp(request: web.Request) -> str:
|
def get_csp(request: web.Request) -> str:
|
||||||
data = [
|
data = [
|
||||||
|
@ -48,7 +41,7 @@ def get_csp(request: web.Request) -> str:
|
||||||
"img-src 'self'",
|
"img-src 'self'",
|
||||||
"object-src 'none'",
|
"object-src 'none'",
|
||||||
"frame-ancestors 'none'",
|
"frame-ancestors 'none'",
|
||||||
f"manifest-src 'self' https://{request.app['config'].domain}"
|
f"manifest-src 'self' https://{request.app["config"].domain}"
|
||||||
]
|
]
|
||||||
|
|
||||||
return "; ".join(data) + ";"
|
return "; ".join(data) + ";"
|
||||||
|
@ -58,14 +51,6 @@ class Application(web.Application):
|
||||||
DEFAULT: Application | None = None
|
DEFAULT: Application | None = None
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def default(cls: type[Self]) -> Application:
|
|
||||||
if cls.DEFAULT is None:
|
|
||||||
raise ValueError("Default application not set")
|
|
||||||
|
|
||||||
return cls.DEFAULT
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, cfgpath: Path | None, dev: bool = False):
|
def __init__(self, cfgpath: Path | None, dev: bool = False):
|
||||||
web.Application.__init__(self,
|
web.Application.__init__(self,
|
||||||
middlewares = [
|
middlewares = [
|
||||||
|
@ -284,7 +269,7 @@ class CacheCleanupThread(Thread):
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
while self.running.is_set():
|
while self.running.is_set():
|
||||||
self.running.wait(3600)
|
time.sleep(3600)
|
||||||
logging.verbose("Removing old cache items")
|
logging.verbose("Removing old cache items")
|
||||||
self.app.cache.delete_old(14)
|
self.app.cache.delete_old(14)
|
||||||
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import click
|
|
||||||
import json
|
|
||||||
import multiprocessing
|
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
from functools import update_wrapper
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Concatenate, ParamSpec, TypeVar
|
|
||||||
|
|
||||||
from .. import __version__
|
|
||||||
from ..application import Application
|
|
||||||
from ..misc import IS_DOCKER
|
|
||||||
|
|
||||||
|
|
||||||
P = ParamSpec("P")
|
|
||||||
R = TypeVar("R")
|
|
||||||
|
|
||||||
|
|
||||||
@click.group("cli", context_settings = {"show_default": True})
|
|
||||||
@click.option("--config", "-c", type = Path, help = "path to the relay config")
|
|
||||||
@click.version_option(version = __version__, prog_name = "ActivityRelay")
|
|
||||||
@click.pass_context
|
|
||||||
def cli(ctx: click.Context, config: Path | None) -> None:
|
|
||||||
if IS_DOCKER:
|
|
||||||
config = Path("/data/relay.yaml")
|
|
||||||
|
|
||||||
# The database was named "relay.jsonld" even though it"s an sqlite file. Fix it.
|
|
||||||
db = Path("/data/relay.sqlite3")
|
|
||||||
wrongdb = Path("/data/relay.jsonld")
|
|
||||||
|
|
||||||
if wrongdb.exists() and not db.exists():
|
|
||||||
try:
|
|
||||||
with wrongdb.open("rb") as fd:
|
|
||||||
json.load(fd)
|
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
wrongdb.rename(db)
|
|
||||||
|
|
||||||
ctx.obj = Application(config)
|
|
||||||
|
|
||||||
|
|
||||||
def pass_app(func: Callable[Concatenate[Application, P], R]) -> Callable[P, R]:
|
|
||||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
||||||
return func(Application.default(), *args, **kwargs)
|
|
||||||
|
|
||||||
return update_wrapper(wrapper, func)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
multiprocessing.freeze_support()
|
|
||||||
cli(prog_name="activityrelay")
|
|
||||||
|
|
||||||
|
|
||||||
from . import ( # noqa: E402
|
|
||||||
base,
|
|
||||||
config as config_cli,
|
|
||||||
inbox,
|
|
||||||
instance_ban,
|
|
||||||
request,
|
|
||||||
software_ban,
|
|
||||||
user,
|
|
||||||
whitelist
|
|
||||||
)
|
|
|
@ -1,347 +0,0 @@
|
||||||
import aputils
|
|
||||||
import click
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from shutil import copyfile
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from .. import logger as logging
|
|
||||||
from ..application import Application
|
|
||||||
from ..compat import RelayConfig, RelayDatabase
|
|
||||||
from ..config import Config
|
|
||||||
from ..database import TABLES, get_database
|
|
||||||
from ..misc import IS_DOCKER, RELAY_SOFTWARE
|
|
||||||
from ..views import ROUTES
|
|
||||||
|
|
||||||
|
|
||||||
def check_alphanumeric(text: str) -> str:
|
|
||||||
if not text.isalnum():
|
|
||||||
raise click.BadParameter("String not alphanumeric")
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("convert")
|
|
||||||
@click.option("--old-config", "-o", help = "Path to the config file to convert from")
|
|
||||||
@pass_app
|
|
||||||
def cli_convert(app: Application, old_config: str) -> None:
|
|
||||||
"Convert an old config and jsonld database to the new format."
|
|
||||||
|
|
||||||
old_config = str(Path(old_config).expanduser().resolve()) if old_config else str(app.config.path)
|
|
||||||
backup = app.config.path.parent.joinpath(f"{app.config.path.stem}.backup.yaml")
|
|
||||||
|
|
||||||
if str(old_config) == str(app.config.path) and not backup.exists():
|
|
||||||
logging.info("Created backup config @ %s", backup)
|
|
||||||
copyfile(app.config.path, backup)
|
|
||||||
|
|
||||||
config = RelayConfig(old_config)
|
|
||||||
config.load()
|
|
||||||
|
|
||||||
database = RelayDatabase(config)
|
|
||||||
database.load()
|
|
||||||
|
|
||||||
app.config.set("listen", config["listen"])
|
|
||||||
app.config.set("port", config["port"])
|
|
||||||
app.config.set("workers", config["workers"])
|
|
||||||
app.config.set("sq_path", config["db"].replace("jsonld", "sqlite3"))
|
|
||||||
app.config.set("domain", config["host"])
|
|
||||||
app.config.save()
|
|
||||||
|
|
||||||
with get_database(app.config) as db:
|
|
||||||
with db.session(True) as conn:
|
|
||||||
conn.put_config("private-key", database["private-key"])
|
|
||||||
conn.put_config("note", config["note"])
|
|
||||||
conn.put_config("whitelist-enabled", config.get("whitelist-enabled", False))
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
database["relay-list"].values(),
|
|
||||||
label = "Inboxes".ljust(15),
|
|
||||||
width = 0
|
|
||||||
) as inboxes:
|
|
||||||
for inbox in inboxes:
|
|
||||||
match inbox["software"]:
|
|
||||||
case "akkoma" | "pleroma":
|
|
||||||
inbox["actor"] = f"https://{inbox['domain']}/relay"
|
|
||||||
|
|
||||||
case "mastodon":
|
|
||||||
inbox["actor"] = f"https://{inbox['domain']}/actor"
|
|
||||||
|
|
||||||
case _:
|
|
||||||
inbox["actor"] = None
|
|
||||||
|
|
||||||
conn.put_inbox(
|
|
||||||
inbox["domain"],
|
|
||||||
inbox["inbox"],
|
|
||||||
actor = inbox["actor"],
|
|
||||||
followid = inbox["followid"],
|
|
||||||
software = inbox["software"]
|
|
||||||
)
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
config.get("blocked_software", []),
|
|
||||||
label = "Banned software".ljust(15),
|
|
||||||
width = 0
|
|
||||||
) as banned_software:
|
|
||||||
|
|
||||||
for software in banned_software:
|
|
||||||
conn.put_software_ban(
|
|
||||||
software,
|
|
||||||
reason = "relay" if software in RELAY_SOFTWARE else None
|
|
||||||
)
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
config.get("blocked_instances", []),
|
|
||||||
label = "Banned domains".ljust(15),
|
|
||||||
width = 0
|
|
||||||
) as banned_software:
|
|
||||||
|
|
||||||
for domain in banned_software:
|
|
||||||
conn.put_domain_ban(domain)
|
|
||||||
|
|
||||||
with click.progressbar(
|
|
||||||
config.get("whitelist", []),
|
|
||||||
label = "Whitelist".ljust(15),
|
|
||||||
width = 0
|
|
||||||
) as whitelist:
|
|
||||||
|
|
||||||
for instance in whitelist:
|
|
||||||
conn.put_domain_whitelist(instance)
|
|
||||||
|
|
||||||
click.echo("Finished converting old config and database :3")
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("db-maintenance")
|
|
||||||
@pass_app
|
|
||||||
def cli_db_maintenance(app: Application) -> None:
|
|
||||||
"Perform maintenance tasks on the database"
|
|
||||||
|
|
||||||
if app.config.db_type == "postgres":
|
|
||||||
return
|
|
||||||
|
|
||||||
with app.database.session(False) as s:
|
|
||||||
with s.transaction():
|
|
||||||
s.fix_timestamps()
|
|
||||||
|
|
||||||
with s.execute("VACUUM"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("edit-config")
|
|
||||||
@click.option("--editor", "-e", help = "Text editor to use")
|
|
||||||
@pass_app
|
|
||||||
def cli_editconfig(app: Application, editor: str) -> None:
|
|
||||||
"Edit the config file"
|
|
||||||
|
|
||||||
click.edit(
|
|
||||||
editor = editor,
|
|
||||||
filename = str(app.config.path)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("run")
|
|
||||||
@click.option("--dev", "-d", is_flag=True, help="Enable developer mode")
|
|
||||||
@pass_app
|
|
||||||
def cli_run(app: Application, dev: bool = False) -> None:
|
|
||||||
"Run the relay"
|
|
||||||
|
|
||||||
if app.config.domain.endswith("example.com") or app.signer is None:
|
|
||||||
if not IS_DOCKER:
|
|
||||||
click.echo("Relay is not set up. Please run \"activityrelay setup\"")
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
cli_setup.callback() # type: ignore
|
|
||||||
return
|
|
||||||
|
|
||||||
for method, path, handler in ROUTES:
|
|
||||||
app.router.add_route(method, path, handler)
|
|
||||||
|
|
||||||
app["dev"] = dev
|
|
||||||
app.run()
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("setup")
|
|
||||||
@click.option("--skip-questions", "-s", is_flag = True,
|
|
||||||
help = "Assume the config file is correct and just setup the database")
|
|
||||||
@pass_app
|
|
||||||
def cli_setup(app: Application, skip_questions: bool) -> None:
|
|
||||||
"Generate a new config and create the database"
|
|
||||||
|
|
||||||
if app.signer is not None:
|
|
||||||
if not click.prompt("The database is already setup. Are you sure you want to continue?"):
|
|
||||||
return
|
|
||||||
|
|
||||||
if skip_questions and app.config.domain.endswith("example.com"):
|
|
||||||
click.echo("You cannot skip the questions if the relay is not configured yet")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not skip_questions:
|
|
||||||
while True:
|
|
||||||
app.config.domain = click.prompt(
|
|
||||||
"What domain will the relay be hosted on?",
|
|
||||||
default = app.config.domain
|
|
||||||
)
|
|
||||||
|
|
||||||
if not app.config.domain.endswith("example.com"):
|
|
||||||
break
|
|
||||||
|
|
||||||
click.echo("The domain must not end with \"example.com\"")
|
|
||||||
|
|
||||||
if not IS_DOCKER:
|
|
||||||
app.config.listen = click.prompt(
|
|
||||||
"Which address should the relay listen on?",
|
|
||||||
default = app.config.listen
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.port = click.prompt(
|
|
||||||
"What TCP port should the relay listen on?",
|
|
||||||
default = app.config.port,
|
|
||||||
type = int
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.db_type = click.prompt(
|
|
||||||
"Which database backend will be used?",
|
|
||||||
default = app.config.db_type,
|
|
||||||
type = click.Choice(["postgres", "sqlite"], case_sensitive = False)
|
|
||||||
)
|
|
||||||
|
|
||||||
if app.config.db_type == "sqlite" and not IS_DOCKER:
|
|
||||||
app.config.sq_path = click.prompt(
|
|
||||||
"Where should the database be stored?",
|
|
||||||
default = app.config.sq_path
|
|
||||||
)
|
|
||||||
|
|
||||||
elif app.config.db_type == "postgres":
|
|
||||||
config_postgresql(app.config)
|
|
||||||
|
|
||||||
app.config.ca_type = click.prompt(
|
|
||||||
"Which caching backend?",
|
|
||||||
default = app.config.ca_type,
|
|
||||||
type = click.Choice(["database", "redis"], case_sensitive = False)
|
|
||||||
)
|
|
||||||
|
|
||||||
if app.config.ca_type == "redis":
|
|
||||||
app.config.rd_host = click.prompt(
|
|
||||||
"What IP address, hostname, or unix socket does the server listen on?",
|
|
||||||
default = app.config.rd_host
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.rd_port = click.prompt(
|
|
||||||
"What port does the server listen on?",
|
|
||||||
default = app.config.rd_port,
|
|
||||||
type = int
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.rd_user = click.prompt(
|
|
||||||
"Which user will authenticate with the server",
|
|
||||||
default = app.config.rd_user
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.rd_pass = click.prompt(
|
|
||||||
"User password",
|
|
||||||
hide_input = True,
|
|
||||||
show_default = False,
|
|
||||||
default = app.config.rd_pass or ""
|
|
||||||
) or None
|
|
||||||
|
|
||||||
app.config.rd_database = click.prompt(
|
|
||||||
"Which database number to use?",
|
|
||||||
default = app.config.rd_database,
|
|
||||||
type = int
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.rd_prefix = click.prompt(
|
|
||||||
"What text should each cache key be prefixed with?",
|
|
||||||
default = app.config.rd_database,
|
|
||||||
type = check_alphanumeric
|
|
||||||
)
|
|
||||||
|
|
||||||
app.config.save()
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"private-key": aputils.Signer.new("n/a").export()
|
|
||||||
}
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for key, value in config.items():
|
|
||||||
conn.put_config(key, value)
|
|
||||||
|
|
||||||
if IS_DOCKER:
|
|
||||||
click.echo("Relay all setup! Start the container to run the relay.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if click.confirm("Relay all setup! Would you like to run it now?"):
|
|
||||||
cli_run.callback() # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command("switch-backend")
|
|
||||||
@pass_app
|
|
||||||
def cli_switchbackend(app: Application) -> None:
|
|
||||||
"""
|
|
||||||
Copy the database from one backend to the other
|
|
||||||
|
|
||||||
Be sure to set the database type to the backend you want to convert from. For instance, set
|
|
||||||
the database type to `sqlite`, fill out the connection details for postgresql, and the
|
|
||||||
data from the sqlite database will be copied to the postgresql database. This only works if
|
|
||||||
the database in postgresql already exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
config = Config(app.config.path, load = True)
|
|
||||||
config.db_type = "sqlite" if config.db_type == "postgres" else "postgres"
|
|
||||||
|
|
||||||
if config.db_type == "postgres":
|
|
||||||
if click.confirm("Setup PostgreSQL configuration?"):
|
|
||||||
config_postgresql(config)
|
|
||||||
|
|
||||||
order = ("SQLite", "PostgreSQL")
|
|
||||||
click.pause("Make sure the database and user already exist before continuing")
|
|
||||||
|
|
||||||
else:
|
|
||||||
order = ("PostgreSQL", "SQLite")
|
|
||||||
|
|
||||||
click.echo(f"About to convert from {order[0]} to {order[1]}...")
|
|
||||||
database = get_database(config, migrate = False)
|
|
||||||
|
|
||||||
with database.session(True) as new, app.database.session(False) as old:
|
|
||||||
if click.confirm("All tables in the destination database will be dropped. Continue?"):
|
|
||||||
new.drop_tables()
|
|
||||||
|
|
||||||
new.create_tables()
|
|
||||||
|
|
||||||
for table in TABLES.keys():
|
|
||||||
for row in old.execute(f"SELECT * FROM {table}"):
|
|
||||||
new.insert(table, row).close()
|
|
||||||
|
|
||||||
config.save()
|
|
||||||
click.echo("Done!")
|
|
||||||
|
|
||||||
|
|
||||||
def config_postgresql(config: Config) -> None:
|
|
||||||
config.pg_name = click.prompt(
|
|
||||||
"What is the name of the database?",
|
|
||||||
default = config.pg_name
|
|
||||||
)
|
|
||||||
|
|
||||||
config.pg_host = click.prompt(
|
|
||||||
"What IP address, hostname, or unix socket does the server listen on?",
|
|
||||||
default = config.pg_host,
|
|
||||||
)
|
|
||||||
|
|
||||||
config.pg_port = click.prompt(
|
|
||||||
"What port does the server listen on?",
|
|
||||||
default = config.pg_port,
|
|
||||||
type = int
|
|
||||||
)
|
|
||||||
|
|
||||||
config.pg_user = click.prompt(
|
|
||||||
"Which user will authenticate with the server?",
|
|
||||||
default = config.pg_user
|
|
||||||
)
|
|
||||||
|
|
||||||
config.pg_pass = click.prompt(
|
|
||||||
"User password",
|
|
||||||
hide_input = True,
|
|
||||||
show_default = False,
|
|
||||||
default = config.pg_pass or ""
|
|
||||||
) or None
|
|
|
@ -1,51 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from ..application import Application
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("config")
|
|
||||||
def cli_config() -> None:
|
|
||||||
"Manage the relay settings stored in the database"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_config.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_config_list(app: Application) -> None:
|
|
||||||
"List the current relay config"
|
|
||||||
|
|
||||||
click.echo("Relay Config:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
config = conn.get_config_all()
|
|
||||||
|
|
||||||
for key, value in config.to_dict().items():
|
|
||||||
if key in type(config).SYSTEM_KEYS():
|
|
||||||
continue
|
|
||||||
|
|
||||||
if key == "log-level":
|
|
||||||
value = value.name
|
|
||||||
|
|
||||||
key_str = f"{key}:".ljust(20)
|
|
||||||
click.echo(f"- {key_str} {repr(value)}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_config.command("set")
|
|
||||||
@click.argument("key")
|
|
||||||
@click.argument("value")
|
|
||||||
@pass_app
|
|
||||||
def cli_config_set(app: Application, key: str, value: Any) -> None:
|
|
||||||
"Set a config value"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with app.database.session() as conn:
|
|
||||||
new_value = conn.put_config(key, value)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
click.echo(f"Invalid config name: {key}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"{key}: {repr(new_value)}")
|
|
|
@ -1,169 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import click
|
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from .. import http_client as http
|
|
||||||
from ..application import Application
|
|
||||||
from ..database.schema import Instance
|
|
||||||
from ..misc import ACTOR_FORMATS, Message
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("inbox")
|
|
||||||
def cli_inbox() -> None:
|
|
||||||
"Manage the inboxes in the database"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_inbox.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_inbox_list(app: Application) -> None:
|
|
||||||
"List the connected instances or relays"
|
|
||||||
|
|
||||||
click.echo("Connected to the following instances or relays:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_inboxes():
|
|
||||||
click.echo(f"- {row.inbox}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_inbox.command("follow")
|
|
||||||
@click.argument("actor")
|
|
||||||
@pass_app
|
|
||||||
def cli_inbox_follow(app: Application, actor: str) -> None:
|
|
||||||
"Follow an actor (Relay must be running)"
|
|
||||||
|
|
||||||
instance: Instance | None = None
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_domain_ban(actor):
|
|
||||||
click.echo(f"Error: Refusing to follow banned actor: {actor}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if (instance := conn.get_inbox(actor)) is not None:
|
|
||||||
inbox = instance.inbox
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not actor.startswith("http"):
|
|
||||||
actor = f"https://{actor}/actor"
|
|
||||||
|
|
||||||
if (actor_data := asyncio.run(http.get(actor, sign_headers = True))) is None:
|
|
||||||
click.echo(f"Failed to fetch actor: {actor}")
|
|
||||||
return
|
|
||||||
|
|
||||||
inbox = actor_data.shared_inbox
|
|
||||||
|
|
||||||
message = Message.new_follow(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = actor
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run(http.post(inbox, message, instance))
|
|
||||||
click.echo(f"Sent follow message to actor: {actor}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_inbox.command("unfollow")
|
|
||||||
@click.argument("actor")
|
|
||||||
@pass_app
|
|
||||||
def cli_inbox_unfollow(app: Application, actor: str) -> None:
|
|
||||||
"Unfollow an actor (Relay must be running)"
|
|
||||||
|
|
||||||
instance: Instance | None = None
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_domain_ban(actor):
|
|
||||||
click.echo(f"Error: Refusing to follow banned actor: {actor}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if (instance := conn.get_inbox(actor)):
|
|
||||||
inbox = instance.inbox
|
|
||||||
message = Message.new_unfollow(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = actor,
|
|
||||||
follow = instance.followid
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if not actor.startswith("http"):
|
|
||||||
actor = f"https://{actor}/actor"
|
|
||||||
|
|
||||||
actor_data = asyncio.run(http.get(actor, sign_headers = True))
|
|
||||||
|
|
||||||
if not actor_data:
|
|
||||||
click.echo("Failed to fetch actor")
|
|
||||||
return
|
|
||||||
|
|
||||||
inbox = actor_data.shared_inbox
|
|
||||||
message = Message.new_unfollow(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = actor,
|
|
||||||
follow = {
|
|
||||||
"type": "Follow",
|
|
||||||
"object": actor,
|
|
||||||
"actor": f"https://{app.config.domain}/actor"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run(http.post(inbox, message, instance))
|
|
||||||
click.echo(f"Sent unfollow message to: {actor}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_inbox.command("add")
|
|
||||||
@click.argument("inbox")
|
|
||||||
@click.option("--actor", "-a", help = "Actor url for the inbox")
|
|
||||||
@click.option("--followid", "-f", help = "Url for the follow activity")
|
|
||||||
@click.option("--software", "-s", help = "Nodeinfo software name of the instance")
|
|
||||||
@pass_app
|
|
||||||
def cli_inbox_add(
|
|
||||||
app: Application,
|
|
||||||
inbox: str,
|
|
||||||
actor: str | None = None,
|
|
||||||
followid: str | None = None,
|
|
||||||
software: str | None = None) -> None:
|
|
||||||
"Add an inbox to the database"
|
|
||||||
|
|
||||||
if not inbox.startswith("http"):
|
|
||||||
domain = inbox
|
|
||||||
inbox = f"https://{inbox}/inbox"
|
|
||||||
|
|
||||||
else:
|
|
||||||
domain = urlparse(inbox).netloc
|
|
||||||
|
|
||||||
if not software:
|
|
||||||
if (nodeinfo := asyncio.run(http.fetch_nodeinfo(domain))):
|
|
||||||
software = nodeinfo.sw_name
|
|
||||||
|
|
||||||
if not actor and software:
|
|
||||||
try:
|
|
||||||
actor = ACTOR_FORMATS[software].format(domain = domain)
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_domain_ban(domain):
|
|
||||||
click.echo(f"Refusing to add banned inbox: {inbox}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if conn.get_inbox(inbox):
|
|
||||||
click.echo(f"Error: Inbox already in database: {inbox}")
|
|
||||||
return
|
|
||||||
|
|
||||||
conn.put_inbox(domain, inbox, actor, followid, software)
|
|
||||||
|
|
||||||
click.echo(f"Added inbox to the database: {inbox}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_inbox.command("remove")
|
|
||||||
@click.argument("inbox")
|
|
||||||
@pass_app
|
|
||||||
def cli_inbox_remove(app: Application, inbox: str) -> None:
|
|
||||||
"Remove an inbox from the database"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if not conn.del_inbox(inbox):
|
|
||||||
click.echo(f"Inbox not in database: {inbox}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Removed inbox from the database: {inbox}")
|
|
|
@ -1,89 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from ..application import Application
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("instance")
|
|
||||||
def cli_instance() -> None:
|
|
||||||
"Manage instance bans"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_instance.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_instance_list(app: Application) -> None:
|
|
||||||
"List all banned instances"
|
|
||||||
|
|
||||||
click.echo("Banned domains:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_domain_bans():
|
|
||||||
if row.reason:
|
|
||||||
click.echo(f"- {row.domain} ({row.reason})")
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo(f"- {row.domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_instance.command("ban")
|
|
||||||
@click.argument("domain")
|
|
||||||
@click.option("--reason", "-r", help = "Public note about why the domain is banned")
|
|
||||||
@click.option("--note", "-n", help = "Internal note that will only be seen by admins and mods")
|
|
||||||
@pass_app
|
|
||||||
def cli_instance_ban(app: Application, domain: str, reason: str, note: str) -> None:
|
|
||||||
"Ban an instance and remove the associated inbox if it exists"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_domain_ban(domain) is not None:
|
|
||||||
click.echo(f"Domain already banned: {domain}")
|
|
||||||
return
|
|
||||||
|
|
||||||
conn.put_domain_ban(domain, reason, note)
|
|
||||||
conn.del_inbox(domain)
|
|
||||||
click.echo(f"Banned instance: {domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_instance.command("unban")
|
|
||||||
@click.argument("domain")
|
|
||||||
@pass_app
|
|
||||||
def cli_instance_unban(app: Application, domain: str) -> None:
|
|
||||||
"Unban an instance"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.del_domain_ban(domain) is None:
|
|
||||||
click.echo(f"Instance wasn\"t banned: {domain}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Unbanned instance: {domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_instance.command("update")
|
|
||||||
@click.argument("domain")
|
|
||||||
@click.option("--reason", "-r")
|
|
||||||
@click.option("--note", "-n")
|
|
||||||
@click.pass_context
|
|
||||||
@pass_app
|
|
||||||
def cli_instance_update(
|
|
||||||
app: Application,
|
|
||||||
ctx: click.Context,
|
|
||||||
domain: str,
|
|
||||||
reason: str,
|
|
||||||
note: str) -> None:
|
|
||||||
"Update the public reason or internal note for a domain ban"
|
|
||||||
|
|
||||||
if not (reason or note):
|
|
||||||
ctx.fail("Must pass --reason or --note")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if not (row := conn.update_domain_ban(domain, reason, note)):
|
|
||||||
click.echo(f"Failed to update domain ban: {domain}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Updated domain ban: {domain}")
|
|
||||||
|
|
||||||
if row.reason:
|
|
||||||
click.echo(f"- {row.domain} ({row.reason})")
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo(f"- {row.domain}")
|
|
|
@ -1,82 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import click
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from .. import http_client as http
|
|
||||||
from ..application import Application
|
|
||||||
from ..misc import Message
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("request")
|
|
||||||
def cli_request() -> None:
|
|
||||||
"Manage follow requests"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_request.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_request_list(app: Application) -> None:
|
|
||||||
"List all current follow requests"
|
|
||||||
|
|
||||||
click.echo("Follow requests:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_requests():
|
|
||||||
date = row.created.strftime("%Y-%m-%d")
|
|
||||||
click.echo(f"- [{date}] {row.domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_request.command("accept")
|
|
||||||
@click.argument("domain")
|
|
||||||
@pass_app
|
|
||||||
def cli_request_accept(app: Application, domain: str) -> None:
|
|
||||||
"Accept a follow request"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with app.database.session() as conn:
|
|
||||||
instance = conn.put_request_response(domain, True)
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
click.echo("Request not found")
|
|
||||||
return
|
|
||||||
|
|
||||||
message = Message.new_response(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = instance.actor,
|
|
||||||
followid = instance.followid,
|
|
||||||
accept = True
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run(http.post(instance.inbox, message, instance))
|
|
||||||
|
|
||||||
if instance.software != "mastodon":
|
|
||||||
message = Message.new_follow(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = instance.actor
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run(http.post(instance.inbox, message, instance))
|
|
||||||
|
|
||||||
|
|
||||||
@cli_request.command("deny")
|
|
||||||
@click.argument("domain")
|
|
||||||
@pass_app
|
|
||||||
def cli_request_deny(app: Application, domain: str) -> None:
|
|
||||||
"Accept a follow request"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with app.database.session() as conn:
|
|
||||||
instance = conn.put_request_response(domain, False)
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
click.echo("Request not found")
|
|
||||||
return
|
|
||||||
|
|
||||||
response = Message.new_response(
|
|
||||||
host = app.config.domain,
|
|
||||||
actor = instance.actor,
|
|
||||||
followid = instance.followid,
|
|
||||||
accept = False
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run(http.post(instance.inbox, response, instance))
|
|
|
@ -1,143 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import click
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from .. import http_client as http
|
|
||||||
from ..application import Application
|
|
||||||
from ..misc import RELAY_SOFTWARE
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("software")
|
|
||||||
def cli_software() -> None:
|
|
||||||
"Manage banned software"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_software.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_software_list(app: Application) -> None:
|
|
||||||
"List all banned software"
|
|
||||||
|
|
||||||
click.echo("Banned software:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_software_bans():
|
|
||||||
if row.reason:
|
|
||||||
click.echo(f"- {row.name} ({row.reason})")
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo(f"- {row.name}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_software.command("ban")
|
|
||||||
@click.argument("name")
|
|
||||||
@click.option("--reason", "-r")
|
|
||||||
@click.option("--note", "-n")
|
|
||||||
@click.option(
|
|
||||||
"--fetch-nodeinfo", "-f",
|
|
||||||
is_flag = True,
|
|
||||||
help = "Treat NAME like a domain and try to fetch the software name from nodeinfo"
|
|
||||||
)
|
|
||||||
@pass_app
|
|
||||||
def cli_software_ban(app: Application,
|
|
||||||
name: str,
|
|
||||||
reason: str,
|
|
||||||
note: str,
|
|
||||||
fetch_nodeinfo: bool) -> None:
|
|
||||||
"Ban software. Use RELAYS for NAME to ban relays"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if name == "RELAYS":
|
|
||||||
for item in RELAY_SOFTWARE:
|
|
||||||
if conn.get_software_ban(item):
|
|
||||||
click.echo(f"Relay already banned: {item}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
conn.put_software_ban(item, reason or "relay", note)
|
|
||||||
|
|
||||||
click.echo("Banned all relay software")
|
|
||||||
return
|
|
||||||
|
|
||||||
if fetch_nodeinfo:
|
|
||||||
if not (nodeinfo := asyncio.run(http.fetch_nodeinfo(name))):
|
|
||||||
click.echo(f"Failed to fetch software name from domain: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
name = nodeinfo.sw_name
|
|
||||||
|
|
||||||
if conn.get_software_ban(name):
|
|
||||||
click.echo(f"Software already banned: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not conn.put_software_ban(name, reason, note):
|
|
||||||
click.echo(f"Failed to ban software: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Banned software: {name}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_software.command("unban")
|
|
||||||
@click.argument("name")
|
|
||||||
@click.option("--reason", "-r")
|
|
||||||
@click.option("--note", "-n")
|
|
||||||
@click.option(
|
|
||||||
"--fetch-nodeinfo", "-f",
|
|
||||||
is_flag = True,
|
|
||||||
help = "Treat NAME like a domain and try to fetch the software name from nodeinfo"
|
|
||||||
)
|
|
||||||
@pass_app
|
|
||||||
def cli_software_unban(app: Application, name: str, fetch_nodeinfo: bool) -> None:
|
|
||||||
"Ban software. Use RELAYS for NAME to unban relays"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if name == "RELAYS":
|
|
||||||
for software in RELAY_SOFTWARE:
|
|
||||||
if not conn.del_software_ban(software):
|
|
||||||
click.echo(f"Relay was not banned: {software}")
|
|
||||||
|
|
||||||
click.echo("Unbanned all relay software")
|
|
||||||
return
|
|
||||||
|
|
||||||
if fetch_nodeinfo:
|
|
||||||
if not (nodeinfo := asyncio.run(http.fetch_nodeinfo(name))):
|
|
||||||
click.echo(f"Failed to fetch software name from domain: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
name = nodeinfo.sw_name
|
|
||||||
|
|
||||||
if not conn.del_software_ban(name):
|
|
||||||
click.echo(f"Software was not banned: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Unbanned software: {name}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_software.command("update")
|
|
||||||
@click.argument("name")
|
|
||||||
@click.option("--reason", "-r")
|
|
||||||
@click.option("--note", "-n")
|
|
||||||
@click.pass_context
|
|
||||||
@pass_app
|
|
||||||
def cli_software_update(
|
|
||||||
app: Application,
|
|
||||||
ctx: click.Context,
|
|
||||||
name: str,
|
|
||||||
reason: str,
|
|
||||||
note: str) -> None:
|
|
||||||
"Update the public reason or internal note for a software ban"
|
|
||||||
|
|
||||||
if not (reason or note):
|
|
||||||
ctx.fail("Must pass --reason or --note")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if not (row := conn.update_software_ban(name, reason, note)):
|
|
||||||
click.echo(f"Failed to update software ban: {name}")
|
|
||||||
return
|
|
||||||
|
|
||||||
click.echo(f"Updated software ban: {name}")
|
|
||||||
|
|
||||||
if row.reason:
|
|
||||||
click.echo(f"- {row.name} ({row.reason})")
|
|
||||||
|
|
||||||
else:
|
|
||||||
click.echo(f"- {row.name}")
|
|
|
@ -1,66 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from ..application import Application
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("user")
|
|
||||||
def cli_user() -> None:
|
|
||||||
"Manage local users"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_user.command("list")
|
|
||||||
@pass_app
|
|
||||||
def cli_user_list(app: Application) -> None:
|
|
||||||
"List all local users"
|
|
||||||
|
|
||||||
click.echo("Users:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_users():
|
|
||||||
click.echo(f"- {row.username}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_user.command("create")
|
|
||||||
@click.argument("username")
|
|
||||||
@click.argument("handle", required = False)
|
|
||||||
@pass_app
|
|
||||||
def cli_user_create(app: Application, username: str, handle: str) -> None:
|
|
||||||
"Create a new local user"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_user(username) is not None:
|
|
||||||
click.echo(f"User already exists: {username}")
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if not (password := click.prompt("New password", hide_input = True)):
|
|
||||||
click.echo("No password provided")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if password != click.prompt("New password again", hide_input = True):
|
|
||||||
click.echo("Passwords do not match")
|
|
||||||
continue
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
conn.put_user(username, password, handle)
|
|
||||||
|
|
||||||
click.echo(f"Created user {username}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_user.command("delete")
|
|
||||||
@click.argument("username")
|
|
||||||
@pass_app
|
|
||||||
def cli_user_delete(app: Application, username: str) -> None:
|
|
||||||
"Delete a local user"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_user(username) is None:
|
|
||||||
click.echo(f"User does not exist: {username}")
|
|
||||||
return
|
|
||||||
|
|
||||||
conn.del_user(username)
|
|
||||||
|
|
||||||
click.echo(f"Deleted user {username}")
|
|
|
@ -1,73 +0,0 @@
|
||||||
import click
|
|
||||||
|
|
||||||
from . import cli, pass_app
|
|
||||||
|
|
||||||
from ..application import Application
|
|
||||||
from ..database.schema import Whitelist
|
|
||||||
|
|
||||||
|
|
||||||
@cli.group("whitelist")
|
|
||||||
def cli_whitelist() -> None:
|
|
||||||
"Manage the instance whitelist"
|
|
||||||
|
|
||||||
|
|
||||||
@cli_whitelist.command("list")
|
|
||||||
@click.pass_context
|
|
||||||
@pass_app
|
|
||||||
def cli_whitelist_list(app: Application, ctx: click.Context) -> None:
|
|
||||||
"List all the instances in the whitelist"
|
|
||||||
|
|
||||||
click.echo("Current whitelisted domains:")
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.execute("SELECT * FROM whitelist").all(Whitelist):
|
|
||||||
click.echo(f"- {row.domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_whitelist.command("add")
|
|
||||||
@click.argument("domain")
|
|
||||||
@pass_app
|
|
||||||
def cli_whitelist_add(app: Application, domain: str) -> None:
|
|
||||||
"Add a domain to the whitelist"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if conn.get_domain_whitelist(domain):
|
|
||||||
click.echo(f"Instance already in the whitelist: {domain}")
|
|
||||||
return
|
|
||||||
|
|
||||||
conn.put_domain_whitelist(domain)
|
|
||||||
click.echo(f"Instance added to the whitelist: {domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_whitelist.command("remove")
|
|
||||||
@click.argument("domain")
|
|
||||||
@pass_app
|
|
||||||
def cli_whitelist_remove(app: Application, domain: str) -> None:
|
|
||||||
"Remove an instance from the whitelist"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
if not conn.del_domain_whitelist(domain):
|
|
||||||
click.echo(f"Domain not in the whitelist: {domain}")
|
|
||||||
return
|
|
||||||
|
|
||||||
if conn.get_config("whitelist-enabled"):
|
|
||||||
if conn.del_inbox(domain):
|
|
||||||
click.echo(f"Removed inbox for domain: {domain}")
|
|
||||||
|
|
||||||
click.echo(f"Removed domain from the whitelist: {domain}")
|
|
||||||
|
|
||||||
|
|
||||||
@cli_whitelist.command("import")
|
|
||||||
@pass_app
|
|
||||||
def cli_whitelist_import(app: Application) -> None:
|
|
||||||
"Add all current instances to the whitelist"
|
|
||||||
|
|
||||||
with app.database.session() as conn:
|
|
||||||
for row in conn.get_inboxes():
|
|
||||||
if conn.get_domain_whitelist(row.domain) is not None:
|
|
||||||
click.echo(f"Domain already in whitelist: {row.domain}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
conn.put_domain_whitelist(row.domain)
|
|
||||||
|
|
||||||
click.echo("Imported whitelist from inboxes")
|
|
|
@ -13,12 +13,8 @@ from typing import TYPE_CHECKING, Any
|
||||||
from .misc import IS_DOCKER
|
from .misc import IS_DOCKER
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
try:
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|
|
@ -3,8 +3,8 @@ import sqlite3
|
||||||
from blib import Date, File
|
from blib import Date, File
|
||||||
from bsql import Database
|
from bsql import Database
|
||||||
|
|
||||||
from .config import ConfigData
|
from .config import THEMES, ConfigData
|
||||||
from .connection import Connection
|
from .connection import RELAY_SOFTWARE, Connection
|
||||||
from .schema import TABLES, VERSIONS, migrate_0
|
from .schema import TABLES, VERSIONS, migrate_0
|
||||||
|
|
||||||
from .. import logger as logging
|
from .. import logger as logging
|
||||||
|
|
|
@ -11,12 +11,8 @@ from typing import TYPE_CHECKING, Any
|
||||||
from .. import logger as logging
|
from .. import logger as logging
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
try:
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
THEMES = {
|
THEMES = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
|
@ -22,6 +22,14 @@ from ..misc import Message, get_app
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..application import Application
|
from ..application import Application
|
||||||
|
|
||||||
|
RELAY_SOFTWARE = [
|
||||||
|
"activityrelay", # https://git.pleroma.social/pleroma/relay
|
||||||
|
"activity-relay", # https://github.com/yukimochi/Activity-Relay
|
||||||
|
"aoderelay", # https://git.asonix.dog/asonix/relay
|
||||||
|
"feditools-relay", # https://git.ptzo.gdn/feditools/relay
|
||||||
|
"buzzrelay" # https://github.com/astro/buzzrelay
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Connection(SqlConnection):
|
class Connection(SqlConnection):
|
||||||
hasher = PasswordHasher(
|
hasher = PasswordHasher(
|
||||||
|
|
|
@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
||||||
T = TypeVar("T", bound = JsonBase[Any])
|
T = TypeVar("T", bound = JsonBase[Any])
|
||||||
|
|
||||||
HEADERS = {
|
HEADERS = {
|
||||||
"Accept": f"{MIMETYPES['activity']}, {MIMETYPES['json']};q=0.9",
|
"Accept": f"{MIMETYPES["activity"]}, {MIMETYPES["json"]};q=0.9",
|
||||||
"User-Agent": f"ActivityRelay/{__version__}"
|
"User-Agent": f"ActivityRelay/{__version__}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,8 @@ from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Protocol
|
from typing import TYPE_CHECKING, Any, Protocol
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
try:
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
class LoggingMethod(Protocol):
|
class LoggingMethod(Protocol):
|
||||||
def __call__(self, msg: Any, *args: Any, **kwargs: Any) -> None: ...
|
def __call__(self, msg: Any, *args: Any, **kwargs: Any) -> None: ...
|
||||||
|
|
1044
relay/manage.py
Normal file
1044
relay/manage.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,13 +12,8 @@ from typing import TYPE_CHECKING, Any, TypedDict, TypeVar, overload
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .application import Application
|
|
||||||
|
|
||||||
try:
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
from .application import Application
|
||||||
except ImportError:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
@ -62,14 +57,6 @@ JSON_PATHS: tuple[str, ...] = (
|
||||||
"/oauth/revoke"
|
"/oauth/revoke"
|
||||||
)
|
)
|
||||||
|
|
||||||
RELAY_SOFTWARE = [
|
|
||||||
"activityrelay", # https://git.pleroma.social/pleroma/relay
|
|
||||||
"activity-relay", # https://github.com/yukimochi/Activity-Relay
|
|
||||||
"aoderelay", # https://git.asonix.dog/asonix/relay
|
|
||||||
"feditools-relay", # https://git.ptzo.gdn/feditools/relay
|
|
||||||
"buzzrelay" # https://github.com/astro/buzzrelay
|
|
||||||
]
|
|
||||||
|
|
||||||
TOKEN_PATHS: tuple[str, ...] = (
|
TOKEN_PATHS: tuple[str, ...] = (
|
||||||
"/logout",
|
"/logout",
|
||||||
"/admin",
|
"/admin",
|
||||||
|
@ -181,7 +168,7 @@ class Message(aputils.Message):
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_unfollow(cls: type[Self], host: str, actor: str, follow: dict[str, str] | str) -> Self:
|
def new_unfollow(cls: type[Self], host: str, actor: str, follow: dict[str, str]) -> Self:
|
||||||
return cls.new(aputils.ObjectType.UNDO, {
|
return cls.new(aputils.ObjectType.UNDO, {
|
||||||
"id": f"https://{host}/activities/{uuid4()}",
|
"id": f"https://{host}/activities/{uuid4()}",
|
||||||
"to": [actor],
|
"to": [actor],
|
||||||
|
|
|
@ -7,7 +7,7 @@ from urllib.parse import unquote
|
||||||
|
|
||||||
from .base import METHODS, register_route
|
from .base import METHODS, register_route
|
||||||
|
|
||||||
from ..database.config import THEMES
|
from ..database import THEMES
|
||||||
from ..logger import LogLevel
|
from ..logger import LogLevel
|
||||||
from ..misc import Response
|
from ..misc import Response
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue