push push push

This commit is contained in:
lily 2026-07-31 22:39:01 -04:00
parent 89486478fc
commit e284fa6d6d
11 changed files with 887 additions and 25 deletions

BIN
..env.un~ Normal file

Binary file not shown.

11
.env.example Normal file
View file

@ -0,0 +1,11 @@
HS_URL=http://host.docker.internal:8008
HS_TOKEN=
AS_TOKEN=
BOT_MXID=@_discord_bot:example.com
DISCORD_TOKEN=
DISCORD_CHANNEL_ID=
MATRIX_ROOM_ID=!roomid:example.com
# optional, defaults to bridge.db
DB_PATH=/data/bridge.db

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
/target
.env
*.db
/data

650
Cargo.lock generated
View file

@ -2,12 +2,38 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "anyhow"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
dependencies = [
"serde",
]
[[package]]
name = "async-trait"
version = "0.1.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -78,12 +104,27 @@ version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.12.1"
@ -119,8 +160,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core",
"cpufeatures 0.3.0",
"rand_core 0.10.1",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
@ -132,6 +182,50 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "data-encoding"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"serde_core",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.7"
@ -153,12 +247,40 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@ -168,6 +290,20 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.33"
@ -175,6 +311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@ -183,6 +320,29 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
[[package]]
name = "futures-io"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
[[package]]
name = "futures-macro"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "futures-sink"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
[[package]]
name = "futures-task"
version = "0.3.33"
@ -195,12 +355,27 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
@ -224,10 +399,37 @@ dependencies = [
"js-sys",
"libc",
"r-efi",
"rand_core",
"rand_core 0.10.1",
"wasm-bindgen",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248"
dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "http"
version = "1.5.0"
@ -303,11 +505,11 @@ dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls 0.23.43",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.4",
"tower-service",
"webpki-roots",
"webpki-roots 1.0.9",
]
[[package]]
@ -471,6 +673,17 @@ version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libsqlite3-sys"
version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "litemap"
version = "0.8.2"
@ -511,8 +724,10 @@ dependencies = [
"anyhow",
"axum",
"reqwest",
"rusqlite",
"serde",
"serde_json",
"serenity",
"tokio",
"tracing",
"tracing-subscriber",
@ -532,6 +747,26 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "mio"
version = "1.2.2"
@ -552,6 +787,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "once_cell"
version = "1.21.4"
@ -593,6 +834,12 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "potential_utf"
version = "0.1.5"
@ -602,6 +849,21 @@ dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
@ -623,9 +885,9 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"rustls 0.23.43",
"socket2",
"thiserror",
"thiserror 2.0.19",
"tokio",
"tracing",
"web-time",
@ -640,14 +902,14 @@ dependencies = [
"bytes",
"getrandom 0.4.3",
"lru-slab",
"rand",
"rand 0.10.2",
"rand_pcg",
"ring",
"rustc-hash",
"rustls",
"rustls 0.23.43",
"rustls-pki-types",
"slab",
"thiserror",
"thiserror 2.0.19",
"tinyvec",
"tracing",
"web-time",
@ -682,6 +944,17 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.10.2"
@ -690,7 +963,26 @@ checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
"chacha20",
"getrandom 0.4.3",
"rand_core",
"rand_core 0.10.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
]
[[package]]
@ -705,7 +997,7 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
"rand_core",
"rand_core 0.10.1",
]
[[package]]
@ -726,6 +1018,7 @@ dependencies = [
"base64",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
@ -734,25 +1027,28 @@ dependencies = [
"hyper-util",
"js-sys",
"log",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls 0.23.43",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tokio-rustls 0.26.4",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
"webpki-roots 1.0.9",
]
[[package]]
@ -769,12 +1065,51 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rsqlite-vfs"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
dependencies = [
"hashbrown 0.16.1",
"thiserror 2.0.19",
]
[[package]]
name = "rusqlite"
version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
"sqlite-wasm-rs",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustls"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
dependencies = [
"log",
"ring",
"rustls-pki-types",
"rustls-webpki 0.102.8",
"subtle",
"zeroize",
]
[[package]]
name = "rustls"
version = "0.23.43"
@ -784,7 +1119,7 @@ dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"rustls-webpki 0.103.13",
"subtle",
"zeroize",
]
@ -799,6 +1134,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
@ -828,6 +1174,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "secrecy"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
dependencies = [
"serde",
"zeroize",
]
[[package]]
name = "serde"
version = "1.0.229"
@ -847,6 +1203,15 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde_cow"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
@ -894,6 +1259,45 @@ dependencies = [
"serde",
]
[[package]]
name = "serenity"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bde37f42765dfdc34e2a039e0c84afbf79a3101c1941763b0beb816c2f17541"
dependencies = [
"arrayvec",
"async-trait",
"base64",
"bitflags",
"bytes",
"flate2",
"futures",
"mime_guess",
"percent-encoding",
"reqwest",
"secrecy",
"serde",
"serde_cow",
"serde_json",
"time",
"tokio",
"tokio-tungstenite",
"tracing",
"typemap_rev",
"url",
]
[[package]]
name = "sha1"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@ -919,6 +1323,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "slab"
version = "0.4.12"
@ -941,6 +1351,18 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
dependencies = [
"cc",
"js-sys",
"rsqlite-vfs",
"wasm-bindgen",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -995,13 +1417,33 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
dependencies = [
"thiserror-impl",
"thiserror-impl 2.0.19",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
@ -1024,6 +1466,36 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "time"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
dependencies = [
"deranged",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.3"
@ -1077,13 +1549,53 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"rustls 0.23.43",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
dependencies = [
"futures-util",
"log",
"rustls 0.22.4",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tungstenite",
"webpki-roots 0.26.11",
]
[[package]]
name = "tokio-util"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
@ -1197,6 +1709,45 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.8.7",
"rustls 0.22.4",
"rustls-pki-types",
"sha1",
"thiserror 1.0.69",
"url",
"utf-8",
]
[[package]]
name = "typemap_rev"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998"
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.24"
@ -1219,6 +1770,7 @@ dependencies = [
"idna",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
@ -1227,6 +1779,12 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -1250,6 +1808,18 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
version = "0.3.1"
@ -1320,6 +1890,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.103"
@ -1340,6 +1923,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.9",
]
[[package]]
name = "webpki-roots"
version = "1.0.9"
@ -1466,6 +2058,26 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "zerofrom"
version = "0.1.8"

View file

@ -13,4 +13,6 @@ tracing-subscriber = "0.3"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
urlencoding = "2"
uuid = { version = "1", features = ["v4"] }
anyhow = "1"
anyhow = "1"
serenity = { version = "0.12.5", default-features = false, features = ["client", "gateway", "rustls_backend", "model"] }
rusqlite = { version = "0.40.1", features = ["bundled"] }

View file

@ -6,4 +6,6 @@ services:
- "9000:9000"
extra_hosts:
- "host.docker.internal:host-gateway"
env_file: .env
env_file: .env
volumes:
- ./data:/data

58
src/bridge.rs Normal file
View file

@ -0,0 +1,58 @@
use anyhow::Result;
use serenity::model::channel::Message as DiscordMessage;
use crate::AppState;
/// Looks up (or creates) the matrix ghost puppet for a discord user, registering
/// it via the appservice and joining it to the bridged room if needed.
pub async fn get_or_create_ghost(
state: &AppState,
discord_user_id: &str,
display_name: &str,
) -> Result<String> {
if let Some((mxid, joined)) = state.db.get_ghost(discord_user_id).await? {
if !joined {
state.matrix.join_room(&state.matrix_room_id, &mxid).await?;
state.db.mark_joined(discord_user_id).await?;
}
return Ok(mxid);
}
let localpart = format!("_discord_{}", discord_user_id);
let mxid = format!("@{}:{}", localpart, state.matrix_domain);
state.matrix.register_ghost(&localpart).await?;
if let Err(e) = state.matrix.set_display_name(&mxid, display_name).await {
tracing::warn!("failed to set ghost display name for {}: {}", mxid, e);
}
state.matrix.join_room(&state.matrix_room_id, &mxid).await?;
state.db.insert_ghost(discord_user_id, &mxid).await?;
state.db.mark_joined(discord_user_id).await?;
tracing::info!(
"registered ghost {} for discord user {}",
mxid,
discord_user_id
);
Ok(mxid)
}
/// Discord -> Matrix: puppet the sending discord user via their ghost.
pub async fn forward_to_matrix(state: &AppState, msg: &DiscordMessage) -> Result<()> {
let mxid = get_or_create_ghost(state, &msg.author.id.to_string(), &msg.author.name).await?;
state
.matrix
.send_text(&state.matrix_room_id, &mxid, &msg.content)
.await?;
Ok(())
}
/// Matrix -> Discord: relay as the bridge bot, prefixed with the matrix sender.
pub async fn forward_to_discord(state: &AppState, sender: &str, body: &str) -> Result<()> {
state
.discord_channel_id
.say(&state.discord_http, format!("**{}**: {}", sender, body))
.await?;
Ok(())
}

53
src/db.rs Normal file
View file

@ -0,0 +1,53 @@
use rusqlite::{Connection, OptionalExtension, params};
use tokio::sync::Mutex;
pub struct Db {
conn: Mutex<Connection>,
}
impl Db {
pub fn open(path: &str) -> rusqlite::Result<Self> {
let conn = Connection::open(path)?;
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS ghosts (
discord_user_id TEXT PRIMARY KEY,
mxid TEXT NOT NULL,
joined_room INTEGER NOT NULL DEFAULT 0
);",
)?;
Ok(Self {
conn: Mutex::new(conn),
})
}
pub async fn get_ghost(
&self,
discord_user_id: &str,
) -> rusqlite::Result<Option<(String, bool)>> {
let conn = self.conn.lock().await;
conn.query_row(
"SELECT mxid, joined_room FROM ghosts WHERE discord_user_id = ?1",
params![discord_user_id],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)? != 0)),
)
.optional()
}
pub async fn insert_ghost(&self, discord_user_id: &str, mxid: &str) -> rusqlite::Result<()> {
let conn = self.conn.lock().await;
conn.execute(
"INSERT OR IGNORE INTO ghosts (discord_user_id, mxid, joined_room) VALUES (?1, ?2, 0)",
params![discord_user_id, mxid],
)?;
Ok(())
}
pub async fn mark_joined(&self, discord_user_id: &str) -> rusqlite::Result<()> {
let conn = self.conn.lock().await;
conn.execute(
"UPDATE ghosts SET joined_room = 1 WHERE discord_user_id = ?1",
params![discord_user_id],
)?;
Ok(())
}
}

