mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-02 09:21:40 +00:00
feat: unified messaging TUI MVP — Synapse + 7 mautrix bridges + Textual TUI + MCP draft staging
One-command local stack: cp .env.example .env && make up. Idempotent bootstrap renders Synapse + bridge configs from pristine upstream examples, provisions databases, registrations, double puppeting, and the admin user. Textual TUI with bridge manager, in-terminal QR login, and drafts panel. MCP server exposes read tools and a human-gated draft send workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# ── Vortex unified messaging stack ─────────────────────────────────
|
||||
# Copy to .env, edit, then `make up`. Nothing else to configure.
|
||||
|
||||
# Matrix server name (domain part of user IDs). Keep `localhost` for local use.
|
||||
MATRIX_SERVER_NAME=localhost
|
||||
|
||||
# Admin account created automatically on first boot.
|
||||
MATRIX_USER=admin
|
||||
MATRIX_PASSWORD=changeme
|
||||
|
||||
# Postgres superuser password (user: vortex).
|
||||
POSTGRES_PASSWORD=changeme
|
||||
|
||||
# Shared secret for Synapse admin registration. `auto` = generated once and
|
||||
# persisted to the data volume.
|
||||
REGISTRATION_SHARED_SECRET=auto
|
||||
|
||||
# Shared secret for bridge double puppeting (appservice method). `auto` = generated.
|
||||
DOUBLEPUPPET_SHARED_SECRET=auto
|
||||
|
||||
# Which bridges to run. Comma-separated, no spaces.
|
||||
# Full set: gmessages,telegram,whatsapp,twitter,linkedin,discord,meta
|
||||
BRIDGES_ENABLED=gmessages,telegram,whatsapp,twitter,linkedin,discord,meta
|
||||
# Compose starts exactly the bridges listed above (do not edit this line):
|
||||
COMPOSE_PROFILES=${BRIDGES_ENABLED}
|
||||
|
||||
# Telegram needs API credentials from https://my.telegram.org.
|
||||
# Leave empty to skip the Telegram bridge gracefully (its container idles with a log line).
|
||||
TELEGRAM_API_ID=
|
||||
TELEGRAM_API_HASH=
|
||||
|
||||
# MCP server (streamable HTTP) host port.
|
||||
MCP_HTTP_PORT=8765
|
||||
|
||||
# When false (default), the MCP send_draft tool refuses to send; a human must
|
||||
# send drafts from the TUI. Set true to let AI agents send staged drafts.
|
||||
MCP_ALLOW_SEND=false
|
||||
|
||||
TZ=UTC
|
||||
@@ -0,0 +1,5 @@
|
||||
.env
|
||||
shared/drafts/*.db*
|
||||
**/__pycache__/
|
||||
*.egg-info/
|
||||
.venv*/
|
||||
@@ -0,0 +1,28 @@
|
||||
.PHONY: up down logs bootstrap tui verify clean nuke
|
||||
|
||||
up: .env
|
||||
docker compose up -d --build --wait
|
||||
|
||||
.env:
|
||||
@test -f .env || { echo "No .env found - copy .env.example to .env first:"; echo " cp .env.example .env"; exit 1; }
|
||||
|
||||
down:
|
||||
docker compose down
|
||||
|
||||
logs:
|
||||
docker compose logs -f --tail=100
|
||||
|
||||
bootstrap:
|
||||
docker compose run --rm bootstrap
|
||||
|
||||
tui:
|
||||
docker compose run --rm --build tui
|
||||
|
||||
verify:
|
||||
bash scripts/verify.sh
|
||||
|
||||
clean:
|
||||
docker compose down --remove-orphans
|
||||
|
||||
nuke:
|
||||
docker compose --profile '*' down -v --remove-orphans
|
||||
@@ -0,0 +1,149 @@
|
||||
# Vortex — unified messaging terminal client
|
||||
|
||||
All your chat networks (WhatsApp, Telegram, Google Messages, X/Twitter, LinkedIn,
|
||||
Discord, Instagram) in one terminal UI, built on Matrix + mautrix bridges, with an
|
||||
MCP server that lets AI agents read your messages and stage drafts — but never
|
||||
send without a human.
|
||||
|
||||
```
|
||||
┌────────────┐ ┌─────────┐ ┌──────────────────────────────┐
|
||||
│ Textual TUI│──▶│ Synapse │◀──│ mautrix bridges (7 networks) │
|
||||
└─────┬──────┘ │ +Postgres│ └──────────────────────────────┘
|
||||
│ drafts └────┬────┘
|
||||
▼ SQLite │
|
||||
┌────────────┐ │
|
||||
│ MCP server │────────┘ read tools + draft staging for AI agents
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
## Quickstart
|
||||
|
||||
```sh
|
||||
cp .env.example .env # edit MATRIX_PASSWORD / POSTGRES_PASSWORD at minimum
|
||||
make up # builds + provisions + starts everything
|
||||
make tui # open the terminal client (already logged in)
|
||||
make verify # smoke-test the whole stack
|
||||
```
|
||||
|
||||
That is the entire setup. Bootstrap renders the Synapse config, creates
|
||||
databases, generates bridge configs + appservice registrations, registers the
|
||||
admin user, and writes credentials to a shared volume. Re-running `make up` is
|
||||
a no-op. No YAML editing, no token copying.
|
||||
|
||||
## Make targets
|
||||
|
||||
| target | what |
|
||||
|---|---|
|
||||
| `make up` | build + start the full stack, wait for healthy |
|
||||
| `make tui` | run the Textual TUI (interactive) |
|
||||
| `make verify` | smoke tests: health, appservice round-trip, MCP draft gating |
|
||||
| `make logs` | follow all logs |
|
||||
| `make down` | stop containers |
|
||||
| `make nuke` | stop and delete **all data** (volumes included) |
|
||||
|
||||
## Logging into bridges
|
||||
|
||||
Open the TUI, press `b` for the bridge screen, pick a network — it opens a DM
|
||||
with that bridge's bot. Send `help` to list commands and `login` to start the
|
||||
login flow. QR codes sent by bots render in-terminal.
|
||||
|
||||
| network | bot | login flow |
|
||||
|---|---|---|
|
||||
| WhatsApp | `@whatsappbot` | `login qr` (scan with phone) or `login pairing-code` |
|
||||
| Google Messages | `@gmessagesbot` | `login` → QR scan from the Messages app |
|
||||
| Telegram | `@telegrambot` | `login` → phone number + code. **Requires `TELEGRAM_API_ID`/`TELEGRAM_API_HASH` in `.env`** (get them at [my.telegram.org](https://my.telegram.org)). Without them the bridge is skipped with a log line. |
|
||||
| X/Twitter | `@twitterbot` | `login cookies` — paste browser cookies |
|
||||
| LinkedIn | `@linkedinbot` | `login cookies` |
|
||||
| Discord | `@discordbot` | `login qr` (scan with Discord app) or `login token` |
|
||||
| Instagram | `@metabot` | `login cookies` (bridge runs in `instagram` mode) |
|
||||
|
||||
Cookie flows: the bot explains exactly which cookies to paste. Double puppeting
|
||||
is pre-configured for all bridges — your own messages sent from other devices
|
||||
appear as you.
|
||||
|
||||
## The TUI
|
||||
|
||||
Three panes: room list grouped by network (with `[wa] [tg] [gm] [tw] [li] [dc]
|
||||
[ig] [mx]` labels and unread counts), message timeline, input bar.
|
||||
|
||||
Keys: `q` quit · `b` bridge manager · `d` drafts panel · `tab` cycle focus ·
|
||||
`enter` (in room list) open room. Type `/reply <text>` to reply to the last
|
||||
message. Media shows as download links.
|
||||
|
||||
The drafts panel lists drafts staged by AI agents via MCP (live, 2s poll):
|
||||
`e` edit · `s` send · `x` discard. This is the human approval step.
|
||||
|
||||
Native run (no Docker): `pip install -e ./tui`, then
|
||||
`MATRIX_HOMESERVER=http://localhost:8008 vortex-tui` (needs the shared volume
|
||||
mounted or `CREDENTIALS_FILE`/`DRAFTS_DB`/`STORE_DIR` pointed somewhere useful).
|
||||
|
||||
## MCP server for AI agents
|
||||
|
||||
Runs at `http://localhost:8765/mcp` (streamable HTTP) and over stdio.
|
||||
|
||||
Tools: `list_rooms`, `read_messages`, `search_messages`, `create_draft`,
|
||||
`list_drafts`, `update_draft`, `discard_draft`, `send_draft`.
|
||||
|
||||
`create_draft` never sends. `send_draft` is refused while `MCP_ALLOW_SEND=false`
|
||||
(the default) — the agent is told a human must send from the TUI. Drafts appear
|
||||
in the TUI drafts panel live.
|
||||
|
||||
Claude Code:
|
||||
|
||||
```sh
|
||||
claude mcp add --transport http vortex http://localhost:8765/mcp
|
||||
```
|
||||
|
||||
Claude Desktop (`claude_desktop_config.json`), HTTP transport:
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "vortex": { "url": "http://localhost:8765/mcp" } } }
|
||||
```
|
||||
|
||||
stdio transport (spawns inside the running container):
|
||||
|
||||
```json
|
||||
{ "mcpServers": { "vortex": {
|
||||
"command": "docker",
|
||||
"args": ["compose", "-f", "/absolute/path/to/vortex/docker-compose.yml",
|
||||
"exec", "-i", "mcp", "vortex-mcp", "--stdio"] } } }
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
See `.env.example` — every value is documented inline. Highlights:
|
||||
|
||||
- `BRIDGES_ENABLED` — comma-separated bridge list; compose starts exactly these.
|
||||
After changing it, run `make down && make up`.
|
||||
- `REGISTRATION_SHARED_SECRET` / `DOUBLEPUPPET_SHARED_SECRET` — leave as `auto`;
|
||||
generated once and persisted in the data volume.
|
||||
- `MCP_ALLOW_SEND` — set `true` only if you want AI agents to send without you.
|
||||
|
||||
## How provisioning works
|
||||
|
||||
`bootstrap` (one-shot, runs before Synapse) renders `homeserver.yaml` from
|
||||
`synapse/homeserver.template.yaml`, generates a signing key and secrets, creates
|
||||
one Postgres DB per bridge, patches each bridge's config from the pristine
|
||||
upstream example in `bridges/templates/` (homeserver address, appservice
|
||||
address, Postgres URI, admin permissions, double-puppet secret; Telegram API
|
||||
creds; Instagram mode), and writes matching `registration.yaml` files for
|
||||
Synapse *and* each bridge. `provision` (one-shot, after Synapse is healthy)
|
||||
registers the admin user via the shared-secret admin API and writes
|
||||
`credentials.json` to the shared volume; the TUI and MCP server log in from it
|
||||
with their own devices. Everything is keyed on "file already exists" — re-runs
|
||||
change nothing.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`The as_token was not accepted` in a bridge log** — registration drift;
|
||||
run `make down && make up` (bootstrap rewrites registrations from persisted
|
||||
tokens). If it persists: `make nuke && make up`.
|
||||
- **Telegram container prints "skipped"** — expected without
|
||||
`TELEGRAM_API_ID`/`TELEGRAM_API_HASH`. Set them, then `make down && make up`.
|
||||
- **Bridge bot doesn't answer** — `docker compose logs <bridge>`; the bridge
|
||||
must show a successful websocket/appservice connection to Synapse.
|
||||
- **TUI can't log in** — check `docker compose logs provision`; delete
|
||||
`tui-store/session.json` in the shared volume to force a fresh login.
|
||||
- **Changed `MATRIX_SERVER_NAME` after first boot** — Synapse server names are
|
||||
permanent; `make nuke && make up`.
|
||||
- **Ports busy** — Synapse uses host `8008`, MCP uses `MCP_HTTP_PORT` (8765).
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache bash curl openssl yq gettext postgresql16-client python3
|
||||
COPY bootstrap.sh provision.sh /
|
||||
RUN chmod +x /bootstrap.sh /provision.sh
|
||||
CMD ["/bootstrap.sh"]
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/bin/bash
|
||||
# Idempotent one-shot provisioning: renders Synapse + bridge configs, creates
|
||||
# bridge databases, generates registrations. Safe to re-run on every `up`.
|
||||
set -euo pipefail
|
||||
|
||||
DATA=/data
|
||||
SECRETS=$DATA/secrets
|
||||
DOMAIN=${MATRIX_SERVER_NAME:-localhost}
|
||||
ADMIN_MXID="@${MATRIX_USER:-admin}:${DOMAIN}"
|
||||
ALL_BRIDGES="gmessages telegram whatsapp twitter linkedin discord meta"
|
||||
|
||||
log() { echo "[bootstrap] $*"; }
|
||||
|
||||
mkdir -p "$SECRETS" "$DATA/synapse/appservices" "$DATA/shared/drafts" "$DATA/shared/tui-store" "$DATA/shared/mcp-store"
|
||||
for b in $ALL_BRIDGES; do mkdir -p "$DATA/bridges/$b"; done # subpath mounts need these even for disabled bridges
|
||||
chmod -R a+rwX "$DATA/shared"
|
||||
|
||||
# ── secrets ─────────────────────────────────────────────────────────
|
||||
# secret_of NAME ENV_VALUE: if env value is "auto"/"auto-or-changeme"/empty,
|
||||
# generate once and persist; else use env value verbatim.
|
||||
secret_of() {
|
||||
local name=$1 val=${2:-auto} f=$SECRETS/$1
|
||||
case "$val" in auto|auto-or-changeme|"")
|
||||
[ -f "$f" ] || openssl rand -hex 32 > "$f"
|
||||
cat "$f" ;;
|
||||
*) echo "$val" ;;
|
||||
esac
|
||||
}
|
||||
REG_SECRET=$(secret_of registration_shared_secret "${REGISTRATION_SHARED_SECRET:-auto}")
|
||||
DP_SECRET=$(secret_of doublepuppet_shared_secret "${DOUBLEPUPPET_SHARED_SECRET:-auto}")
|
||||
MACAROON=$(secret_of macaroon_secret_key auto)
|
||||
FORM=$(secret_of form_secret auto)
|
||||
|
||||
# per-bridge appservice tokens, generated once
|
||||
bridge_token() { # bridge kind(as|hs)
|
||||
local f=$SECRETS/${1}_${2}_token
|
||||
[ -f "$f" ] || openssl rand -hex 32 > "$f"
|
||||
cat "$f"
|
||||
}
|
||||
|
||||
# ── synapse ─────────────────────────────────────────────────────────
|
||||
if [ ! -f "$DATA/synapse/signing.key" ]; then
|
||||
echo "ed25519 a_$(openssl rand -hex 2) $(openssl rand -base64 32 | tr -d '=' | tr '+/' '-_')" > "$DATA/synapse/signing.key"
|
||||
log "generated synapse signing key"
|
||||
fi
|
||||
|
||||
cat > "$DATA/synapse/log.config" <<'EOF'
|
||||
version: 1
|
||||
formatters:
|
||||
precise:
|
||||
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
handlers:
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: precise
|
||||
loggers:
|
||||
synapse.storage.SQL:
|
||||
level: INFO
|
||||
root:
|
||||
level: INFO
|
||||
handlers: [console]
|
||||
disable_existing_loggers: false
|
||||
EOF
|
||||
|
||||
IFS=',' read -ra ENABLED <<< "${BRIDGES_ENABLED:-}"
|
||||
enabled() { local b; for b in "${ENABLED[@]}"; do [ "$b" = "$1" ] && return 0; done; return 1; }
|
||||
|
||||
# telegram needs API creds; drop it from the effective set if missing
|
||||
telegram_ok=true
|
||||
if enabled telegram && { [ -z "${TELEGRAM_API_ID:-}" ] || [ -z "${TELEGRAM_API_HASH:-}" ]; }; then
|
||||
telegram_ok=false
|
||||
log "WARNING: telegram enabled but TELEGRAM_API_ID/TELEGRAM_API_HASH empty - skipping telegram bridge"
|
||||
fi
|
||||
|
||||
export MATRIX_SERVER_NAME="$DOMAIN" POSTGRES_PASSWORD REGISTRATION_SHARED_SECRET="$REG_SECRET" \
|
||||
MACAROON_SECRET_KEY="$MACAROON" FORM_SECRET="$FORM"
|
||||
envsubst < /templates/synapse/homeserver.template.yaml > "$DATA/synapse/homeserver.yaml.new"
|
||||
|
||||
# ── double puppet appservice ────────────────────────────────────────
|
||||
DP_HS_TOKEN=$(bridge_token doublepuppet hs)
|
||||
cat > "$DATA/synapse/appservices/doublepuppet.yaml" <<EOF
|
||||
id: doublepuppet
|
||||
url:
|
||||
as_token: "$DP_SECRET"
|
||||
hs_token: "$DP_HS_TOKEN"
|
||||
sender_localpart: doublepuppet-sender
|
||||
rate_limited: false
|
||||
namespaces:
|
||||
users:
|
||||
- regex: '@.*:$(echo "$DOMAIN" | sed 's/\./\\./g')'
|
||||
exclusive: false
|
||||
EOF
|
||||
|
||||
echo "app_service_config_files:" >> "$DATA/synapse/homeserver.yaml.new"
|
||||
echo " - /data/appservices/doublepuppet.yaml" >> "$DATA/synapse/homeserver.yaml.new"
|
||||
|
||||
# ── bridges ─────────────────────────────────────────────────────────
|
||||
pg() { PGPASSWORD=$POSTGRES_PASSWORD psql -h postgres -U vortex -d postgres -qtAc "$1"; }
|
||||
|
||||
for b in $ALL_BRIDGES; do
|
||||
enabled "$b" || continue
|
||||
[ "$b" = telegram ] && [ "$telegram_ok" = false ] && continue
|
||||
|
||||
db="mautrix_$b"
|
||||
if [ "$(pg "SELECT 1 FROM pg_database WHERE datname='$db'")" != 1 ]; then
|
||||
pg "CREATE DATABASE $db OWNER vortex" >/dev/null
|
||||
log "created database $db"
|
||||
fi
|
||||
|
||||
mkdir -p "$DATA/bridges/$b"
|
||||
cfg=$DATA/bridges/$b/config.yaml
|
||||
AS_TOKEN=$(bridge_token "$b" as)
|
||||
HS_TOKEN=$(bridge_token "$b" hs)
|
||||
port=$(yq '.appservice.port' "/templates/bridges/$b.yaml")
|
||||
|
||||
if [ ! -f "$cfg" ]; then
|
||||
cp "/templates/bridges/$b.yaml" "$cfg"
|
||||
export B="$b" PORT="$port" AS_TOKEN HS_TOKEN DP_SECRET DOMAIN ADMIN_MXID \
|
||||
DB_URI="postgres://vortex:${POSTGRES_PASSWORD}@postgres/${db}?sslmode=disable"
|
||||
yq -i '
|
||||
.homeserver.address = "http://synapse:8008" |
|
||||
.homeserver.domain = strenv(DOMAIN) |
|
||||
.appservice.address = "http://" + strenv(B) + ":" + (strenv(PORT) | tostring) |
|
||||
.appservice.hostname = "0.0.0.0" |
|
||||
.appservice.as_token = strenv(AS_TOKEN) |
|
||||
.appservice.hs_token = strenv(HS_TOKEN)
|
||||
' "$cfg"
|
||||
if [ "$b" = discord ]; then # legacy config layout
|
||||
yq -i '
|
||||
.appservice.database.type = "postgres" |
|
||||
.appservice.database.uri = strenv(DB_URI) |
|
||||
.bridge.permissions = {"*": "relay", strenv(DOMAIN): "user", strenv(ADMIN_MXID): "admin"} |
|
||||
.bridge.login_shared_secret_map = {strenv(DOMAIN): ("as_token:" + strenv(DP_SECRET))}
|
||||
' "$cfg"
|
||||
else # bridgev2 layout
|
||||
yq -i '
|
||||
.database.type = "postgres" |
|
||||
.database.uri = strenv(DB_URI) |
|
||||
.bridge.permissions = {"*": "relay", strenv(DOMAIN): "user", strenv(ADMIN_MXID): "admin"} |
|
||||
.double_puppet.servers = {} |
|
||||
.double_puppet.secrets = {strenv(DOMAIN): ("as_token:" + strenv(DP_SECRET))} |
|
||||
.provisioning.shared_secret = "disable"
|
||||
' "$cfg"
|
||||
fi
|
||||
if [ "$b" = telegram ]; then
|
||||
yq -i '.network.api_id = (strenv(TELEGRAM_API_ID) | tonumber) | .network.api_hash = strenv(TELEGRAM_API_HASH)' "$cfg"
|
||||
fi
|
||||
if [ "$b" = meta ]; then
|
||||
yq -i '.network.mode = "instagram"' "$cfg"
|
||||
fi
|
||||
log "rendered $b config"
|
||||
fi
|
||||
|
||||
# registration: regenerated deterministically from persisted tokens.
|
||||
# Written to the bridge dir too, else the mautrix docker-run.sh entrypoint
|
||||
# regenerates it with fresh tokens and clobbers the ones in config.yaml.
|
||||
esc_domain=$(echo "$DOMAIN" | sed 's/\./\\./g')
|
||||
cat > "$DATA/bridges/$b/registration.yaml" <<EOF
|
||||
id: $b
|
||||
url: http://$b:$port
|
||||
as_token: "$AS_TOKEN"
|
||||
hs_token: "$HS_TOKEN"
|
||||
sender_localpart: ${b}-as-sender
|
||||
rate_limited: false
|
||||
de.sorunome.msc2409.push_ephemeral: true
|
||||
receive_ephemeral: true
|
||||
namespaces:
|
||||
users:
|
||||
- regex: '@${b}bot:${esc_domain}'
|
||||
exclusive: true
|
||||
- regex: '@${b}_.*:${esc_domain}'
|
||||
exclusive: true
|
||||
EOF
|
||||
cp "$DATA/bridges/$b/registration.yaml" "$DATA/synapse/appservices/$b.yaml"
|
||||
echo " - /data/appservices/$b.yaml" >> "$DATA/synapse/homeserver.yaml.new"
|
||||
log "bridge $b ready (port $port)"
|
||||
done
|
||||
|
||||
mv "$DATA/synapse/homeserver.yaml.new" "$DATA/synapse/homeserver.yaml"
|
||||
chown -R 991:991 "$DATA/synapse" # synapse image runs as uid 991
|
||||
log "done"
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# One-shot post-synapse provisioning: create admin user (idempotent) and
|
||||
# write /data/shared/credentials.json for the TUI and MCP server.
|
||||
set -euo pipefail
|
||||
|
||||
DATA=/data
|
||||
DOMAIN=${MATRIX_SERVER_NAME:-localhost}
|
||||
USER=${MATRIX_USER:-admin}
|
||||
PASS=${MATRIX_PASSWORD:?}
|
||||
MXID="@${USER}:${DOMAIN}"
|
||||
HS=http://synapse:8008
|
||||
REG_SECRET=$(cat $DATA/secrets/registration_shared_secret 2>/dev/null || true)
|
||||
|
||||
log() { echo "[provision] $*"; }
|
||||
|
||||
# already provisioned and token still valid? then no-op
|
||||
if [ -f "$DATA/shared/credentials.json" ]; then
|
||||
TOK=$(yq -p json '.access_token' "$DATA/shared/credentials.json")
|
||||
if curl -sf -H "Authorization: Bearer $TOK" "$HS/_matrix/client/v3/account/whoami" >/dev/null; then
|
||||
log "credentials already valid, nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
login() {
|
||||
curl -sf -X POST "$HS/_matrix/client/v3/login" \
|
||||
-d "{\"type\":\"m.login.password\",\"identifier\":{\"type\":\"m.id.user\",\"user\":\"$USER\"},\"password\":$(python3 -c "import json,sys;print(json.dumps(sys.argv[1]))" "$PASS"),\"initial_device_display_name\":\"provision\"}"
|
||||
}
|
||||
|
||||
RESP=$(login || true)
|
||||
if [ -z "$RESP" ]; then
|
||||
log "admin user missing, registering $MXID"
|
||||
NONCE=$(curl -sf "$HS/_synapse/admin/v1/register" | yq -p json '.nonce')
|
||||
MAC=$(printf '%s\0%s\0%s\0admin' "$NONCE" "$USER" "$PASS" | openssl dgst -sha1 -hmac "$REG_SECRET" | awk '{print $NF}')
|
||||
RESP=$(python3 - "$NONCE" "$USER" "$PASS" "$MAC" <<'EOF' | curl -sf -X POST "$HS/_synapse/admin/v1/register" -d @-
|
||||
import json, sys
|
||||
n, u, p, m = sys.argv[1:5]
|
||||
print(json.dumps({"nonce": n, "username": u, "password": p, "admin": True, "mac": m}))
|
||||
EOF
|
||||
)
|
||||
[ -n "$RESP" ] || { log "registration failed"; exit 1; }
|
||||
fi
|
||||
|
||||
TOKEN=$(echo "$RESP" | yq -p json '.access_token')
|
||||
DEVICE=$(echo "$RESP" | yq -p json '.device_id')
|
||||
|
||||
python3 - "$HS" "$MXID" "$PASS" "$TOKEN" "$DEVICE" <<'EOF' > $DATA/shared/credentials.json.new
|
||||
import json, sys
|
||||
hs, mxid, pw, tok, dev = sys.argv[1:6]
|
||||
print(json.dumps({"homeserver": hs, "user_id": mxid, "password": pw,
|
||||
"access_token": tok, "device_id": dev}, indent=2))
|
||||
EOF
|
||||
mv $DATA/shared/credentials.json.new $DATA/shared/credentials.json
|
||||
chmod a+rw $DATA/shared/credentials.json
|
||||
log "credentials written for $MXID"
|
||||
@@ -0,0 +1,381 @@
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
address: https://matrix.example.com
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's discord connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint: null
|
||||
# Endpoint for reporting per-message status.
|
||||
message_send_checkpoint_endpoint: null
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration.
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
address: http://localhost:29334
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
hostname: 0.0.0.0
|
||||
port: 29334
|
||||
|
||||
# Database config.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections. Mostly relevant for Postgres.
|
||||
max_open_conns: 20
|
||||
max_idle_conns: 2
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: discord
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: discordbot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: Discord bridge bot
|
||||
avatar: mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC
|
||||
|
||||
# Whether or not to receive ephemeral events via appservice transactions.
|
||||
# Requires MSC2409 support (i.e. Synapse 1.22+).
|
||||
ephemeral_events: true
|
||||
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Bridge config
|
||||
bridge:
|
||||
# Localpart template of MXIDs for Discord users.
|
||||
# {{.}} is replaced with the internal ID of the Discord user.
|
||||
username_template: discord_{{.}}
|
||||
# Displayname template for Discord users. This is also used as the room name in DMs if private_chat_portal_meta is enabled.
|
||||
# Available variables:
|
||||
# .ID - Internal user ID
|
||||
# .Username - Legacy display/username on Discord
|
||||
# .GlobalName - New displayname on Discord
|
||||
# .Discriminator - The 4 numbers after the name on Discord
|
||||
# .Bot - Whether the user is a bot
|
||||
# .System - Whether the user is an official system user
|
||||
# .Webhook - Whether the user is a webhook and is not an application
|
||||
# .Application - Whether the user is an application
|
||||
displayname_template: '{{if .Webhook}}Webhook{{else}}{{or .GlobalName .Username}}{{if .Bot}} (bot){{end}}{{end}}'
|
||||
# Displayname template for Discord channels (bridged as rooms, or spaces when type=4).
|
||||
# Available variables:
|
||||
# .Name - Channel name, or user displayname (pre-formatted with displayname_template) in DMs.
|
||||
# .ParentName - Parent channel name (used for categories).
|
||||
# .GuildName - Guild name.
|
||||
# .NSFW - Whether the channel is marked as NSFW.
|
||||
# .Type - Channel type (see values at https://github.com/bwmarrin/discordgo/blob/v0.25.0/structs.go#L251-L267)
|
||||
channel_name_template: '{{if or (eq .Type 3) (eq .Type 4)}}{{.Name}}{{else}}#{{.Name}}{{end}}'
|
||||
# Displayname template for Discord guilds (bridged as spaces).
|
||||
# Available variables:
|
||||
# .Name - Guild name
|
||||
guild_name_template: '{{.Name}}'
|
||||
# Whether to explicitly set the avatar and room name for private chat portal rooms.
|
||||
# If set to `default`, this will be enabled in encrypted rooms and disabled in unencrypted rooms.
|
||||
# If set to `always`, all DM rooms will have explicit names and avatars set.
|
||||
# If set to `never`, DM rooms will never have names and avatars set.
|
||||
private_chat_portal_meta: default
|
||||
|
||||
# Publicly accessible base URL that Discord can use to reach the bridge, used for avatars in relay mode.
|
||||
# If not set, avatars will not be bridged. Only the /mautrix-discord/avatar/{server}/{id}/{hash} endpoint is used on this address.
|
||||
# This should not have a trailing slash, the endpoint above will be appended to the provided address.
|
||||
public_address: null
|
||||
# A random key used to sign the avatar URLs. The bridge will only accept requests with a valid signature.
|
||||
avatar_proxy_key: generate
|
||||
|
||||
portal_message_buffer: 128
|
||||
|
||||
# Number of private channel portals to create on bridge startup.
|
||||
# Other portals will be created when receiving messages.
|
||||
startup_private_channel_create_limit: 5
|
||||
# Should the bridge send a read receipt from the bridge bot when a message has been sent to Discord?
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Should the bridge use space-restricted join rules instead of invite-only for guild rooms?
|
||||
# This can avoid unnecessary invite events in guild rooms when members are synced in.
|
||||
restricted_rooms: false
|
||||
# Should the bridge automatically join the user to threads on Discord when the thread is opened on Matrix?
|
||||
# This only works with clients that support thread read receipts (MSC3771 added in Matrix v1.4).
|
||||
autojoin_thread_on_open: true
|
||||
# Should inline fields in Discord embeds be bridged as HTML tables to Matrix?
|
||||
# Tables aren't supported in all clients, but are the only way to emulate the Discord inline field UI.
|
||||
embed_fields_as_tables: true
|
||||
# Should guild channels be muted when the portal is created? This only meant for single-user instances,
|
||||
# it won't mute it for all users if there are multiple Matrix users in the same Discord guild.
|
||||
mute_channels_on_create: false
|
||||
# Should the bridge update the m.direct account data event when double puppeting is enabled.
|
||||
# Note that updating the m.direct event is not atomic (except with mautrix-asmux)
|
||||
# and is therefore prone to race conditions.
|
||||
sync_direct_chat_list: false
|
||||
# Set this to true to tell the bridge to re-send m.bridge events to all rooms on the next run.
|
||||
# This field will automatically be changed back to false after it, except if the config file is not writable.
|
||||
resend_bridge_info: false
|
||||
# Should incoming custom emoji reactions be bridged as mxc:// URIs?
|
||||
# If set to false, custom emoji reactions will be bridged as the shortcode instead, and the image won't be available.
|
||||
custom_emoji_reactions: true
|
||||
# Should the bridge attempt to completely delete portal rooms when a channel is deleted on Discord?
|
||||
# If true, the bridge will try to kick Matrix users from the room. Otherwise, the bridge only makes ghosts leave.
|
||||
delete_portal_on_channel_delete: false
|
||||
# Should the bridge delete all portal rooms when you leave a guild on Discord?
|
||||
# This only applies if the guild has no other Matrix users on this bridge instance.
|
||||
delete_guild_on_leave: true
|
||||
# Whether or not created rooms should have federation enabled.
|
||||
# If false, created portal rooms will never be federated.
|
||||
federate_rooms: true
|
||||
# Prefix messages from webhooks with the profile info? This can be used along with a custom displayname_template
|
||||
# to better handle webhooks that change their name all the time (like ones used by bridges).
|
||||
#
|
||||
# This will use the fallback mode in MSC4144, which means clients that support MSC4144 will not show the prefix
|
||||
# (and will instead show the name and avatar as the message sender).
|
||||
prefix_webhook_messages: true
|
||||
# Bridge webhook avatars?
|
||||
enable_webhook_avatars: false
|
||||
# Should the bridge upload media to the Discord CDN directly before sending the message when using a user token,
|
||||
# like the official client does? The other option is sending the media in the message send request as a form part
|
||||
# (which is always used by bots and webhooks).
|
||||
use_discord_cdn_upload: true
|
||||
# Proxy for Discord connections
|
||||
proxy:
|
||||
# Should mxc uris copied from Discord be cached?
|
||||
# This can be `never` to never cache, `unencrypted` to only cache unencrypted mxc uris, or `always` to cache everything.
|
||||
# If you have a media repo that generates non-unique mxc uris, you should set this to never.
|
||||
cache_media: unencrypted
|
||||
# Settings for converting Discord media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# The bridge supports MSC3860 media download redirects and will use them if the requester supports it.
|
||||
# Optionally, you can force redirects and not allow proxying at all by setting this to false.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
# Settings for converting animated stickers.
|
||||
animated_sticker:
|
||||
# Format to which animated stickers should be converted.
|
||||
# disable - No conversion, send as-is (lottie JSON)
|
||||
# png - converts to non-animated png (fastest)
|
||||
# gif - converts to animated gif
|
||||
# webm - converts to webm video, requires ffmpeg executable with vp9 codec and webm container support
|
||||
# webp - converts to animated webp, requires ffmpeg executable with webp codec/container support
|
||||
target: webp
|
||||
# Arguments for converter. All converters take width and height.
|
||||
args:
|
||||
width: 320
|
||||
height: 320
|
||||
fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
|
||||
# Servers to always allow double puppeting from
|
||||
double_puppet_server_map:
|
||||
example.com: https://example.com
|
||||
# Allow using double puppeting from any server with a valid client .well-known file.
|
||||
double_puppet_allow_discovery: false
|
||||
# Shared secrets for https://github.com/devture/matrix-synapse-shared-secret-auth
|
||||
#
|
||||
# If set, double puppeting will be enabled automatically for local users
|
||||
# instead of users having to find an access token and run `login-matrix`
|
||||
# manually.
|
||||
login_shared_secret_map:
|
||||
example.com: foobar
|
||||
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!discord'
|
||||
# Messages sent upon joining a management room.
|
||||
# Markdown is supported. The defaults are listed below.
|
||||
management_room_text:
|
||||
# Sent when joining a room.
|
||||
welcome: "Hello, I'm a Discord bridge bot."
|
||||
# Sent when joining a management room and the user is already logged in.
|
||||
welcome_connected: "Use `help` for help."
|
||||
# Sent when joining a management room and the user is not logged in.
|
||||
welcome_unconnected: "Use `help` for help or `login` to log in."
|
||||
# Optional extra text sent when joining a management room.
|
||||
additional_help: ""
|
||||
|
||||
# Settings for backfilling messages.
|
||||
backfill:
|
||||
# Limits for forward backfilling.
|
||||
forward_limits:
|
||||
# Initial backfill (when creating portal). 0 means backfill is disabled.
|
||||
# A special unlimited value is not supported, you must set a limit. Initial backfill will
|
||||
# fetch all messages first before backfilling anything, so high limits can take a lot of time.
|
||||
initial:
|
||||
dm: 0
|
||||
channel: 0
|
||||
thread: 0
|
||||
# Missed message backfill (on startup).
|
||||
# 0 means backfill is disabled, -1 means fetch all messages since last bridged message.
|
||||
# When using unlimited backfill (-1), messages are backfilled as they are fetched.
|
||||
# With limits, all messages up to the limit are fetched first and backfilled afterwards.
|
||||
missed:
|
||||
dm: 0
|
||||
channel: 0
|
||||
thread: 0
|
||||
# Maximum members in a guild to enable backfilling. Set to -1 to disable limit.
|
||||
# This can be used as a rough heuristic to disable backfilling in channels that are too active.
|
||||
# Currently only applies to missed message backfill.
|
||||
max_guild_members: -1
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Allow encryption, work in group chat rooms with e2ee enabled
|
||||
allow: false
|
||||
# Default to encryption, force-enable encryption in all portals the bridge creates
|
||||
# This will cause the bridge bot to be in private chats for the encryption to work properly.
|
||||
default: false
|
||||
# Whether to use MSC2409/MSC3202 instead of /sync long polling for receiving encryption-related data.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
msc4190: false
|
||||
# Require encryption, drop any unencrypted messages.
|
||||
require: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: false
|
||||
# Should users mentions be in the event wire content to enable the server to send push notifications?
|
||||
plaintext_mentions: false
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from WhatsApp to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to
|
||||
# configure the m.room.encryption event content. See:
|
||||
# https://spec.matrix.org/v1.3/client-server-api/#mroomencryption for
|
||||
# more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is
|
||||
# set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Prefix for the provisioning API paths.
|
||||
prefix: /_matrix/provision
|
||||
# Shared secret for authentication. If set to "generate", a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled.
|
||||
shared_secret: generate
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# user - Access to use the bridge to chat with a Discord account.
|
||||
# admin - User level and some additional administration tools
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/mautrix-discord.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: true
|
||||
@@ -0,0 +1,534 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Displayname template for SMS users.
|
||||
# {{.FullName}} - Full name provided by the phone
|
||||
# {{.FirstName}} - First name provided by the phone
|
||||
# {{.PhoneNumber}} - Formatted phone number provided by the phone
|
||||
displayname_template: "{{or .FullName .PhoneNumber}}"
|
||||
# Settings for how the bridge appears to the phone.
|
||||
device_meta:
|
||||
# OS name to tell the phone. This is the name that shows up in the paired devices list.
|
||||
os: mautrix-gmessages
|
||||
# Browser type to tell the phone. This decides which icon is shown.
|
||||
# Valid types: OTHER, CHROME, FIREFOX, SAFARI, OPERA, IE, EDGE
|
||||
browser: OTHER
|
||||
# Device type to tell the phone. This also affects the icon, as well as how many sessions are allowed simultaneously.
|
||||
# One web, two tablets and one PWA should be able to connect at the same time.
|
||||
# Valid types: WEB, TABLET, PWA
|
||||
type: TABLET
|
||||
# Should the bridge aggressively set itself as the active device if the user opens Google Messages in a browser?
|
||||
# If this is disabled, the user must manually use the `set-active` command to reactivate the bridge.
|
||||
aggressive_reconnect: false
|
||||
# Number of chats to sync when connecting to Google Messages.
|
||||
initial_chat_sync_count: 25
|
||||
# Interval at which to ping the phone to check if it's still connected.
|
||||
ping_interval: 20m
|
||||
# How many ping timeouts should happen before a bridge state update is sent?
|
||||
# This only starts counting after a normal ping doesn't respond within a minute.
|
||||
# After that, new pings are sent with doubled timeouts (up to an hour).
|
||||
# The default value of 4 means 1 + 2 + 4 + 8 = 15 minutes of no response before a notice.
|
||||
alert_timeout_count: 4
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!gm'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29336
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29336
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: gmessages
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: gmessagesbot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: Google Messages bridge bot
|
||||
avatar: mxc://maunium.net/yGOdcrJcwqARZqdzbfuxfhzb
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: gmessages_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,520 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Displayname template for LinkedIn users.
|
||||
# .FirstName is replaced with the first name
|
||||
# .LastName is replaced with the last name
|
||||
# .Organization is replaced with the organization name
|
||||
displayname_template: "{{ with .Organization }}{{ . }}{{ else }}{{ .FirstName }} {{ .LastName }}{{ end }} (LinkedIn)"
|
||||
|
||||
sync:
|
||||
# Number of most recently active dialogs to check when syncing chats.
|
||||
# Set to 0 to remove limit.
|
||||
update_limit: 0
|
||||
# Number of most recently active dialogs to create portals for when syncing
|
||||
# chats.
|
||||
# Set to 0 to remove limit.
|
||||
create_limit: 10
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!linkedin'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29341
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29341
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: linkedin
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: linkedinbot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: LinkedIn bridge bot
|
||||
avatar: mxc://maunium.net/CqzBEHjrLsfdqixWZgNHMlRT
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: linkedin_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,581 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Which service is this bridge for? Available options:
|
||||
# * unset - allow users to pick any service when logging in (except facebook-tor)
|
||||
# * facebook - connect to FB Messenger via facebook.com
|
||||
# * facebook-tor - connect to FB Messenger via facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd.onion
|
||||
# * messenger - connect to FB Messenger via messenger.com (can be used with the facebook side deactivated)
|
||||
# * messenger-lite - connect to FB Messenger via Messenger iOS API
|
||||
# * instagram - connect to Instagram DMs via instagram.com
|
||||
#
|
||||
# Remember to change the appservice id, bot profile info, bridge username_template and management_room_text too.
|
||||
mode:
|
||||
# Should users be allowed to pick messenger.com login when mode is set to `facebook`?
|
||||
allow_messenger_com_on_fb: false
|
||||
# Explicit list of allowed login methods. Overrides other login method options if non-empty.
|
||||
allowed_modes: []
|
||||
|
||||
# Displayname template for FB/IG users. Available variables:
|
||||
# .DisplayName - The display name set by the user.
|
||||
# .Username - The username set by the user.
|
||||
# .ID - The internal user ID of the user.
|
||||
displayname_template: '{{or .DisplayName .Username "Unknown user"}}'
|
||||
|
||||
# Static proxy address (HTTP or SOCKS5) for connecting to Meta.
|
||||
proxy:
|
||||
# HTTP endpoint to request new proxy address from, for dynamically assigned proxies.
|
||||
# The endpoint must return a JSON body with a string field called proxy_url.
|
||||
get_proxy_from:
|
||||
# Should media be proxied too?
|
||||
proxy_media: false
|
||||
# Should E2EE messages be proxied too?
|
||||
proxy_e2ee: false
|
||||
# Should Messenger Lite login traffic be proxied?
|
||||
proxy_messenger_lite: true
|
||||
# Should other traffic, not configured here, be proxied?
|
||||
proxy_other: true
|
||||
# Minimum interval between full reconnects in seconds, default is 1 hour
|
||||
min_full_reconnect_interval_seconds: 3600
|
||||
# Interval to force refresh the connection (full reconnect), default is 20 hours. Set 0 to disable force refreshes.
|
||||
force_refresh_interval_seconds: 72000
|
||||
# Should connection state be cached to allow quicker restarts?
|
||||
cache_connection_state: false
|
||||
# Disable fetching XMA media (reels, stories, etc) when backfilling.
|
||||
disable_xma_backfill: true
|
||||
# Disable fetching XMA media entirely.
|
||||
disable_xma_always: false
|
||||
# Should the bridge mark you as online when you send typing
|
||||
# notifications? Currently, this only has an effect for E2EE chats on
|
||||
# Facebook/Messenger. Full presence bridging is not supported.
|
||||
send_presence_on_typing: false
|
||||
# Should the bridge, when operating in Instagram mode, subscribe to an
|
||||
# additional websocket to receive typing indicators from other users?
|
||||
# An additional connection is only needed for receiving Instagram
|
||||
# typing indicators; the existing connection(s) are sufficient to send
|
||||
# and receive typing indicators in all other cases.
|
||||
receive_instagram_typing_indicators: true
|
||||
# Should view-once messages be disabled entirely?
|
||||
disable_view_once: false
|
||||
# Should FB marketplace chats have a separate space inside the main Facebook space?
|
||||
marketplace_space: true
|
||||
# Log raw Bloks payloads even at debug level, with any user identifiers or credentials redacted.
|
||||
log_redacted_bloks_payloads: false
|
||||
|
||||
# Thread backfill settings for syncing older conversations
|
||||
thread_backfill:
|
||||
# Number of batches (pages) to backfill (-1 for unlimited, 0 to disable)
|
||||
batch_count: -1
|
||||
# Delay between fetching each batch of threads (to avoid rate limiting)
|
||||
batch_delay: 2s
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!meta'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
# When requests to the homeserver fail with a 502/503/504/429 status or a network error,
|
||||
# how many times should the bridge retry the request before giving up?
|
||||
retry_limit: 4
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29319
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29319
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: meta
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: metabot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: Meta bridge bot
|
||||
avatar: mxc://maunium.net/DxpVrwwzPUwaUSazpsjXgcKB
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: meta_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Should m.mentions be sent in the unencrypted content? This is non-standard and should not be enabled.
|
||||
plaintext_mentions: false
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# The variable names can also have a `_FILE` suffix to tell the bridge to read the value from the
|
||||
# path set in the variable rather than using the value directly.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,641 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Get your own API keys at https://my.telegram.org/apps
|
||||
api_id: 12345
|
||||
api_hash: tjyd5yge35lbodk1xwzw2jstp90k55qz
|
||||
|
||||
# Device info shown in the Telegram device list.
|
||||
device_info:
|
||||
device_model: mautrix-telegram
|
||||
system_version:
|
||||
app_version: auto
|
||||
lang_code: en
|
||||
system_lang_code: en
|
||||
|
||||
# Settings for converting animated stickers.
|
||||
animated_sticker:
|
||||
# Format to which animated stickers should be converted.
|
||||
#
|
||||
# disable - no conversion, send as-is (gzipped lottie)
|
||||
# png - converts to non-animated png (fastest),
|
||||
# gif - converts to animated gif
|
||||
# webm - converts to webm video, requires ffmpeg executable with vp9 codec
|
||||
# and webm container support
|
||||
# webp - converts to animated webp, requires ffmpeg executable with webp
|
||||
# codec/container support
|
||||
target: gif
|
||||
# Should video stickers be converted to the specified format as well?
|
||||
convert_from_webm: false
|
||||
# Arguments for converter. All converters take width and height.
|
||||
args:
|
||||
width: 256
|
||||
height: 256
|
||||
fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
|
||||
|
||||
# Settings for syncing the member list for portals.
|
||||
member_list:
|
||||
# Maximum number of members to sync per portal when starting up. Other
|
||||
# members will be synced when they send messages. The maximum is 10000,
|
||||
# after which the Telegram server will not send any more members.
|
||||
#
|
||||
# -1 means no limit (which means it's limited to 10000 by the server)
|
||||
max_initial_sync: 100
|
||||
# Whether or not to sync the member list in broadcast channels. If
|
||||
# disabled, members will still be synced when they send messages.
|
||||
#
|
||||
# If no channel admins have logged into the bridge, the bridge won't be
|
||||
# able to sync the member list regardless of this setting.
|
||||
sync_broadcast_channels: false
|
||||
# Whether or not to skip deleted members when syncing members.
|
||||
skip_deleted: true
|
||||
|
||||
# Settings for pings to the Telegram server.
|
||||
ping:
|
||||
# The interval (in seconds) between pings.
|
||||
interval_seconds: 30
|
||||
# The timeout (in seconds) for a single ping.
|
||||
timeout_seconds: 10
|
||||
|
||||
# Proxy settings
|
||||
proxy:
|
||||
# Allowed types: disabled, socks5, mtproxy
|
||||
type: disabled
|
||||
# Proxy IP address/domain name and port.
|
||||
address: "127.0.0.1:1080"
|
||||
# Proxy authentication (optional). Put MTProxy secret in password field.
|
||||
username:
|
||||
password:
|
||||
|
||||
sync:
|
||||
# Number of most recently active dialogs to check when syncing chats.
|
||||
# Set to -1 to remove limit.
|
||||
update_limit: 100
|
||||
# Number of most recently active dialogs to create portals for when syncing chats.
|
||||
# Set to -1 to remove limit.
|
||||
create_limit: 15
|
||||
# Number of chats to sync immediately on login before the data export is accepted.
|
||||
# The create_limit above still applies. This is ignored if takeout.dialog_sync is false.
|
||||
login_sync_limit: 15
|
||||
# Whether or not to sync and create portals for direct chats at startup.
|
||||
direct_chats: true
|
||||
|
||||
takeout:
|
||||
# Should the bridge use the data export mode for syncing the full chat list?
|
||||
# If true, login_sync_limit of chats is synced immediately on login,
|
||||
# then the rest are synced after the takeout is accepted.
|
||||
dialog_sync: false
|
||||
# Should the bridge use the data export mode for forward backfilling messages?
|
||||
# This should be set to true if the forward backfill limits are set to high values,
|
||||
# but is probably not necessary otherwise.
|
||||
forward_backfill: false
|
||||
# Should the bridge use the data export mode for backward backfilling messages?
|
||||
# This only affects the backfill queue, which is only available on Beeper.
|
||||
backward_backfill: false
|
||||
|
||||
# Maximum number of participants in chats to bridge. Only applies when the
|
||||
# portal is being created. If there are more members when trying to create a
|
||||
# room, the room creation will be cancelled.
|
||||
#
|
||||
# -1 means no limit (which means all chats can be bridged)
|
||||
max_member_count: -1
|
||||
# Should personal avatars (that are only visible to specific users) be allowed?
|
||||
contact_avatars: false
|
||||
# Should contact names be updated from any source even if a name is already set?
|
||||
# Note that contact names will still be used if there's no other name available.
|
||||
contact_names: false
|
||||
# Should the bridge send all unicode reactions as custom emoji reactions to
|
||||
# Telegram? By default, the bridge only uses custom emojis for unicode emojis
|
||||
# that aren't allowed in reactions.
|
||||
always_custom_emoji_reaction: false
|
||||
# The avatar to use for the Telegram Saved Messages chat
|
||||
saved_message_avatar: mxc://maunium.net/XhhfHoPejeneOngMyBbtyWDk
|
||||
# Create a new room and tombstone the old one when upgrading rooms
|
||||
always_tombstone_on_supergroup_migration: false
|
||||
# Maximum number of pixels in an image before sending to Telegram as a
|
||||
# document. Defaults to 4096x4096 = 16777216.
|
||||
image_as_file_pixels: 16777216
|
||||
# Should view-once messages be disabled entirely?
|
||||
disable_view_once: false
|
||||
# Should video URL previews be bridged as m.video messages to Matrix?
|
||||
# By default, video URL previews will not be bridged.
|
||||
video_url_preview_as_file: false
|
||||
# Displayname template for Telegram users.
|
||||
# {{ .FullName }} - the full name of the Telegram user
|
||||
# {{ .FirstName }} - the first name of the Telegram user
|
||||
# {{ .LastName }} - the last name of the Telegram user
|
||||
# {{ .Username }} - the primary username of the Telegram user, if the user has one
|
||||
# {{ .UserID }} - the internal user ID of the Telegram user
|
||||
# {{ .Deleted }} - true if the user has been deleted, false otherwise
|
||||
displayname_template: "{{ if .Deleted }}Deleted account {{ .UserID }}{{ else }}{{ .FullName }}{{ end }}"
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!tg'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
# When requests to the homeserver fail with a 502/503/504/429 status or a network error,
|
||||
# how many times should the bridge retry the request before giving up?
|
||||
retry_limit: 4
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29317
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29317
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: telegram
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: telegrambot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: Telegram bridge bot
|
||||
avatar: mxc://maunium.net/tJCRmUyJDsgRNgqhOgoiHWbX
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: telegram_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Should m.mentions be sent in the unencrypted content? This is non-standard and should not be enabled.
|
||||
plaintext_mentions: false
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# The variable names can also have a `_FILE` suffix to tell the bridge to read the value from the
|
||||
# path set in the variable rather than using the value directly.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,533 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Proxy to use for all Twitter connections.
|
||||
proxy: null
|
||||
# Alternative to proxy: an HTTP endpoint that returns the proxy URL to use for Twitter connections.
|
||||
get_proxy_url: null
|
||||
|
||||
# Displayname template for Twitter users.
|
||||
# {{ .DisplayName }} is replaced with the display name of the Twitter user.
|
||||
# {{ .Username }} is replaced with the username of the Twitter user.
|
||||
displayname_template: "{{ .DisplayName }} (Twitter)"
|
||||
|
||||
# Maximum number of conversations to sync on startup
|
||||
conversation_sync_limit: 20
|
||||
|
||||
# Should the bridge cache sessions instead of resyncing chats on every restart?
|
||||
cache_session: true
|
||||
|
||||
# Should the bridge use "X" instead of "Twitter" in certain places,
|
||||
# such as the management room welcome message and MSC2346 bridge info?
|
||||
x: false
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!twitter'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
# When requests to the homeserver fail with a 502/503/504/429 status or a network error,
|
||||
# how many times should the bridge retry the request before giving up?
|
||||
retry_limit: 4
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29327
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29327
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: twitter
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: twitterbot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: Twitter bridge bot
|
||||
avatar: mxc://maunium.net/HVHcnusJkQcpVcsVGZRELLCn
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: twitter_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Should m.mentions be sent in the unencrypted content? This is non-standard and should not be enabled.
|
||||
plaintext_mentions: false
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# The variable names can also have a `_FILE` suffix to tell the bridge to read the value from the
|
||||
# path set in the variable rather than using the value directly.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,640 @@
|
||||
# Network-specific config options
|
||||
network:
|
||||
# Device name that's shown in the "WhatsApp Web" section in the mobile app.
|
||||
os_name: Mautrix-WhatsApp bridge
|
||||
# Browser name that determines the logo shown in the mobile app.
|
||||
# Must be "unknown" for a generic icon or a valid browser name if you want a specific icon.
|
||||
# List of valid browser names: https://github.com/tulir/whatsmeow/blob/efc632c008604016ddde63bfcfca8de4e5304da9/binary/proto/def.proto#L43-L64
|
||||
browser_name: unknown
|
||||
|
||||
# Proxy to use for all WhatsApp connections.
|
||||
proxy: null
|
||||
# Alternative to proxy: an HTTP endpoint that returns the proxy URL to use for WhatsApp connections.
|
||||
get_proxy_url: null
|
||||
# Whether the proxy options should only apply to the login websocket and not to authenticated connections.
|
||||
proxy_only_login: false
|
||||
|
||||
# Displayname template for WhatsApp users.
|
||||
# {{.PushName}} - nickname set by the WhatsApp user
|
||||
# {{.BusinessName}} - validated WhatsApp business name
|
||||
# {{.Phone}} - phone number (international format)
|
||||
# {{.RedactedPhone}} - phone number with middle digits replaced by "∙"
|
||||
# {{.FullName}} - Name you set in the contacts list
|
||||
displayname_template: '{{or .BusinessName .PushName .Phone .RedactedPhone "Unknown user"}} (WA)'
|
||||
|
||||
# Should incoming calls send a message to the Matrix room?
|
||||
call_start_notices: true
|
||||
# Should another user's cryptographic identity changing send a message to Matrix?
|
||||
identity_change_notices: false
|
||||
# Should the bridge mark you as online on WhatsApp when you send typing notifications?
|
||||
# Full presence bridging is not supported.
|
||||
send_presence_on_typing: false
|
||||
# Should WhatsApp status messages be bridged into a Matrix room?
|
||||
enable_status_broadcast: true
|
||||
# Should sending WhatsApp status messages be allowed?
|
||||
# This can cause issues if the user has lots of contacts, so it's disabled by default.
|
||||
disable_status_broadcast_send: true
|
||||
# Should the status broadcast room be muted and moved into low priority by default?
|
||||
# This is only applied when creating the room, the user can unmute it later.
|
||||
mute_status_broadcast: true
|
||||
# Tag to apply to pinned chats on WhatsApp.
|
||||
pinned_tag: m.favourite
|
||||
# Tag to apply to archived chats on WhatsApp.
|
||||
# Set to m.lowpriority to move them to low priority.
|
||||
archive_tag:
|
||||
# Tag to apply to the status broadcast room.
|
||||
status_broadcast_tag: m.lowpriority
|
||||
# Should the bridge use thumbnails from WhatsApp?
|
||||
# They're disabled by default due to very low resolution.
|
||||
whatsapp_thumbnail: false
|
||||
# Should the bridge detect URLs in outgoing messages, ask the homeserver to generate a preview,
|
||||
# and send it to WhatsApp? URL previews can always be sent using the `com.beeper.linkpreviews`
|
||||
# key in the event content even if this is disabled.
|
||||
url_previews: false
|
||||
# Should polls be sent using unstable MSC3381 event types?
|
||||
extev_polls: false
|
||||
# Should view-once messages be disabled entirely?
|
||||
disable_view_once: false
|
||||
# Should the bridge always send "active" delivery receipts (two gray ticks on WhatsApp)
|
||||
# even if the user isn't marked as online (e.g. when presence bridging isn't enabled)?
|
||||
#
|
||||
# By default, the bridge acts like WhatsApp web, which only sends active delivery
|
||||
# receipts when it's in the foreground.
|
||||
force_active_delivery_receipts: false
|
||||
# When direct media is enabled and a piece of media isn't available on the WhatsApp servers,
|
||||
# should it be automatically requested from the phone?
|
||||
direct_media_auto_request: true
|
||||
# Should the bridge automatically reconnect if it fails to connect on startup?
|
||||
initial_auto_reconnect: true
|
||||
# WhatsApp messages are sometimes undecryptable. Should the bridge store messages it sends in the
|
||||
# bridge database in order to accept retry receipts from other WhatsApp users for messages sent via
|
||||
# the bridge? By default, the bridge only stores messages in memory, and therefore can't accept
|
||||
# retry receipts if the bridge is restarted after the message is sent.
|
||||
use_whatsapp_retry_store: false
|
||||
|
||||
# Settings for converting animated stickers.
|
||||
animated_sticker:
|
||||
# Format to which animated stickers should be converted.
|
||||
# disable - No conversion, just unzip and send raw lottie JSON
|
||||
# png - converts to non-animated png (fastest)
|
||||
# gif - converts to animated gif
|
||||
# webm - converts to webm video, requires ffmpeg executable with vp9 codec and webm container support
|
||||
# webp - converts to animated webp, requires ffmpeg executable with webp codec/container support
|
||||
target: webp
|
||||
# Arguments for converter. All converters take width and height.
|
||||
args:
|
||||
width: 320
|
||||
height: 320
|
||||
fps: 25 # only for webm, webp and gif (2, 5, 10, 20 or 25 recommended)
|
||||
|
||||
# Settings for handling history sync payloads.
|
||||
history_sync:
|
||||
# How many conversations should the bridge create after login?
|
||||
# If -1, all conversations received from history sync will be bridged.
|
||||
# Other conversations will be backfilled on demand when receiving a message.
|
||||
max_initial_conversations: -1
|
||||
# Should the bridge request a full sync from the phone when logging in?
|
||||
# This bumps the size of history syncs from 3 months to 1 year.
|
||||
request_full_sync: false
|
||||
# Time to wait for history sync payloads before starting backfill. Each new payload resets the timer.
|
||||
# If this is too low, the backfill may happen with incomplete history
|
||||
# and backfill less messages than what is configured in the backfill section.
|
||||
dispatch_wait: 1m
|
||||
# Configuration parameters that are sent to the phone along with the request full sync flag.
|
||||
# By default, (when the values are null or 0), the config isn't sent at all.
|
||||
full_sync_config:
|
||||
# Number of days of history to request.
|
||||
# The limit seems to be around 3 years, but using higher values doesn't break.
|
||||
days_limit: null
|
||||
# This is presumably the maximum size of the transferred history sync blob, which may affect what the phone includes in the blob.
|
||||
size_mb_limit: null
|
||||
# This is presumably the local storage quota, which may affect what the phone includes in the history sync blob.
|
||||
storage_quota_mb: null
|
||||
# Settings for media requests. If the media expired, then it will not be on the WA servers.
|
||||
# Media can always be requested by reacting with the ♻️ (recycle) emoji.
|
||||
# These settings determine if the media requests should be done automatically during or after backfill.
|
||||
media_requests:
|
||||
# Should the expired media be automatically requested from the server as part of the backfill process?
|
||||
auto_request_media: true
|
||||
# Whether to request the media immediately after the media message is backfilled ("immediate")
|
||||
# or at a specific time of the day ("local_time").
|
||||
request_method: immediate
|
||||
# If request_method is "local_time", what time should the requests be sent (in minutes after midnight)?
|
||||
request_local_time: 120
|
||||
# Maximum number of media request responses to handle in parallel per user.
|
||||
max_async_handle: 2
|
||||
# Use on-demand history sync requests for fetching older messages?
|
||||
# This only applies when using the backfill queue, never for forward backfills.
|
||||
backwards_on_demand: false
|
||||
|
||||
|
||||
# Config options that affect the central bridge module.
|
||||
bridge:
|
||||
# The prefix for commands. Only required in non-management rooms.
|
||||
command_prefix: '!wa'
|
||||
# Should the bridge create a space for each login containing the rooms that account is in?
|
||||
personal_filtering_spaces: true
|
||||
# Whether the bridge should set names and avatars explicitly for DM portals.
|
||||
# This is only necessary when using clients that don't support MSC4171.
|
||||
private_chat_portal_meta: true
|
||||
# Should events be handled asynchronously within portal rooms?
|
||||
# If true, events may end up being out of order, but slow events won't block other ones.
|
||||
# This is not yet safe to use.
|
||||
async_events: false
|
||||
# Should every user have their own portals rather than sharing them?
|
||||
# By default, users who are in the same group on the remote network will be
|
||||
# in the same Matrix room bridged to that group. If this is set to true,
|
||||
# every user will get their own Matrix room instead.
|
||||
# SETTING THIS IS IRREVERSIBLE AND POTENTIALLY DESTRUCTIVE IF PORTALS ALREADY EXIST.
|
||||
split_portals: false
|
||||
# Should the bridge resend `m.bridge` events to all portals on startup?
|
||||
resend_bridge_info: false
|
||||
# Should `m.bridge` events be sent without a state key?
|
||||
# By default, the bridge uses a unique key that won't conflict with other bridges.
|
||||
no_bridge_info_state_key: false
|
||||
# Should bridge connection status be sent to the management room as `m.notice` events?
|
||||
# These contain the same data that can be posted to an external HTTP server using homeserver -> status_endpoint.
|
||||
# Allowed values: none, errors, all
|
||||
bridge_status_notices: errors
|
||||
# How long after an unknown error should the bridge attempt a full reconnect?
|
||||
# Must be at least 1 minute. The bridge will add an extra ±20% jitter to this value.
|
||||
unknown_error_auto_reconnect: null
|
||||
# Maximum number of times to do the auto-reconnect above.
|
||||
# The counter is per login, but is never reset except on logout and restart.
|
||||
unknown_error_max_auto_reconnects: 10
|
||||
|
||||
# Should leaving Matrix rooms be bridged as leaving groups on the remote network?
|
||||
bridge_matrix_leave: false
|
||||
# Should `m.notice` messages be bridged?
|
||||
bridge_notices: false
|
||||
# Should room tags only be synced when creating the portal? Tags mean things like favorite/pin and archive/low priority.
|
||||
# Tags currently can't be synced back to the remote network, so a continuous sync means tagging from Matrix will be undone.
|
||||
tag_only_on_create: true
|
||||
# List of tags to allow bridging. If empty, no tags will be bridged.
|
||||
only_bridge_tags: [m.favourite, m.lowpriority]
|
||||
# Should room mute status only be synced when creating the portal?
|
||||
# Like tags, mutes can't currently be synced back to the remote network.
|
||||
mute_only_on_create: true
|
||||
# Should the bridge check the db to ensure that incoming events haven't been handled before
|
||||
deduplicate_matrix_messages: false
|
||||
# Should cross-room reply metadata be bridged?
|
||||
# Most Matrix clients don't support this and servers may reject such messages too.
|
||||
cross_room_replies: false
|
||||
# If a state event fails to bridge, should the bridge revert any state changes made by that event?
|
||||
revert_failed_state_changes: false
|
||||
# In portals with no relay set, should Matrix users be kicked if they're
|
||||
# not logged into an account that's in the remote chat?
|
||||
kick_matrix_users: true
|
||||
# Should the bridge listen to com.beeper.state_request events?
|
||||
# This is not necessary for anything outside of Beeper.
|
||||
enable_send_state_requests: false
|
||||
# Should the com.beeper.bridge.identifiers list in global ghost profiles include phone numbers?
|
||||
phone_numbers_in_profile: false
|
||||
|
||||
# What should be done to portal rooms when a user logs out or is logged out?
|
||||
# Permitted values:
|
||||
# nothing - Do nothing, let the user stay in the portals
|
||||
# kick - Remove the user from the portal rooms, but don't delete them
|
||||
# unbridge - Remove all ghosts in the room and disassociate it from the remote chat
|
||||
# delete - Remove all ghosts and users from the room (i.e. delete it)
|
||||
cleanup_on_logout:
|
||||
# Should cleanup on logout be enabled at all?
|
||||
enabled: false
|
||||
# Settings for manual logouts (explicitly initiated by the Matrix user)
|
||||
manual:
|
||||
# Action for private portals which will never be shared with other Matrix users.
|
||||
private: nothing
|
||||
# Action for portals with a relay user configured.
|
||||
relayed: nothing
|
||||
# Action for portals which may be shared, but don't currently have any other Matrix users.
|
||||
shared_no_users: nothing
|
||||
# Action for portals which have other logged-in Matrix users.
|
||||
shared_has_users: nothing
|
||||
# Settings for credentials being invalidated (initiated by the remote network, possibly through user action).
|
||||
# Keys have the same meanings as in the manual section.
|
||||
bad_credentials:
|
||||
private: nothing
|
||||
relayed: nothing
|
||||
shared_no_users: nothing
|
||||
shared_has_users: nothing
|
||||
|
||||
# Settings for relay mode
|
||||
relay:
|
||||
# Whether relay mode should be allowed. If allowed, the set-relay command can be used to turn any
|
||||
# authenticated user into a relaybot for that chat.
|
||||
enabled: false
|
||||
# Should only admins be allowed to set themselves as relay users?
|
||||
# If true, non-admins can only set users listed in default_relays as relays in a room.
|
||||
admin_only: true
|
||||
# Should default relays be preferred when an explicit login ID isn't specified even if the user is logged in?
|
||||
# This applies to the set-relay and bridge commands sent by any user, including admins.
|
||||
prefer_default: true
|
||||
# Should non-admins be allowed to use the bridge and sync-chat commands via default relays specified below?
|
||||
allow_bridge: true
|
||||
# List of user login IDs which anyone can set as a relay, as long as the relay user is in the room.
|
||||
default_relays: []
|
||||
# The formats to use when sending messages via the relaybot.
|
||||
# Available variables:
|
||||
# .Sender.UserID - The Matrix user ID of the sender.
|
||||
# .Sender.Displayname - The display name of the sender (if set).
|
||||
# .Sender.RequiresDisambiguation - Whether the sender's name may be confused with the name of another user in the room.
|
||||
# .Sender.DisambiguatedName - The disambiguated name of the sender. This will be the displayname if set,
|
||||
# plus the user ID in parentheses if the displayname is not unique.
|
||||
# If the displayname is not set, this is just the user ID.
|
||||
# .Message - The `formatted_body` field of the message.
|
||||
# .Caption - The `formatted_body` field of the message, if it's a caption. Otherwise an empty string.
|
||||
# .FileName - The name of the file being sent.
|
||||
message_formats:
|
||||
m.text: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.notice: "<b>{{ .Sender.DisambiguatedName }}</b>: {{ .Message }}"
|
||||
m.emote: "* <b>{{ .Sender.DisambiguatedName }}</b> {{ .Message }}"
|
||||
m.file: "<b>{{ .Sender.DisambiguatedName }}</b> sent a file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.image: "<b>{{ .Sender.DisambiguatedName }}</b> sent an image{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.audio: "<b>{{ .Sender.DisambiguatedName }}</b> sent an audio file{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.video: "<b>{{ .Sender.DisambiguatedName }}</b> sent a video{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
m.location: "<b>{{ .Sender.DisambiguatedName }}</b> sent a location{{ if .Caption }}: {{ .Caption }}{{ end }}"
|
||||
# For networks that support per-message displaynames (i.e. Slack and Discord), the template for those names.
|
||||
# This has all the Sender variables available under message_formats (but without the .Sender prefix).
|
||||
# Note that you need to manually remove the displayname from message_formats above.
|
||||
displayname_format: "{{ .DisambiguatedName }}"
|
||||
|
||||
# Filter for automatically creating portals.
|
||||
portal_create_filter:
|
||||
# The mode for filtering, either `deny` or `allow`
|
||||
mode: deny
|
||||
# The list of portal IDs to deny or allow depending on the mode config.
|
||||
# Items here can either be the plain portal ID as a string, or an object with `id` and `receiver` fields.
|
||||
# The receiver field is necessary if you want to target a specific DM portal for example.
|
||||
list: []
|
||||
# A list of user login IDs from which to always deny creating portals.
|
||||
# This is meant to be used with default relays, such that the relay bot
|
||||
# being added to a group wouldn't automatically trigger portal creation.
|
||||
always_deny_from_login: []
|
||||
|
||||
# Permissions for using the bridge.
|
||||
# Permitted values:
|
||||
# relay - Talk through the relaybot (if enabled), no access otherwise
|
||||
# commands - Access to use commands in the bridge, but not login.
|
||||
# user - Access to use the bridge with puppeting.
|
||||
# admin - Full access, user level with some additional administration tools.
|
||||
# Permitted keys:
|
||||
# * - All Matrix users
|
||||
# domain - All users on that homeserver
|
||||
# mxid - Specific user
|
||||
permissions:
|
||||
"*": relay
|
||||
"example.com": user
|
||||
"@admin:example.com": admin
|
||||
|
||||
# Config for the bridge's database.
|
||||
database:
|
||||
# The database type. "sqlite3-fk-wal" and "postgres" are supported.
|
||||
type: postgres
|
||||
# The database URI.
|
||||
# SQLite: A raw file path is supported, but `file:<path>?_txlock=immediate` is recommended.
|
||||
# https://github.com/mattn/go-sqlite3#connection-string
|
||||
# Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable
|
||||
# To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql
|
||||
uri: postgres://user:password@host/database?sslmode=disable
|
||||
# Maximum number of connections.
|
||||
max_open_conns: 5
|
||||
max_idle_conns: 1
|
||||
# Maximum connection idle time and lifetime before they're closed. Disabled if null.
|
||||
# Parsed with https://pkg.go.dev/time#ParseDuration
|
||||
max_conn_idle_time: null
|
||||
max_conn_lifetime: null
|
||||
|
||||
# Homeserver details.
|
||||
homeserver:
|
||||
# The address that this appservice can use to connect to the homeserver.
|
||||
# Local addresses without HTTPS are generally recommended when the bridge is running on the same machine,
|
||||
# but https also works if they run on different machines.
|
||||
address: http://example.localhost:8008
|
||||
# The domain of the homeserver (also known as server_name, used for MXIDs, etc).
|
||||
domain: example.com
|
||||
|
||||
# What software is the homeserver running?
|
||||
# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.
|
||||
software: standard
|
||||
# The URL to push real-time bridge status to.
|
||||
# If set, the bridge will make POST requests to this URL whenever a user's remote network connection state changes.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
status_endpoint:
|
||||
# Endpoint for reporting per-message status.
|
||||
# If set, the bridge will make POST requests to this URL when processing a message from Matrix.
|
||||
# It will make one request when receiving the message (step BRIDGE), one after decrypting if applicable
|
||||
# (step DECRYPTED) and one after sending to the remote network (step REMOTE). Errors will also be reported.
|
||||
# The bridge will use the appservice as_token to authorize requests.
|
||||
message_send_checkpoint_endpoint:
|
||||
# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?
|
||||
async_media: false
|
||||
|
||||
# Should the bridge use a websocket for connecting to the homeserver?
|
||||
# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,
|
||||
# mautrix-asmux (deprecated), and hungryserv (proprietary).
|
||||
websocket: false
|
||||
# How often should the websocket be pinged? Pinging will be disabled if this is zero.
|
||||
ping_interval_seconds: 0
|
||||
# When requests to the homeserver fail with a 502/503/504/429 status or a network error,
|
||||
# how many times should the bridge retry the request before giving up?
|
||||
retry_limit: 4
|
||||
|
||||
# Application service host/registration related details.
|
||||
# Changing these values requires regeneration of the registration (except when noted otherwise)
|
||||
appservice:
|
||||
# The address that the homeserver can use to connect to this appservice.
|
||||
# Like the homeserver address, a local non-https address is recommended when the bridge is on the same machine.
|
||||
# If the bridge is elsewhere, you must secure the connection yourself (e.g. with https or wireguard)
|
||||
# If you want to use https, you need to use a reverse proxy. The bridge does not have TLS support built in.
|
||||
address: http://localhost:29318
|
||||
# A public address that external services can use to reach this appservice.
|
||||
# This is only needed for things like public media. A reverse proxy is generally necessary when using this field.
|
||||
# This value doesn't affect the registration file.
|
||||
public_address: https://bridge.example.com
|
||||
|
||||
# The hostname and port where this appservice should listen.
|
||||
# For Docker, you generally have to change the hostname to 0.0.0.0.
|
||||
hostname: 127.0.0.1
|
||||
port: 29318
|
||||
|
||||
# The unique ID of this appservice.
|
||||
id: whatsapp
|
||||
# Appservice bot details.
|
||||
bot:
|
||||
# Username of the appservice bot.
|
||||
username: whatsappbot
|
||||
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
|
||||
# to leave display name/avatar as-is.
|
||||
displayname: WhatsApp bridge bot
|
||||
avatar: mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr
|
||||
|
||||
# Whether to receive ephemeral events via appservice transactions.
|
||||
ephemeral_events: true
|
||||
# Should incoming events be handled asynchronously?
|
||||
# This may be necessary for large public instances with lots of messages going through.
|
||||
# However, messages will not be guaranteed to be bridged in the same order they were sent in.
|
||||
# This value doesn't affect the registration file.
|
||||
async_transactions: false
|
||||
|
||||
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
|
||||
as_token: "This value is generated when generating the registration"
|
||||
hs_token: "This value is generated when generating the registration"
|
||||
|
||||
# Localpart template of MXIDs for remote users.
|
||||
# {{.}} is replaced with the internal ID of the user.
|
||||
username_template: whatsapp_{{.}}
|
||||
|
||||
# Config options that affect the Matrix connector of the bridge.
|
||||
matrix:
|
||||
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
|
||||
message_status_events: false
|
||||
# Whether the bridge should send a read receipt after successfully bridging a message.
|
||||
delivery_receipts: false
|
||||
# Whether the bridge should send error notices via m.notice events when a message fails to bridge.
|
||||
message_error_notices: true
|
||||
# Whether the bridge should update the m.direct account data event when double puppeting is enabled.
|
||||
sync_direct_chat_list: true
|
||||
# Whether created rooms should have federation enabled. If false, created portal rooms
|
||||
# will never be federated. Changing this option requires recreating rooms.
|
||||
federate_rooms: true
|
||||
# The threshold as bytes after which the bridge should roundtrip uploads via the disk
|
||||
# rather than keeping the whole file in memory.
|
||||
upload_file_threshold: 5242880
|
||||
# Should the bridge set additional custom profile info for ghosts?
|
||||
# This can make a lot of requests, as there's no batch profile update endpoint.
|
||||
ghost_extra_profile_info: false
|
||||
|
||||
# Segment-compatible analytics endpoint for tracking some events, like provisioning API login and encryption errors.
|
||||
analytics:
|
||||
# API key to send with tracking requests. Tracking is disabled if this is null.
|
||||
token: null
|
||||
# Address to send tracking requests to.
|
||||
url: https://api.segment.io/v1/track
|
||||
# Optional user ID for tracking events. If null, defaults to using Matrix user ID.
|
||||
user_id: null
|
||||
|
||||
# Settings for provisioning API
|
||||
provisioning:
|
||||
# Shared secret for authentication. If set to "generate" or null, a random secret will be generated,
|
||||
# or if set to "disable", the provisioning API will be disabled. Must be at least 16 characters.
|
||||
shared_secret: generate
|
||||
# Whether to allow provisioning API requests to be authed using Matrix access tokens.
|
||||
# This follows the same rules as double puppeting to determine which server to contact to check the token,
|
||||
# which means that by default, it only works for users on the same server as the bridge.
|
||||
allow_matrix_auth: true
|
||||
# Enable debug API at /debug with provisioning authentication.
|
||||
debug_endpoints: false
|
||||
# Enable session transfers between bridges. Note that this only validates Matrix or shared secret
|
||||
# auth before passing live network client credentials down in the response.
|
||||
enable_session_transfers: false
|
||||
|
||||
# Some networks require publicly accessible media download links (e.g. for user avatars when using Discord webhooks).
|
||||
# These settings control whether the bridge will provide such public media access.
|
||||
public_media:
|
||||
# Should public media be enabled at all?
|
||||
# The public_address field under the appservice section MUST be set when enabling public media.
|
||||
enabled: false
|
||||
# A key for signing public media URLs.
|
||||
# If set to "generate", a random key will be generated.
|
||||
signing_key: generate
|
||||
# Number of seconds that public media URLs are valid for.
|
||||
# If set to 0, URLs will never expire.
|
||||
expiry: 0
|
||||
# Length of hash to use for public media URLs. Must be between 0 and 32.
|
||||
hash_length: 32
|
||||
# The path prefix for generated URLs. Note that this will NOT change the path where media is actually served.
|
||||
# If you change this, you must configure your reverse proxy to rewrite the path accordingly.
|
||||
path_prefix: /_mautrix/publicmedia
|
||||
# Should the bridge store media metadata in the database in order to support encrypted media and generate shorter URLs?
|
||||
# If false, the generated URLs will just have the MXC URI and a HMAC signature.
|
||||
# The hash_length field will be used to decide the length of the generated URL.
|
||||
# This also allows invalidating URLs by deleting the database entry.
|
||||
use_database: false
|
||||
|
||||
# Settings for converting remote media to custom mxc:// URIs instead of reuploading.
|
||||
# More details can be found at https://docs.mau.fi/bridges/go/discord/direct-media.html
|
||||
direct_media:
|
||||
# Should custom mxc:// URIs be used instead of reuploading media?
|
||||
enabled: false
|
||||
# The server name to use for the custom mxc:// URIs.
|
||||
# This server name will effectively be a real Matrix server, it just won't implement anything other than media.
|
||||
# You must either set up .well-known delegation from this domain to the bridge, or proxy the domain directly to the bridge.
|
||||
server_name: discord-media.example.com
|
||||
# Optionally a custom .well-known response. This defaults to `server_name:443`
|
||||
well_known_response:
|
||||
# Optionally specify a custom prefix for the media ID part of the MXC URI.
|
||||
media_id_prefix:
|
||||
# If the remote network supports media downloads over HTTP, then the bridge will use MSC3860/MSC3916
|
||||
# media download redirects if the requester supports it. Optionally, you can force redirects
|
||||
# and not allow proxying at all by setting this to false.
|
||||
# This option does nothing if the remote network does not support media downloads over HTTP.
|
||||
allow_proxy: true
|
||||
# Matrix server signing key to make the federation tester pass, same format as synapse's .signing.key file.
|
||||
# This key is also used to sign the mxc:// URIs to ensure only the bridge can generate them.
|
||||
server_key: generate
|
||||
|
||||
# Settings for backfilling messages.
|
||||
# Note that the exact way settings are applied depends on the network connector.
|
||||
# See https://docs.mau.fi/bridges/general/backfill.html for more details.
|
||||
backfill:
|
||||
# Whether to do backfilling at all.
|
||||
enabled: false
|
||||
# Maximum number of messages to backfill in empty rooms.
|
||||
# If this is zero or negative, backfill will be disabled in new rooms.
|
||||
max_initial_messages: 50
|
||||
# Maximum number of missed messages to backfill after bridge restarts.
|
||||
max_catchup_messages: 500
|
||||
# If a backfilled chat is older than this number of hours,
|
||||
# mark it as read even if it's unread on the remote network.
|
||||
unread_hours_threshold: 720
|
||||
# Settings for backfilling threads within other backfills.
|
||||
threads:
|
||||
# Maximum number of messages to backfill in a new thread.
|
||||
max_initial_messages: 50
|
||||
# Settings for the backwards backfill queue. This only applies when connecting to
|
||||
# Beeper as standard Matrix servers don't support inserting messages into history.
|
||||
queue:
|
||||
# Should the backfill queue be enabled?
|
||||
enabled: false
|
||||
# Should manual calls to backfill queue tasks be allowed?
|
||||
manual: false
|
||||
# Number of messages to backfill in one batch.
|
||||
batch_size: 100
|
||||
# Delay between batches in seconds.
|
||||
batch_delay: 20
|
||||
# Maximum number of batches to backfill per portal.
|
||||
# If set to -1, all available messages will be backfilled.
|
||||
max_batches: -1
|
||||
# Optional network-specific overrides for max batches.
|
||||
# Interpretation of this field depends on the network connector.
|
||||
max_batches_override: {}
|
||||
|
||||
# Settings for enabling double puppeting
|
||||
double_puppet:
|
||||
# Servers to always allow double puppeting from.
|
||||
# This is only for other servers and should NOT contain the server the bridge is on.
|
||||
servers:
|
||||
anotherserver.example.org: https://matrix.anotherserver.example.org
|
||||
# Whether to allow client API URL discovery for other servers. When using this option,
|
||||
# users on other servers can use double puppeting even if their server URLs aren't
|
||||
# explicitly added to the servers map above.
|
||||
allow_discovery: false
|
||||
# Shared secrets for automatic double puppeting.
|
||||
# See https://docs.mau.fi/bridges/general/double-puppeting.html for instructions.
|
||||
secrets:
|
||||
example.com: as_token:foobar
|
||||
|
||||
# End-to-bridge encryption support options.
|
||||
#
|
||||
# See https://docs.mau.fi/bridges/general/end-to-bridge-encryption.html for more info.
|
||||
encryption:
|
||||
# Whether to enable encryption at all. If false, the bridge will not function in encrypted rooms.
|
||||
allow: false
|
||||
# Whether to force-enable encryption in all bridged rooms.
|
||||
default: false
|
||||
# Whether to require all messages to be encrypted and drop any unencrypted messages.
|
||||
require: false
|
||||
# Whether to use MSC3202/MSC4203 instead of /sync long polling for receiving encryption-related data.
|
||||
# This is an experimental option, see the docs for more info.
|
||||
# Changing this option requires updating the appservice registration file.
|
||||
appservice: false
|
||||
# Whether to use MSC4190 instead of appservice login to create the bridge bot device.
|
||||
# Requires the homeserver to support MSC4190 and the device masquerading parts of MSC3202.
|
||||
# Only relevant when using end-to-bridge encryption, required when using encryption with next-gen auth (MSC3861).
|
||||
msc4190: false
|
||||
# Whether to encrypt reactions and reply metadata as per MSC4392.
|
||||
# This is not supported by most clients.
|
||||
msc4392: false
|
||||
# Should the bridge bot generate a recovery key and cross-signing keys and verify itself?
|
||||
# Note that without the latest version of MSC4190, this will fail if you reset the bridge database.
|
||||
# The generated recovery key will be saved in the kv_store table under `recovery_key`.
|
||||
self_sign: false
|
||||
# Enable key sharing? If enabled, key requests for rooms where users are in will be fulfilled.
|
||||
# You must use a client that supports requesting keys from other users to use this feature.
|
||||
allow_key_sharing: true
|
||||
# Should m.mentions be sent in the unencrypted content? This is non-standard and should not be enabled.
|
||||
plaintext_mentions: false
|
||||
# Pickle key for encrypting encryption keys in the bridge database.
|
||||
# If set to generate, a random key will be generated.
|
||||
pickle_key: generate
|
||||
# Options for deleting megolm sessions from the bridge.
|
||||
delete_keys:
|
||||
# Beeper-specific: delete outbound sessions when hungryserv confirms
|
||||
# that the user has uploaded the key to key backup.
|
||||
delete_outbound_on_ack: false
|
||||
# Don't store outbound sessions in the inbound table.
|
||||
dont_store_outbound: false
|
||||
# Ratchet megolm sessions forward after decrypting messages.
|
||||
ratchet_on_decrypt: false
|
||||
# Delete fully used keys (index >= max_messages) after decrypting messages.
|
||||
delete_fully_used_on_decrypt: false
|
||||
# Delete previous megolm sessions from same device when receiving a new one.
|
||||
delete_prev_on_new_session: false
|
||||
# Delete megolm sessions received from a device when the device is deleted.
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
# unverified - Send keys to all device in the room.
|
||||
# cross-signed-untrusted - Require valid cross-signing, but trust all cross-signing keys.
|
||||
# cross-signed-tofu - Require valid cross-signing, trust cross-signing keys on first use (and reject changes).
|
||||
# cross-signed-verified - Require valid cross-signing, plus a valid user signature from the bridge bot.
|
||||
# Note that creating user signatures from the bridge bot is not currently possible.
|
||||
# verified - Require manual per-device verification
|
||||
# (currently only possible by modifying the `trust` column in the `crypto_device` database table).
|
||||
verification_levels:
|
||||
# Minimum level for which the bridge should send keys to when bridging messages from the remote network to Matrix.
|
||||
receive: unverified
|
||||
# Minimum level that the bridge should accept for incoming Matrix messages.
|
||||
send: unverified
|
||||
# Minimum level that the bridge should require for accepting key requests.
|
||||
share: cross-signed-tofu
|
||||
# Options for Megolm room key rotation. These options allow you to configure the m.room.encryption event content.
|
||||
# See https://spec.matrix.org/v1.10/client-server-api/#mroomencryption for more information about that event.
|
||||
rotation:
|
||||
# Enable custom Megolm room key rotation settings. Note that these
|
||||
# settings will only apply to rooms created after this option is set.
|
||||
enable_custom: false
|
||||
# The maximum number of milliseconds a session should be used
|
||||
# before changing it. The Matrix spec recommends 604800000 (a week)
|
||||
# as the default.
|
||||
milliseconds: 604800000
|
||||
# The maximum number of messages that should be sent with a given a
|
||||
# session before changing it. The Matrix spec recommends 100 as the
|
||||
# default.
|
||||
messages: 100
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Prefix for environment variables. All variables with this prefix must map to valid config fields.
|
||||
# Nesting in variable names is represented with a dot (.).
|
||||
# If there are no dots in the name, two underscores (__) are replaced with a dot.
|
||||
#
|
||||
# e.g. if the prefix is set to `BRIDGE_`, then `BRIDGE_APPSERVICE__AS_TOKEN` will set appservice.as_token.
|
||||
# `BRIDGE_appservice.as_token` would work as well, but can't be set in a shell as easily.
|
||||
#
|
||||
# The variable names can also have a `_FILE` suffix to tell the bridge to read the value from the
|
||||
# path set in the variable rather than using the value directly.
|
||||
#
|
||||
# If this is null, reading config fields from environment will be disabled.
|
||||
env_config_prefix: null
|
||||
|
||||
# Logging config. See https://github.com/tulir/zeroconfig for details.
|
||||
logging:
|
||||
min_level: debug
|
||||
writers:
|
||||
- type: stdout
|
||||
format: pretty-colored
|
||||
- type: file
|
||||
format: json
|
||||
filename: ./logs/bridge.log
|
||||
max_size: 100
|
||||
max_backups: 10
|
||||
compress: false
|
||||
@@ -0,0 +1,203 @@
|
||||
name: vortex
|
||||
|
||||
x-bridge-common: &bridge-common
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
bootstrap:
|
||||
condition: service_completed_successfully
|
||||
synapse:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
TZ: ${TZ:-UTC}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: vortex
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
||||
POSTGRES_DB: synapse
|
||||
POSTGRES_INITDB_ARGS: --encoding=UTF8 --locale=C
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U vortex -d synapse"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 12
|
||||
|
||||
bootstrap:
|
||||
build: ./bootstrap
|
||||
environment:
|
||||
MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-localhost}
|
||||
MATRIX_USER: ${MATRIX_USER:-admin}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
REGISTRATION_SHARED_SECRET: ${REGISTRATION_SHARED_SECRET:-auto}
|
||||
DOUBLEPUPPET_SHARED_SECRET: ${DOUBLEPUPPET_SHARED_SECRET:-auto}
|
||||
BRIDGES_ENABLED: ${BRIDGES_ENABLED:-}
|
||||
TELEGRAM_API_ID: ${TELEGRAM_API_ID:-}
|
||||
TELEGRAM_API_HASH: ${TELEGRAM_API_HASH:-}
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- data:/data
|
||||
- ./synapse:/templates/synapse:ro
|
||||
- ./bridges/templates:/templates/bridges:ro
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
command: /bootstrap.sh
|
||||
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:v1.156.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: synapse }
|
||||
ports:
|
||||
- "8008:8008"
|
||||
depends_on:
|
||||
bootstrap:
|
||||
condition: service_completed_successfully
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8008/health')\""]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 24
|
||||
start_period: 30s
|
||||
|
||||
provision:
|
||||
build: ./bootstrap
|
||||
environment:
|
||||
MATRIX_SERVER_NAME: ${MATRIX_SERVER_NAME:-localhost}
|
||||
MATRIX_USER: ${MATRIX_USER:-admin}
|
||||
MATRIX_PASSWORD: ${MATRIX_PASSWORD:?set MATRIX_PASSWORD in .env}
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- data:/data
|
||||
depends_on:
|
||||
synapse:
|
||||
condition: service_healthy
|
||||
command: /provision.sh
|
||||
|
||||
whatsapp:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/whatsapp:v0.2606.0
|
||||
profiles: [whatsapp]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/whatsapp }
|
||||
|
||||
gmessages:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/gmessages:v26.05
|
||||
profiles: [gmessages]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/gmessages }
|
||||
|
||||
telegram:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/telegram:v26.06
|
||||
profiles: [telegram]
|
||||
entrypoint: ["/bin/sh", "-c", "if [ ! -f /data/config.yaml ]; then echo 'telegram bridge skipped: TELEGRAM_API_ID / TELEGRAM_API_HASH not set in .env'; exec sleep infinity; fi; exec /docker-run.sh"]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/telegram }
|
||||
|
||||
twitter:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/twitter:v26.06
|
||||
profiles: [twitter]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/twitter }
|
||||
|
||||
linkedin:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/linkedin:v26.04
|
||||
profiles: [linkedin]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/linkedin }
|
||||
|
||||
discord:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/discord:v0.7.6
|
||||
profiles: [discord]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/discord }
|
||||
|
||||
meta:
|
||||
<<: *bridge-common
|
||||
image: dock.mau.dev/mautrix/meta:v26.06
|
||||
profiles: [meta]
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /data
|
||||
volume: { subpath: bridges/meta }
|
||||
|
||||
mcp:
|
||||
build: ./mcp
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MCP_HTTP_PORT: ${MCP_HTTP_PORT:-8765}
|
||||
MCP_ALLOW_SEND: ${MCP_ALLOW_SEND:-false}
|
||||
CREDENTIALS_FILE: /shared/credentials.json
|
||||
DRAFTS_DB: /shared/drafts/drafts.db
|
||||
STORE_DIR: /shared/mcp-store
|
||||
TZ: ${TZ:-UTC}
|
||||
ports:
|
||||
- "${MCP_HTTP_PORT:-8765}:${MCP_HTTP_PORT:-8765}"
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /shared
|
||||
volume: { subpath: shared }
|
||||
depends_on:
|
||||
provision:
|
||||
condition: service_completed_successfully
|
||||
|
||||
tui:
|
||||
build: ./tui
|
||||
profiles: [tui]
|
||||
stdin_open: true
|
||||
tty: true
|
||||
environment:
|
||||
CREDENTIALS_FILE: /shared/credentials.json
|
||||
DRAFTS_DB: /shared/drafts/drafts.db
|
||||
STORE_DIR: /shared/tui-store
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- type: volume
|
||||
source: data
|
||||
target: /shared
|
||||
volume: { subpath: shared }
|
||||
depends_on:
|
||||
synapse:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
data:
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential libolm-dev libolm3 curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml ./
|
||||
COPY server ./server
|
||||
RUN pip install --no-cache-dir .
|
||||
|
||||
ENV MCP_HTTP_PORT=8765
|
||||
|
||||
# Any HTTP response (even 4xx/406) means the server is up; curl exits 0 on connect.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
|
||||
CMD curl -s -o /dev/null "http://localhost:${MCP_HTTP_PORT:-8765}/mcp" || exit 1
|
||||
|
||||
CMD ["vortex-mcp"]
|
||||
@@ -0,0 +1,22 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "vortex-mcp"
|
||||
version = "0.1.0"
|
||||
description = "MCP server for the Vortex Matrix messaging stack"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastmcp>=2",
|
||||
"matrix-nio[e2e]>=0.25",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
vortex-mcp = "vortex_mcp.server:main"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = { "" = "server" }
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["server"]
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Vortex MCP server package."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,319 @@
|
||||
"""Vortex MCP server: read-first Matrix access with a human-gated draft outbox."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
import uuid
|
||||
from collections import deque
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.exceptions import ToolError
|
||||
from nio import (
|
||||
AsyncClient,
|
||||
AsyncClientConfig,
|
||||
LoginResponse,
|
||||
MessageDirection,
|
||||
RoomMessagesResponse,
|
||||
RoomMessageText,
|
||||
)
|
||||
from nio.crypto import ENCRYPTION_ENABLED
|
||||
|
||||
log = logging.getLogger("vortex-mcp")
|
||||
|
||||
CREDENTIALS_FILE = os.environ.get("CREDENTIALS_FILE", "/shared/credentials.json")
|
||||
STORE_DIR = Path(os.environ.get("STORE_DIR", "/shared/mcp-store"))
|
||||
DRAFTS_DB = os.environ.get("DRAFTS_DB", "/shared/drafts/drafts.db")
|
||||
NETWORKS = ("gmessages", "telegram", "whatsapp", "twitter", "linkedin", "discord", "meta")
|
||||
|
||||
mcp = FastMCP("vortex")
|
||||
|
||||
_client: AsyncClient | None = None
|
||||
_sync_task: asyncio.Task | None = None
|
||||
_init_lock = asyncio.Lock()
|
||||
# room_id -> deque of message dicts, filled by the background sync loop
|
||||
_cache: dict[str, deque] = {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- drafts db
|
||||
|
||||
def _db() -> sqlite3.Connection:
|
||||
Path(DRAFTS_DB).parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(DRAFTS_DB)
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS drafts(
|
||||
draft_id TEXT PRIMARY KEY, room_id TEXT NOT NULL, body TEXT NOT NULL,
|
||||
reply_to_event_id TEXT, created_by TEXT NOT NULL, created_at TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft', sent_event_id TEXT)"""
|
||||
)
|
||||
return conn
|
||||
|
||||
|
||||
def _get_draft(conn: sqlite3.Connection, draft_id: str) -> dict:
|
||||
row = conn.execute("SELECT * FROM drafts WHERE draft_id=?", (draft_id,)).fetchone()
|
||||
if row is None:
|
||||
raise ToolError(f"Draft not found: {draft_id}")
|
||||
return dict(row)
|
||||
|
||||
|
||||
# ------------------------------------------------------------- matrix client
|
||||
|
||||
async def _load_credentials() -> dict:
|
||||
for _ in range(150): # bootstrap provisions the file; wait up to ~5 min
|
||||
if os.path.exists(CREDENTIALS_FILE):
|
||||
with open(CREDENTIALS_FILE) as f:
|
||||
return json.load(f)
|
||||
log.info("Waiting for credentials file %s ...", CREDENTIALS_FILE)
|
||||
await asyncio.sleep(2)
|
||||
raise ToolError(f"Credentials file never appeared: {CREDENTIALS_FILE}")
|
||||
|
||||
|
||||
async def _login(creds: dict) -> AsyncClient:
|
||||
homeserver = os.environ.get("MATRIX_HOMESERVER") or creds["homeserver"]
|
||||
STORE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
session_file = STORE_DIR / "session.json"
|
||||
config = AsyncClientConfig(
|
||||
encryption_enabled=ENCRYPTION_ENABLED, store_sync_tokens=True
|
||||
)
|
||||
client = AsyncClient(
|
||||
homeserver, creds["user_id"], store_path=str(STORE_DIR), config=config
|
||||
)
|
||||
|
||||
if session_file.exists():
|
||||
sess = json.loads(session_file.read_text())
|
||||
client.restore_login(sess["user_id"], sess["device_id"], sess["access_token"])
|
||||
log.info("Restored session for %s (device %s)", sess["user_id"], sess["device_id"])
|
||||
return client
|
||||
|
||||
resp = await client.login(creds.get("password") or "", device_name="vortex-mcp")
|
||||
if isinstance(resp, LoginResponse):
|
||||
session_file.write_text(json.dumps({
|
||||
"user_id": resp.user_id,
|
||||
"device_id": resp.device_id,
|
||||
"access_token": resp.access_token,
|
||||
}))
|
||||
log.info("Password login ok, session persisted")
|
||||
return client
|
||||
|
||||
# ponytail: fall back to the shared bootstrap token if password login fails
|
||||
log.warning("Password login failed (%s); using shared access_token", resp)
|
||||
client.restore_login(creds["user_id"], creds.get("device_id") or "", creds["access_token"])
|
||||
return client
|
||||
|
||||
|
||||
async def _on_message(room, event) -> None:
|
||||
dq = _cache.setdefault(room.room_id, deque(maxlen=1000))
|
||||
dq.append({
|
||||
"event_id": event.event_id,
|
||||
"sender": event.sender,
|
||||
"sender_display": room.user_name(event.sender) or event.sender,
|
||||
"body": event.body,
|
||||
"timestamp_ms": event.server_timestamp,
|
||||
"room_id": room.room_id,
|
||||
})
|
||||
|
||||
|
||||
async def _get_client() -> AsyncClient:
|
||||
"""Lazy-init the nio client and background sync task on first tool call."""
|
||||
global _client, _sync_task
|
||||
async with _init_lock:
|
||||
if _client is None:
|
||||
creds = await _load_credentials()
|
||||
_client = await _login(creds)
|
||||
_client.add_event_callback(_on_message, RoomMessageText)
|
||||
if _sync_task is None or _sync_task.done():
|
||||
_sync_task = asyncio.create_task(
|
||||
_client.sync_forever(timeout=30000, full_state=True)
|
||||
)
|
||||
return _client
|
||||
|
||||
|
||||
def _room_network(client: AsyncClient, room) -> str:
|
||||
domain = client.user_id.split(":", 1)[1] if ":" in (client.user_id or "") else ""
|
||||
for uid in room.users:
|
||||
for net in NETWORKS:
|
||||
if uid.startswith(f"@{net}_") or uid == f"@{net}bot:{domain}":
|
||||
return net
|
||||
return "matrix"
|
||||
|
||||
|
||||
# -------------------------------------------------------------------- tools
|
||||
|
||||
@mcp.tool
|
||||
async def list_rooms(network_filter: str | None = None) -> list[dict]:
|
||||
"""List joined rooms. Optionally filter by bridge network
|
||||
(gmessages, telegram, whatsapp, twitter, linkedin, discord, meta, matrix)."""
|
||||
client = await _get_client()
|
||||
out = []
|
||||
for room_id, room in client.rooms.items():
|
||||
network = _room_network(client, room)
|
||||
if network_filter and network != network_filter:
|
||||
continue
|
||||
out.append({
|
||||
"room_id": room_id,
|
||||
"name": room.display_name,
|
||||
"network": network,
|
||||
"unread_count": getattr(room, "unread_notifications", 0) or 0,
|
||||
"member_count": room.member_count,
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def read_messages(room_id: str, limit: int = 50, before: str | None = None) -> dict:
|
||||
"""Read recent messages from a room, newest first. Pass the returned `end`
|
||||
token as `before` to paginate further back."""
|
||||
client = await _get_client()
|
||||
resp = await client.room_messages(
|
||||
room_id,
|
||||
start=before or client.next_batch,
|
||||
direction=MessageDirection.back,
|
||||
limit=limit,
|
||||
)
|
||||
if not isinstance(resp, RoomMessagesResponse):
|
||||
raise ToolError(f"room_messages failed: {resp}")
|
||||
room = client.rooms.get(room_id)
|
||||
messages = [
|
||||
{
|
||||
"event_id": ev.event_id,
|
||||
"sender": ev.sender,
|
||||
"sender_display": room.user_name(ev.sender) if room else ev.sender,
|
||||
"body": ev.body,
|
||||
"timestamp_ms": ev.server_timestamp,
|
||||
"room_id": room_id,
|
||||
}
|
||||
for ev in resp.chunk
|
||||
if isinstance(ev, RoomMessageText)
|
||||
]
|
||||
return {"messages": messages, "end": resp.end}
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def search_messages(query: str, room_id: str | None = None) -> list[dict]:
|
||||
"""Case-insensitive substring search over messages seen since this server
|
||||
started syncing. Optionally restrict to one room."""
|
||||
await _get_client()
|
||||
needle = query.lower()
|
||||
rooms = [room_id] if room_id else list(_cache)
|
||||
return [
|
||||
msg
|
||||
for rid in rooms
|
||||
for msg in _cache.get(rid, ())
|
||||
if needle in msg["body"].lower()
|
||||
]
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def create_draft(room_id: str, body: str, reply_to_event_id: str | None = None) -> dict:
|
||||
"""Create a message draft for a room. Never sends anything; a human reviews
|
||||
drafts and sends them from the TUI."""
|
||||
draft = {
|
||||
"draft_id": uuid.uuid4().hex,
|
||||
"room_id": room_id,
|
||||
"body": body,
|
||||
"reply_to_event_id": reply_to_event_id,
|
||||
"created_by": "mcp",
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
"status": "draft",
|
||||
"sent_event_id": None,
|
||||
}
|
||||
with _db() as conn:
|
||||
conn.execute(
|
||||
"INSERT INTO drafts VALUES(:draft_id,:room_id,:body,:reply_to_event_id,"
|
||||
":created_by,:created_at,:status,:sent_event_id)",
|
||||
draft,
|
||||
)
|
||||
return draft
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def list_drafts() -> list[dict]:
|
||||
"""List all pending (unsent, undiscarded) drafts."""
|
||||
with _db() as conn:
|
||||
rows = conn.execute("SELECT * FROM drafts WHERE status='draft'").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def update_draft(draft_id: str, body: str) -> dict:
|
||||
"""Replace the body of a pending draft."""
|
||||
with _db() as conn:
|
||||
draft = _get_draft(conn, draft_id)
|
||||
if draft["status"] != "draft":
|
||||
raise ToolError(f"Draft {draft_id} is {draft['status']}, not editable")
|
||||
conn.execute("UPDATE drafts SET body=? WHERE draft_id=?", (body, draft_id))
|
||||
draft["body"] = body
|
||||
return draft
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def discard_draft(draft_id: str) -> dict:
|
||||
"""Mark a draft as discarded."""
|
||||
with _db() as conn:
|
||||
draft = _get_draft(conn, draft_id)
|
||||
conn.execute("UPDATE drafts SET status='discarded' WHERE draft_id=?", (draft_id,))
|
||||
draft["status"] = "discarded"
|
||||
return draft
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def send_draft(draft_id: str) -> dict:
|
||||
"""Send a pending draft to its room. Only works when MCP_ALLOW_SEND=true;
|
||||
otherwise a human must send it from the TUI."""
|
||||
if os.environ.get("MCP_ALLOW_SEND", "false").lower() != "true":
|
||||
raise ToolError(
|
||||
"Sending disabled: a human must send this draft from the TUI (MCP_ALLOW_SEND=false)"
|
||||
)
|
||||
with _db() as conn:
|
||||
draft = _get_draft(conn, draft_id)
|
||||
if draft["status"] != "draft":
|
||||
raise ToolError(f"Draft {draft_id} is {draft['status']}, cannot send")
|
||||
|
||||
client = await _get_client()
|
||||
content: dict = {"msgtype": "m.text", "body": draft["body"]}
|
||||
if draft["reply_to_event_id"]:
|
||||
content["m.relates_to"] = {
|
||||
"m.in_reply_to": {"event_id": draft["reply_to_event_id"]}
|
||||
}
|
||||
resp = await client.room_send(
|
||||
draft["room_id"], "m.room.message", content, ignore_unverified_devices=True
|
||||
)
|
||||
event_id = getattr(resp, "event_id", None)
|
||||
if not event_id:
|
||||
raise ToolError(f"Send failed: {resp}")
|
||||
with _db() as conn:
|
||||
conn.execute(
|
||||
"UPDATE drafts SET status='sent', sent_event_id=? WHERE draft_id=?",
|
||||
(event_id, draft_id),
|
||||
)
|
||||
draft.update(status="sent", sent_event_id=event_id)
|
||||
return draft
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- main
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
parser = argparse.ArgumentParser(description="Vortex MCP server")
|
||||
parser.add_argument("--stdio", action="store_true", help="use stdio transport")
|
||||
args = parser.parse_args()
|
||||
if args.stdio:
|
||||
mcp.run()
|
||||
else:
|
||||
mcp.run(
|
||||
transport="http",
|
||||
host="0.0.0.0",
|
||||
port=int(os.environ.get("MCP_HTTP_PORT", "8765")),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
# Smoke tests: stack health, appservice round-trip, MCP tools + draft gating.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
set -a; source .env; set +a
|
||||
|
||||
HS=http://localhost:8008
|
||||
pass() { echo "PASS: $*"; }
|
||||
fail() { echo "FAIL: $*"; exit 1; }
|
||||
|
||||
echo "== 1/4 containers"
|
||||
docker compose ps --format '{{.Service}} {{.State}} {{.Health}}' | tee /tmp/vortex-ps.txt
|
||||
for svc in postgres synapse mcp; do
|
||||
grep -q "^$svc running" /tmp/vortex-ps.txt || fail "$svc not running"
|
||||
done
|
||||
IFS=',' read -ra BRIDGES <<< "${BRIDGES_ENABLED:-}"
|
||||
for b in "${BRIDGES[@]}"; do
|
||||
grep -q "^$b running" /tmp/vortex-ps.txt || fail "bridge $b not running"
|
||||
done
|
||||
for oneshot in bootstrap provision; do
|
||||
st=$(docker compose ps -a --format '{{.Service}} {{.ExitCode}}' $oneshot | awk '{print $2}')
|
||||
[ "$st" = 0 ] || fail "$oneshot exit code $st"
|
||||
done
|
||||
pass "all services up, one-shots exited 0"
|
||||
|
||||
echo "== 2/4 synapse health + login"
|
||||
curl -sf $HS/health >/dev/null || fail "synapse /health"
|
||||
TOKEN=$(curl -sf -X POST $HS/_matrix/client/v3/login -d "{
|
||||
\"type\":\"m.login.password\",
|
||||
\"identifier\":{\"type\":\"m.id.user\",\"user\":\"$MATRIX_USER\"},
|
||||
\"password\":\"$MATRIX_PASSWORD\",\"initial_device_display_name\":\"verify\"}" |
|
||||
python3 -c 'import json,sys;print(json.load(sys.stdin)["access_token"])')
|
||||
[ -n "$TOKEN" ] || fail "login"
|
||||
pass "synapse healthy, admin login works"
|
||||
|
||||
echo "== 3/4 bridge bot round-trip"
|
||||
FIRST_BRIDGE=$(echo "${BRIDGES_ENABLED}" | cut -d, -f1)
|
||||
BOT="@${FIRST_BRIDGE}bot:${MATRIX_SERVER_NAME}"
|
||||
python3 - "$HS" "$TOKEN" "$BOT" <<'EOF' || fail "bridge bot did not respond"
|
||||
import json, sys, time, urllib.request
|
||||
|
||||
hs, token, bot = sys.argv[1:4]
|
||||
def api(method, path, body=None):
|
||||
req = urllib.request.Request(hs + path, method=method,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
headers={"Authorization": "Bearer " + token})
|
||||
return json.load(urllib.request.urlopen(req))
|
||||
|
||||
room = api("POST", "/_matrix/client/v3/createRoom",
|
||||
{"invite": [bot], "is_direct": True, "preset": "trusted_private_chat"})["room_id"]
|
||||
api("PUT", f"/_matrix/client/v3/rooms/{room}/send/m.room.message/{time.time_ns()}",
|
||||
{"msgtype": "m.text", "body": "help"})
|
||||
deadline = time.time() + 30
|
||||
since = None
|
||||
while time.time() < deadline:
|
||||
q = f"/_matrix/client/v3/sync?timeout=5000" + (f"&since={since}" if since else "")
|
||||
resp = api("GET", q)
|
||||
since = resp["next_batch"]
|
||||
events = resp.get("rooms", {}).get("join", {}).get(room, {}).get("timeline", {}).get("events", [])
|
||||
for e in events:
|
||||
if e["sender"] == bot and e["type"] == "m.room.message":
|
||||
print("bot replied:", e["content"]["body"][:80].replace("\n", " "))
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
EOF
|
||||
pass "appservice round-trip via $BOT"
|
||||
|
||||
echo "== 4/4 MCP tools + draft gating"
|
||||
docker compose exec -T mcp python - <<'EOF' || fail "mcp checks"
|
||||
import asyncio, json, os
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
port = os.environ.get("MCP_HTTP_PORT", "8765")
|
||||
async with Client(f"http://localhost:{port}/mcp") as c:
|
||||
tools = {t.name for t in await c.list_tools()}
|
||||
assert {"list_rooms", "read_messages", "create_draft", "send_draft"} <= tools, tools
|
||||
rooms = (await c.call_tool("list_rooms")).data
|
||||
assert isinstance(rooms, list), rooms
|
||||
print(f"list_rooms: {len(rooms)} rooms")
|
||||
room_id = rooms[0]["room_id"] if rooms else "!dummy:localhost"
|
||||
d = (await c.call_tool("create_draft",
|
||||
{"room_id": room_id, "body": "verify draft"})).data
|
||||
did = d["draft_id"]
|
||||
drafts = (await c.call_tool("list_drafts")).data
|
||||
assert any(x["draft_id"] == did for x in drafts)
|
||||
try:
|
||||
await c.call_tool("send_draft", {"draft_id": did})
|
||||
raise SystemExit("send_draft should have been blocked")
|
||||
except Exception as e:
|
||||
assert "disabled" in str(e).lower() or "human" in str(e).lower(), e
|
||||
print("send_draft correctly blocked:", str(e)[:80])
|
||||
await c.call_tool("discard_draft", {"draft_id": did})
|
||||
print("draft lifecycle OK")
|
||||
|
||||
asyncio.run(main())
|
||||
EOF
|
||||
pass "mcp list_rooms + draft lifecycle + send gating"
|
||||
|
||||
echo
|
||||
echo "ALL CHECKS PASSED"
|
||||
@@ -0,0 +1,60 @@
|
||||
server_name: "${MATRIX_SERVER_NAME}"
|
||||
pid_file: /data/homeserver.pid
|
||||
report_stats: false
|
||||
|
||||
listeners:
|
||||
- port: 8008
|
||||
tls: false
|
||||
type: http
|
||||
x_forwarded: true
|
||||
bind_addresses: ['0.0.0.0']
|
||||
resources:
|
||||
- names: [client, federation]
|
||||
compress: false
|
||||
|
||||
database:
|
||||
name: psycopg2
|
||||
args:
|
||||
user: vortex
|
||||
password: "${POSTGRES_PASSWORD}"
|
||||
database: synapse
|
||||
host: postgres
|
||||
cp_min: 5
|
||||
cp_max: 10
|
||||
|
||||
log_config: /data/log.config
|
||||
media_store_path: /data/media_store
|
||||
signing_key_path: /data/signing.key
|
||||
|
||||
registration_shared_secret: "${REGISTRATION_SHARED_SECRET}"
|
||||
enable_registration: false
|
||||
|
||||
macaroon_secret_key: "${MACAROON_SECRET_KEY}"
|
||||
form_secret: "${FORM_SECRET}"
|
||||
|
||||
trusted_key_servers: []
|
||||
suppress_key_server_warning: true
|
||||
serve_server_wellknown: false
|
||||
|
||||
# Relaxed rate limits for local single-user use.
|
||||
rc_message:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
rc_login:
|
||||
address: { per_second: 1000, burst_count: 1000 }
|
||||
account: { per_second: 1000, burst_count: 1000 }
|
||||
failed_attempts: { per_second: 1000, burst_count: 1000 }
|
||||
rc_registration:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
rc_joins:
|
||||
local: { per_second: 1000, burst_count: 1000 }
|
||||
remote: { per_second: 1000, burst_count: 1000 }
|
||||
rc_invites:
|
||||
per_room: { per_second: 1000, burst_count: 1000 }
|
||||
per_user: { per_second: 1000, burst_count: 1000 }
|
||||
rc_admin_redaction:
|
||||
per_second: 1000
|
||||
burst_count: 1000
|
||||
|
||||
# app_service_config_files is appended by bootstrap.sh
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
# libolm + toolchain: matrix-nio[e2e] builds python-olm against libolm
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libolm-dev libolm3 build-essential \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/vortex-tui
|
||||
COPY pyproject.toml ./
|
||||
COPY app ./app
|
||||
RUN pip install --no-cache-dir .
|
||||
|
||||
ENV TERM=xterm-256color \
|
||||
CREDENTIALS_FILE=/shared/credentials.json \
|
||||
STORE_DIR=/shared/tui-store \
|
||||
DRAFTS_DB=/shared/drafts/drafts.db
|
||||
|
||||
# interactive TUI: run with `docker compose run --rm tui` (TTY)
|
||||
CMD ["vortex-tui"]
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Vortex TUI - unified Matrix messaging client."""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,53 @@
|
||||
"""SQLite drafts store, shared with other vortex components via DRAFTS_DB."""
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS drafts(
|
||||
draft_id TEXT PRIMARY KEY,
|
||||
room_id TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
reply_to_event_id TEXT,
|
||||
created_by TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'draft',
|
||||
sent_event_id TEXT
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
def connect(path: str | None = None) -> sqlite3.Connection:
|
||||
db = Path(path or os.environ.get("DRAFTS_DB", "/shared/drafts/drafts.db"))
|
||||
db.parent.mkdir(parents=True, exist_ok=True)
|
||||
con = sqlite3.connect(db)
|
||||
con.row_factory = sqlite3.Row
|
||||
con.execute("PRAGMA journal_mode=WAL")
|
||||
con.execute(SCHEMA)
|
||||
con.commit()
|
||||
return con
|
||||
|
||||
|
||||
def pending(con: sqlite3.Connection) -> list[sqlite3.Row]:
|
||||
return con.execute(
|
||||
"SELECT * FROM drafts WHERE status='draft' ORDER BY created_at"
|
||||
).fetchall()
|
||||
|
||||
|
||||
def update_body(con: sqlite3.Connection, draft_id: str, body: str) -> None:
|
||||
con.execute("UPDATE drafts SET body=? WHERE draft_id=?", (body, draft_id))
|
||||
con.commit()
|
||||
|
||||
|
||||
def mark(
|
||||
con: sqlite3.Connection,
|
||||
draft_id: str,
|
||||
status: str,
|
||||
sent_event_id: str | None = None,
|
||||
) -> None:
|
||||
con.execute(
|
||||
"UPDATE drafts SET status=?, sent_event_id=? WHERE draft_id=?",
|
||||
(status, sent_event_id, draft_id),
|
||||
)
|
||||
con.commit()
|
||||
@@ -0,0 +1,369 @@
|
||||
"""Vortex TUI application: room list, timeline, bridges, drafts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nio import (
|
||||
AsyncClient,
|
||||
MatrixRoom,
|
||||
RoomCreateResponse,
|
||||
RoomMessageMedia,
|
||||
RoomMessageNotice,
|
||||
RoomMessageText,
|
||||
SyncResponse,
|
||||
)
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.binding import Binding
|
||||
from textual.containers import Horizontal, Vertical
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Footer, Input, Label, ListItem, ListView, RichLog, Static
|
||||
|
||||
from . import drafts as drafts_db
|
||||
from .qr import looks_like_qr_payload, render_qr
|
||||
from .session import build_client, load_credentials
|
||||
|
||||
NETWORKS = ["gmessages", "telegram", "whatsapp", "twitter", "linkedin", "discord", "meta"]
|
||||
LABELS = {
|
||||
"gmessages": "gm",
|
||||
"telegram": "tg",
|
||||
"whatsapp": "wa",
|
||||
"twitter": "tw",
|
||||
"linkedin": "li",
|
||||
"discord": "dc",
|
||||
"meta": "ig",
|
||||
"matrix": "mx",
|
||||
}
|
||||
|
||||
|
||||
class BridgeScreen(Screen[str | None]):
|
||||
"""List the known bridge bots; Enter opens or creates the bot DM."""
|
||||
|
||||
BINDINGS = [Binding("escape", "back", "Back")]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Static(
|
||||
"Bridges - Enter opens (or creates) the bot DM, then send it a login "
|
||||
"command there (e.g. '!wa help', 'login', 'help').",
|
||||
id="bridge-help",
|
||||
)
|
||||
yield ListView(id="bridge-list")
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
app: VortexApp = self.app
|
||||
lv = self.query_one("#bridge-list", ListView)
|
||||
for net in NETWORKS:
|
||||
bot = f"@{net}bot:{app.domain}"
|
||||
dm = app.find_bot_dm(bot)
|
||||
status = "DM open" if dm else "no DM (Enter creates one)"
|
||||
item = ListItem(Label(f"[{LABELS[net]}] {net:<10} {bot:<32} {status}"))
|
||||
item.bot, item.dm = bot, dm
|
||||
lv.append(item)
|
||||
lv.focus()
|
||||
|
||||
async def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
event.stop()
|
||||
item = event.item
|
||||
if item.dm:
|
||||
self.dismiss(item.dm)
|
||||
return
|
||||
resp = await self.app.client.room_create(invite=[item.bot], is_direct=True)
|
||||
if isinstance(resp, RoomCreateResponse):
|
||||
self.dismiss(resp.room_id)
|
||||
else:
|
||||
self.notify(f"room_create failed: {resp}", severity="error")
|
||||
|
||||
def action_back(self) -> None:
|
||||
self.dismiss(None)
|
||||
|
||||
|
||||
class DraftsScreen(Screen):
|
||||
"""Pending drafts from the shared SQLite DB, polled every 2s."""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("escape", "back", "Back"),
|
||||
Binding("e", "edit", "Edit"),
|
||||
Binding("s", "send", "Send"),
|
||||
Binding("x", "discard", "Discard"),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Static("Drafts - e edit, s send, x discard, esc back", id="drafts-help")
|
||||
yield ListView(id="draft-list")
|
||||
yield Input(placeholder="Edit draft body, Enter saves", id="draft-edit")
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.con = drafts_db.connect()
|
||||
self._editing: str | None = None
|
||||
self.query_one("#draft-edit", Input).display = False
|
||||
self.call_later(self._refresh)
|
||||
# ponytail: 2s poll instead of watchfiles; watch the db file if this chafes
|
||||
self.set_interval(2, self._refresh)
|
||||
self.query_one("#draft-list", ListView).focus()
|
||||
|
||||
async def _refresh(self) -> None:
|
||||
app: VortexApp = self.app
|
||||
lv = self.query_one("#draft-list", ListView)
|
||||
idx = lv.index
|
||||
items = []
|
||||
for row in drafts_db.pending(self.con):
|
||||
room = app.client.rooms.get(row["room_id"]) if app.client else None
|
||||
name = (room.display_name if room else row["room_id"])[:24]
|
||||
body = row["body"].replace("\n", " ")[:48]
|
||||
item = ListItem(
|
||||
Label(f"{name:<24} | {body:<48} | {row['created_by']} @ {row['created_at']}")
|
||||
)
|
||||
item.draft = dict(row)
|
||||
items.append(item)
|
||||
await lv.clear()
|
||||
await lv.extend(items)
|
||||
if idx is not None and items:
|
||||
lv.index = min(idx, len(items) - 1)
|
||||
|
||||
def _current(self) -> dict | None:
|
||||
item = self.query_one("#draft-list", ListView).highlighted_child
|
||||
return getattr(item, "draft", None)
|
||||
|
||||
def action_edit(self) -> None:
|
||||
draft = self._current()
|
||||
if not draft:
|
||||
return
|
||||
self._editing = draft["draft_id"]
|
||||
box = self.query_one("#draft-edit", Input)
|
||||
box.value = draft["body"]
|
||||
box.display = True
|
||||
box.focus()
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
event.stop()
|
||||
if event.input.id != "draft-edit" or not self._editing:
|
||||
return
|
||||
drafts_db.update_body(self.con, self._editing, event.value)
|
||||
self._editing = None
|
||||
event.input.display = False
|
||||
self.query_one("#draft-list", ListView).focus()
|
||||
self.call_later(self._refresh)
|
||||
|
||||
async def action_send(self) -> None:
|
||||
draft = self._current()
|
||||
app: VortexApp = self.app
|
||||
if not draft or not app.client:
|
||||
return
|
||||
resp = await app.client.room_send(
|
||||
draft["room_id"],
|
||||
"m.room.message",
|
||||
{"msgtype": "m.text", "body": draft["body"]},
|
||||
ignore_unverified_devices=True,
|
||||
)
|
||||
drafts_db.mark(self.con, draft["draft_id"], "sent", getattr(resp, "event_id", None))
|
||||
await self._refresh()
|
||||
|
||||
def action_discard(self) -> None:
|
||||
draft = self._current()
|
||||
if draft:
|
||||
drafts_db.mark(self.con, draft["draft_id"], "discarded")
|
||||
self.call_later(self._refresh)
|
||||
|
||||
def action_back(self) -> None:
|
||||
self.con.close()
|
||||
self.dismiss()
|
||||
|
||||
|
||||
class VortexApp(App):
|
||||
"""Three panes: room list | timeline over input, plus footer bindings."""
|
||||
|
||||
CSS = """
|
||||
#rooms { width: 36; border-right: solid $accent; }
|
||||
#timeline { height: 1fr; }
|
||||
#bridge-help, #drafts-help { padding: 0 1; color: $text-muted; }
|
||||
"""
|
||||
TITLE = "Vortex"
|
||||
SUB_TITLE = "/reply <text> replies to the last message in the room"
|
||||
BINDINGS = [
|
||||
Binding("q", "quit", "Quit"),
|
||||
Binding("b", "bridges", "Bridges"),
|
||||
Binding("d", "drafts", "Drafts"),
|
||||
Binding("tab", "focus_next", "Cycle focus"),
|
||||
]
|
||||
|
||||
def __init__(self, creds: dict):
|
||||
super().__init__()
|
||||
self.creds = creds
|
||||
self.client: AsyncClient | None = None
|
||||
self.domain = creds["user_id"].split(":", 1)[1]
|
||||
self.selected_room: str | None = None
|
||||
self.pending_select: str | None = None
|
||||
self.timelines: dict[str, list[str]] = {}
|
||||
self.last_event: dict[str, str] = {}
|
||||
self.unread: dict[str, int] = {}
|
||||
self.first_sync = False
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Horizontal():
|
||||
yield ListView(id="rooms")
|
||||
with Vertical():
|
||||
yield RichLog(id="timeline", wrap=True, markup=False, highlight=False)
|
||||
yield Input(
|
||||
placeholder="Message (Enter sends, /reply <text> replies)", id="msg"
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.run_worker(self._connect_and_sync(), exclusive=True)
|
||||
|
||||
# ---- matrix -----------------------------------------------------------
|
||||
async def _connect_and_sync(self) -> None:
|
||||
log = self.query_one("#timeline", RichLog)
|
||||
log.write("Connecting...")
|
||||
try:
|
||||
self.client = await build_client(self.creds)
|
||||
except Exception as exc: # surface login problems in the UI
|
||||
log.write(f"Login failed: {exc}")
|
||||
return
|
||||
self.client.add_event_callback(
|
||||
self._on_event, (RoomMessageText, RoomMessageNotice, RoomMessageMedia)
|
||||
)
|
||||
self.client.add_response_callback(self._on_sync, SyncResponse)
|
||||
log.write(
|
||||
f"Logged in as {self.client.user_id} ({self.client.device_id}). Syncing..."
|
||||
)
|
||||
await self.client.sync_forever(timeout=30000, full_state=True)
|
||||
|
||||
async def _on_sync(self, _response) -> None:
|
||||
self.first_sync = True
|
||||
await self._refresh_rooms()
|
||||
if self.pending_select and self.pending_select in self.client.rooms:
|
||||
rid, self.pending_select = self.pending_select, None
|
||||
self._select_room(rid)
|
||||
|
||||
async def _on_event(self, room: MatrixRoom, event) -> None:
|
||||
line = self._format_event(room, event)
|
||||
self.timelines.setdefault(room.room_id, []).append(line)
|
||||
self.last_event[room.room_id] = event.event_id
|
||||
if room.room_id == self.selected_room:
|
||||
self.query_one("#timeline", RichLog).write(line)
|
||||
elif self.first_sync:
|
||||
self.unread[room.room_id] = self.unread.get(room.room_id, 0) + 1
|
||||
await self._refresh_rooms()
|
||||
|
||||
def _format_event(self, room: MatrixRoom, event) -> str:
|
||||
sender = room.user_name(event.sender) or event.sender
|
||||
if isinstance(event, RoomMessageMedia):
|
||||
path = (event.url or "")[len("mxc://"):]
|
||||
url = f"{self.client.homeserver}/_matrix/media/v3/download/{path}"
|
||||
return f"<{sender}> [file] {event.body}: {url}"
|
||||
body = event.body or ""
|
||||
is_bot = any(event.sender == f"@{n}bot:{self.domain}" for n in NETWORKS)
|
||||
if looks_like_qr_payload(body, is_bot):
|
||||
return f"<{sender}> QR for {body}\n{render_qr(body)}"
|
||||
return f"<{sender}> {body}"
|
||||
|
||||
# ---- rooms ------------------------------------------------------------
|
||||
def room_network(self, room: MatrixRoom) -> str:
|
||||
for uid in room.users:
|
||||
for net in NETWORKS:
|
||||
if uid.startswith(f"@{net}_") or uid == f"@{net}bot:{self.domain}":
|
||||
return net
|
||||
return "matrix"
|
||||
|
||||
def find_bot_dm(self, bot: str) -> str | None:
|
||||
# ponytail: "DM" = bot is a member and room has <=2 members
|
||||
for room in self.client.rooms.values():
|
||||
if bot in room.users and len(room.users) <= 2:
|
||||
return room.room_id
|
||||
return None
|
||||
|
||||
def _room_label(self, room: MatrixRoom) -> str:
|
||||
n = self.unread.get(room.room_id, 0)
|
||||
badge = f" ({n})" if n else ""
|
||||
return f"[{LABELS[self.room_network(room)]}] {room.display_name or room.room_id}{badge}"
|
||||
|
||||
async def _refresh_rooms(self) -> None:
|
||||
lv = self.query_one("#rooms", ListView)
|
||||
rooms = sorted(
|
||||
self.client.rooms.values(),
|
||||
key=lambda r: (self.room_network(r), (r.display_name or r.room_id).lower()),
|
||||
)
|
||||
items = []
|
||||
for room in rooms:
|
||||
item = ListItem(Label(self._room_label(room)))
|
||||
item.room_id = room.room_id
|
||||
items.append(item)
|
||||
idx = next(
|
||||
(i for i, r in enumerate(rooms) if r.room_id == self.selected_room), None
|
||||
)
|
||||
await lv.clear()
|
||||
await lv.extend(items)
|
||||
if idx is not None:
|
||||
lv.index = idx
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
rid = getattr(event.item, "room_id", None)
|
||||
if rid:
|
||||
self._select_room(rid)
|
||||
|
||||
def _select_room(self, rid: str) -> None:
|
||||
self.selected_room = rid
|
||||
self.unread.pop(rid, None)
|
||||
log = self.query_one("#timeline", RichLog)
|
||||
log.clear()
|
||||
for line in self.timelines.get(rid, []):
|
||||
log.write(line)
|
||||
room = self.client.rooms.get(rid) if self.client else None
|
||||
if room: # update badge in place, no full rebuild
|
||||
for item in self.query_one("#rooms", ListView).children:
|
||||
if getattr(item, "room_id", None) == rid:
|
||||
item.query_one(Label).update(self._room_label(room))
|
||||
self.query_one("#msg", Input).focus()
|
||||
|
||||
# ---- sending ----------------------------------------------------------
|
||||
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
if event.input.id != "msg":
|
||||
return
|
||||
text = event.value.strip()
|
||||
event.input.value = ""
|
||||
if not text or not self.client or not self.selected_room:
|
||||
return
|
||||
content: dict = {"msgtype": "m.text", "body": text}
|
||||
if text.startswith("/reply "):
|
||||
content["body"] = text[len("/reply "):]
|
||||
last = self.last_event.get(self.selected_room)
|
||||
if last:
|
||||
content["m.relates_to"] = {"m.in_reply_to": {"event_id": last}}
|
||||
await self.client.room_send(
|
||||
self.selected_room,
|
||||
"m.room.message",
|
||||
content,
|
||||
ignore_unverified_devices=True,
|
||||
)
|
||||
|
||||
# ---- actions ----------------------------------------------------------
|
||||
def action_bridges(self) -> None:
|
||||
if self.client:
|
||||
self.push_screen(BridgeScreen(), self._after_bridge)
|
||||
|
||||
def _after_bridge(self, room_id: str | None) -> None:
|
||||
if not room_id:
|
||||
return
|
||||
if self.client and room_id in self.client.rooms:
|
||||
self._select_room(room_id)
|
||||
else:
|
||||
self.pending_select = room_id
|
||||
self.notify("DM created - it will open once it syncs.")
|
||||
|
||||
def action_drafts(self) -> None:
|
||||
self.push_screen(DraftsScreen())
|
||||
|
||||
async def action_quit(self) -> None:
|
||||
if self.client:
|
||||
await self.client.close()
|
||||
self.exit()
|
||||
|
||||
|
||||
def run() -> None:
|
||||
creds = load_credentials()
|
||||
VortexApp(creds).run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Render QR-ish message payloads as unicode half-block QR codes."""
|
||||
|
||||
import qrcode
|
||||
|
||||
QR_SCHEMES = ("https://wa.me/", "gmessages://")
|
||||
|
||||
|
||||
def looks_like_qr_payload(body: str, sender_is_bridge_bot: bool) -> bool:
|
||||
"""Deliberately simple heuristic:
|
||||
|
||||
- message starts with a known pairing scheme (whatsapp / gmessages), or
|
||||
- a bridge-bot message that is a single URL/token: 20-199 chars, no
|
||||
whitespace, and either contains '://' or is alnum-ish (pairing code).
|
||||
"""
|
||||
body = body.strip()
|
||||
if body.startswith(QR_SCHEMES):
|
||||
return True
|
||||
if sender_is_bridge_bot and 20 <= len(body) < 200 and not any(c.isspace() for c in body):
|
||||
return "://" in body or body.replace("-", "").replace("_", "").isalnum()
|
||||
return False
|
||||
|
||||
|
||||
def render_qr(text: str) -> str:
|
||||
"""QR code as text, two matrix rows per output line via half blocks."""
|
||||
qr = qrcode.QRCode(border=1)
|
||||
qr.add_data(text)
|
||||
qr.make(fit=True)
|
||||
matrix = qr.get_matrix()
|
||||
if len(matrix) % 2:
|
||||
matrix.append([False] * len(matrix[0]))
|
||||
chars = {(True, True): "█", (True, False): "▀", (False, True): "▄", (False, False): " "}
|
||||
return "\n".join(
|
||||
"".join(chars[(top[x], bottom[x])] for x in range(len(top)))
|
||||
for top, bottom in zip(matrix[::2], matrix[1::2])
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Credentials loading and Matrix client construction."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from nio import AsyncClient, AsyncClientConfig, LoginResponse
|
||||
|
||||
|
||||
def store_dir() -> Path:
|
||||
return Path(os.environ.get("STORE_DIR", "/shared/tui-store"))
|
||||
|
||||
|
||||
def load_credentials(retries: int = 10, delay: float = 3.0) -> dict:
|
||||
"""Read CREDENTIALS_FILE, waiting a bit in case bootstrap is still provisioning."""
|
||||
path = Path(os.environ.get("CREDENTIALS_FILE", "/shared/credentials.json"))
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
return json.loads(path.read_text())
|
||||
except (OSError, json.JSONDecodeError):
|
||||
if attempt < retries - 1:
|
||||
print(
|
||||
f"vortex-tui: waiting for {path} (bootstrap still provisioning?)...",
|
||||
file=sys.stderr,
|
||||
)
|
||||
time.sleep(delay)
|
||||
sys.exit(
|
||||
f"vortex-tui: credentials file {path} missing or invalid after "
|
||||
f"{retries} attempts. Run the bootstrap first, or set CREDENTIALS_FILE."
|
||||
)
|
||||
|
||||
|
||||
async def build_client(creds: dict) -> AsyncClient:
|
||||
"""Return a logged-in AsyncClient with its own persisted E2EE device.
|
||||
|
||||
Order: STORE_DIR/session.json (our own device) -> password login (persist
|
||||
new device) -> bootstrap access_token fallback.
|
||||
"""
|
||||
store = store_dir()
|
||||
store.mkdir(parents=True, exist_ok=True)
|
||||
homeserver = (
|
||||
os.environ.get("MATRIX_HOMESERVER")
|
||||
or creds.get("homeserver")
|
||||
or "http://localhost:8008"
|
||||
)
|
||||
user_id = creds["user_id"]
|
||||
# encryption_enabled defaults to True only when olm is importable,
|
||||
# so this works with or without the [e2e] extra installed.
|
||||
client = AsyncClient(
|
||||
homeserver,
|
||||
user_id,
|
||||
store_path=str(store),
|
||||
config=AsyncClientConfig(store_sync_tokens=True),
|
||||
)
|
||||
session_file = store / "session.json"
|
||||
if session_file.exists():
|
||||
s = json.loads(session_file.read_text())
|
||||
client.restore_login(s["user_id"], s["device_id"], s["access_token"])
|
||||
return client
|
||||
if creds.get("password"):
|
||||
resp = await client.login(creds["password"], device_name="vortex-tui")
|
||||
if isinstance(resp, LoginResponse):
|
||||
session_file.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"user_id": resp.user_id,
|
||||
"device_id": resp.device_id,
|
||||
"access_token": resp.access_token,
|
||||
}
|
||||
)
|
||||
)
|
||||
return client
|
||||
if creds.get("access_token"):
|
||||
# Fall back to the bootstrap token (shares the bootstrap device).
|
||||
client.restore_login(
|
||||
user_id, creds.get("device_id") or "VORTEXTUI", creds["access_token"]
|
||||
)
|
||||
return client
|
||||
await client.close()
|
||||
raise RuntimeError("no usable password or access_token in credentials file")
|
||||
@@ -0,0 +1,22 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "vortex-tui"
|
||||
version = "0.1.0"
|
||||
description = "Unified Matrix messaging TUI (Synapse + mautrix bridges)"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"textual>=0.80",
|
||||
"matrix-nio[e2e]>=0.25",
|
||||
"qrcode>=7",
|
||||
"watchfiles>=0.21",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
vortex-tui = "vortex_tui.main:run"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = { "" = "app" }
|
||||
packages = ["vortex_tui"]
|
||||
Reference in New Issue
Block a user