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:
Prad Nukala
2026-07-08 12:13:13 -04:00
co-authored by Claude Fable 5
commit d57086cd00
29 changed files with 5602 additions and 0 deletions
+39
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
.env
shared/drafts/*.db*
**/__pycache__/
*.egg-info/
.venv*/
+28
View File
@@ -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
+149
View File
@@ -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).
+5
View File
@@ -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"]
+181
View File
@@ -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"
+55
View File
@@ -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"
+381
View File
@@ -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
+534
View File
@@ -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
+520
View File
@@ -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
+581
View File
@@ -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
+641
View File
@@ -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
+533
View File
@@ -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
+640
View File
@@ -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
+203
View File
@@ -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:
+19
View File
@@ -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"]
+22
View File
@@ -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"]
+3
View File
@@ -0,0 +1,3 @@
"""Vortex MCP server package."""
__version__ = "0.1.0"
+319
View File
@@ -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()
+101
View File
@@ -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"
View File
+60
View File
@@ -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
+19
View File
@@ -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"]
+3
View File
@@ -0,0 +1,3 @@
"""Vortex TUI - unified Matrix messaging client."""
__version__ = "0.1.0"
+53
View File
@@ -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()
+369
View File
@@ -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()
+35
View File
@@ -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])
)
+82
View File
@@ -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")
+22
View 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"]