mirror of
https://github.com/prdlk/vortex.git
synced 2026-08-02 17:31:41 +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