43
src/discord.rs Normal file
View file

@ -0,0 +1,43 @@
use std::sync::Arc;
use serenity::async_trait;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
use crate::AppState;
use crate::bridge;
pub struct Handler {
pub state: Arc<AppState>,
}
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _ctx: Context, ready: Ready) {
tracing::info!("discord bot connected as {}", ready.user.name);
}
async fn message(&self, _ctx: Context, msg: Message) {
if msg.author.bot {
return;
}
tracing::info!(
"discord [#{}] {}: {}",
msg.channel_id,
msg.author.name,
msg.content
);
if msg.channel_id != self.state.discord_channel_id {
return;
}
let state = self.state.clone();
tokio::spawn(async move {
if let Err(e) = bridge::forward_to_matrix(&state, &msg).await {
tracing::error!("failed to bridge discord->matrix: {}", e);
}
});
}
}

View file

@ -6,6 +6,9 @@ use axum::{
};
use serde::Deserialize;
use serde_json::{Value, json};
use serenity::http::Http;
use serenity::model::id::ChannelId;
use serenity::prelude::*;
use std::sync::Arc;
#[derive(Deserialize)]
@ -18,6 +21,9 @@ struct Transaction {
events: Vec<Value>,
}
mod bridge;
mod db;
mod discord;
mod matrix;
use matrix::MatrixClient;
@ -31,6 +37,11 @@ struct AppState {
hs_token: String,
bot_mxid: String,
matrix: MatrixClient,
matrix_room_id: String,
matrix_domain: String,
discord_http: Arc<Http>,
discord_channel_id: ChannelId,
db: db::Db,
}
#[tokio::main]
@ -40,11 +51,45 @@ async fn main() {
let hs_token = env_var("HS_TOKEN");
let as_token = env_var("AS_TOKEN");
let bot_mxid = env_var("BOT_MXID");
let discord_token = env_var("DISCORD_TOKEN");
let discord_channel_id: ChannelId = env_var("DISCORD_CHANNEL_ID")
.parse()
.expect("DISCORD_CHANNEL_ID must be a numeric discord channel id");
let matrix_room_id = env_var("MATRIX_ROOM_ID");
let db_path = env::var("DB_PATH").unwrap_or_else(|_| "bridge.db".to_string());
let matrix_domain = bot_mxid
.split(':')
.nth(1)
.expect("BOT_MXID must be in the form @user:domain")
.to_string();
let db = db::Db::open(&db_path).expect("failed to open sqlite db");
let discord_http = Arc::new(Http::new(&discord_token));
let state = Arc::new(AppState {
hs_token,
bot_mxid,
matrix: MatrixClient::new(hs_url, as_token),
matrix_room_id,
matrix_domain,
discord_http,
discord_channel_id,
db,
});
let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
let mut discord_client = Client::builder(&discord_token, intents)
.event_handler(discord::Handler {
state: state.clone(),
})
.await
.expect("failed to build discord client");
tokio::spawn(async move {
if let Err(e) = discord_client.start().await {
tracing::error!("discord client error: {}", e);
}
});
let app = Router::new()
@ -74,7 +119,7 @@ async fn transactions(
check_auth(&auth, &state.hs_token)?;
tracing::info!("txn {} with {} events", txn_id, body.events.len());
for ev in &body.events {
if let Err(e) = handle_event(&state.matrix, &state.bot_mxid, ev).await {
if let Err(e) = handle_event(&state, ev).await {
tracing::error!("error handling event: {}", e);
}
}
@ -101,17 +146,36 @@ async fn query_alias(
Err(StatusCode::NOT_FOUND)
}
async fn handle_event(client: &MatrixClient, bot_mxid: &str, ev: &Value) -> anyhow::Result<()> {
async fn handle_event(state: &AppState, ev: &Value) -> anyhow::Result<()> {
let ev_type = ev["type"].as_str().unwrap_or("");
let room_id = ev["room_id"].as_str().unwrap_or("");
let sender = ev["sender"].as_str().unwrap_or("");
let bot_mxid = state.bot_mxid.as_str();
if ev_type == "m.room.member"
&& ev["state_key"].as_str() == Some(bot_mxid)
&& ev["content"]["membership"].as_str() == Some("invite")
{
tracing::info!("joining {} as {}", room_id, bot_mxid);
client.join_room(room_id, bot_mxid).await?;
client.send_text(room_id, bot_mxid, "hi, i'm alive").await?;
state.matrix.join_room(room_id, bot_mxid).await?;
state
.matrix
.send_text(room_id, bot_mxid, "hi, i'm alive")
.await?;
}
// Matrix -> Discord: relay messages from the bridged room, skipping our own
// ghosts and the bridge bot itself to avoid an echo loop.
if ev_type == "m.room.message"
&& room_id == state.matrix_room_id
&& sender != bot_mxid
&& !sender.starts_with("@_discord_")
{
let body = ev["content"]["body"].as_str().unwrap_or("");
if !body.is_empty() {
bridge::forward_to_discord(state, sender, body).await?;
}
}
Ok(())
}

View file

@ -71,6 +71,21 @@ impl MatrixClient {
Ok(res["event_id"].as_str().unwrap_or("").to_string())
}
pub async fn set_display_name(&self, user_id: &str, name: &str) -> Result<()> {
let path = format!(
"/_matrix/client/v3/profile/{}/displayname",
urlencoding::encode(user_id)
);
self.req(
reqwest::Method::PUT,
&path,
Some(user_id),
Some(json!({ "displayname": name })),
)
.await?;
Ok(())
}
pub async fn register_ghost(&self, localpart: &str) -> Result<()> {
let body = json!({
"type": "m.login.application_service",