Compare commits

...

8 Commits

15 changed files with 551 additions and 534 deletions

389
Cargo.lock generated
View File

@@ -663,12 +663,6 @@ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@@ -849,6 +843,27 @@ dependencies = [
"tiny-keccak", "tiny-keccak",
] ]
[[package]]
name = "const_format"
version = "0.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e"
dependencies = [
"const_format_proc_macros",
"konst",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@@ -1744,12 +1759,15 @@ dependencies = [
"rhai", "rhai",
"rustls 0.23.35", "rustls 0.23.35",
"secrecy 0.10.3", "secrecy 0.10.3",
"shadow-rs",
"snafu", "snafu",
"songbird", "songbird",
"strum 0.28.0", "strum 0.28.0",
"time", "time",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tokio-websockets 0.11.4",
"tokio-websockets 0.13.2",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"twilight-gateway", "twilight-gateway",
@@ -2044,6 +2062,19 @@ dependencies = [
"polyval", "polyval",
] ]
[[package]]
name = "git2"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
dependencies = [
"bitflags 2.10.0",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.3" version = "0.3.3"
@@ -2742,6 +2773,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "is_debug"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe266d2e243c931d8190177f20bf7f24eed45e96f39e87dc49a27b32d12d407"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.2" version = "1.70.2"
@@ -2806,50 +2843,6 @@ dependencies = [
"jiff-tzdb", "jiff-tzdb",
] ]
[[package]]
name = "jni"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
"cesu8",
"cfg-if",
"combine",
"jni-sys 0.3.1",
"log",
"thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
[[package]]
name = "jni-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
"jni-sys 0.4.1",
]
[[package]]
name = "jni-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
dependencies = [
"jni-sys-macros",
]
[[package]]
name = "jni-sys-macros"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
dependencies = [
"quote",
"syn 2.0.111",
]
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.34" version = "0.1.34"
@@ -2899,6 +2892,21 @@ dependencies = [
"elliptic-curve", "elliptic-curve",
] ]
[[package]]
name = "konst"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb"
dependencies = [
"konst_macro_rules",
]
[[package]]
name = "konst_macro_rules"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
[[package]] [[package]]
name = "kv-log-macro" name = "kv-log-macro"
version = "1.0.7" version = "1.0.7"
@@ -3168,6 +3176,18 @@ dependencies = [
"rand 0.9.2", "rand 0.9.2",
] ]
[[package]]
name = "libgit2-sys"
version = "0.18.3+1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]] [[package]]
name = "libloading" name = "libloading"
version = "0.9.0" version = "0.9.0"
@@ -3216,6 +3236,18 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "libz-sys"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.6" version = "0.5.6"
@@ -3706,7 +3738,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]] [[package]]
name = "opendal" name = "opendal"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"ctor", "ctor",
"opendal-core", "opendal-core",
@@ -3752,7 +3784,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-core" name = "opendal-core"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@@ -3768,7 +3800,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"quick-xml 0.38.4", "quick-xml 0.38.4",
"reqsign-core", "reqsign-core",
"reqwest 0.13.2", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@@ -3780,7 +3812,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-layer-concurrent-limit" name = "opendal-layer-concurrent-limit"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"futures", "futures",
"http", "http",
@@ -3791,7 +3823,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-layer-logging" name = "opendal-layer-logging"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"log", "log",
"opendal-core", "opendal-core",
@@ -3800,7 +3832,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-layer-retry" name = "opendal-layer-retry"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"backon", "backon",
"log", "log",
@@ -3810,7 +3842,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-layer-timeout" name = "opendal-layer-timeout"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"tokio", "tokio",
@@ -3819,7 +3851,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-aliyun-drive" name = "opendal-service-aliyun-drive"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3833,7 +3865,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-alluxio" name = "opendal-service-alluxio"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3846,7 +3878,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-azblob" name = "opendal-service-azblob"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -3866,7 +3898,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-azdls" name = "opendal-service-azdls"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3884,7 +3916,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-azfile" name = "opendal-service-azfile"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3901,7 +3933,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-azure-common" name = "opendal-service-azure-common"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"http", "http",
"opendal-core", "opendal-core",
@@ -3910,7 +3942,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-b2" name = "opendal-service-b2"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3924,7 +3956,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-cacache" name = "opendal-service-cacache"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"cacache", "cacache",
@@ -3935,7 +3967,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-d1" name = "opendal-service-d1"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3947,7 +3979,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-dashmap" name = "opendal-service-dashmap"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"dashmap 6.1.0", "dashmap 6.1.0",
"log", "log",
@@ -3958,7 +3990,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-dbfs" name = "opendal-service-dbfs"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -3972,7 +4004,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-dropbox" name = "opendal-service-dropbox"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -3985,7 +4017,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-etcd" name = "opendal-service-etcd"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"etcd-client", "etcd-client",
"fastpool", "fastpool",
@@ -3997,7 +4029,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-fs" name = "opendal-service-fs"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"log", "log",
@@ -4010,7 +4042,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-ftp" name = "opendal-service-ftp"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"fastpool", "fastpool",
@@ -4028,7 +4060,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-gcs" name = "opendal-service-gcs"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -4048,7 +4080,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-gdrive" name = "opendal-service-gdrive"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -4062,7 +4094,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-ghac" name = "opendal-service-ghac"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"ghac", "ghac",
@@ -4081,7 +4113,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-github" name = "opendal-service-github"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -4095,7 +4127,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-gridfs" name = "opendal-service-gridfs"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"futures", "futures",
"mea", "mea",
@@ -4107,7 +4139,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-hdfs-native" name = "opendal-service-hdfs-native"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
@@ -4120,7 +4152,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-ipmfs" name = "opendal-service-ipmfs"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -4133,7 +4165,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-mini-moka" name = "opendal-service-mini-moka"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"log", "log",
"mini-moka", "mini-moka",
@@ -4144,7 +4176,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-moka" name = "opendal-service-moka"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"log", "log",
"moka", "moka",
@@ -4155,7 +4187,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-mongodb" name = "opendal-service-mongodb"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"mea", "mea",
"mongodb", "mongodb",
@@ -4166,7 +4198,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-mysql" name = "opendal-service-mysql"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"mea", "mea",
"opendal-core", "opendal-core",
@@ -4177,7 +4209,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-pcloud" name = "opendal-service-pcloud"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
@@ -4190,7 +4222,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-persy" name = "opendal-service-persy"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"persy", "persy",
@@ -4200,7 +4232,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-postgresql" name = "opendal-service-postgresql"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"mea", "mea",
"opendal-core", "opendal-core",
@@ -4211,7 +4243,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-redb" name = "opendal-service-redb"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"redb", "redb",
@@ -4221,7 +4253,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-redis" name = "opendal-service-redis"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"bytes", "bytes",
"fastpool", "fastpool",
@@ -4235,7 +4267,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-s3" name = "opendal-service-s3"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -4255,7 +4287,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-sled" name = "opendal-service-sled"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"opendal-core", "opendal-core",
"serde", "serde",
@@ -4265,7 +4297,7 @@ dependencies = [
[[package]] [[package]]
name = "opendal-service-webdav" name = "opendal-service-webdav"
version = "0.55.0" version = "0.55.0"
source = "git+https://github.com/apache/opendal#2f3b9807fcc7f5bd94edfd9a529be526b75ed459" source = "git+https://github.com/apache/opendal?rev=ecf840b04afd2be109830b9978ba89759adfee79#ecf840b04afd2be109830b9978ba89759adfee79"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -4955,7 +4987,6 @@ version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
dependencies = [ dependencies = [
"aws-lc-rs",
"bytes", "bytes",
"getrandom 0.3.4", "getrandom 0.3.4",
"lru-slab", "lru-slab",
@@ -5369,49 +5400,11 @@ dependencies = [
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams 0.4.2", "wasm-streams",
"web-sys", "web-sys",
"webpki-roots 1.0.4", "webpki-roots 1.0.4",
] ]
[[package]]
name = "reqwest"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.35",
"rustls-pki-types",
"rustls-platform-verifier",
"sync_wrapper",
"tokio",
"tokio-rustls 0.26.4",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams 0.5.0",
"web-sys",
]
[[package]] [[package]]
name = "resolv-conf" name = "resolv-conf"
version = "0.7.6" version = "0.7.6"
@@ -5637,33 +5630,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls 0.23.35",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki 0.103.8",
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.102.8" version = "0.102.8"
@@ -5974,7 +5940,7 @@ dependencies = [
"futures", "futures",
"mime_guess", "mime_guess",
"percent-encoding", "percent-encoding",
"reqwest 0.12.28", "reqwest",
"secrecy 0.8.0", "secrecy 0.8.0",
"serde", "serde",
"serde_cow", "serde_cow",
@@ -6039,6 +6005,18 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shadow-rs"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd39b4b2077bd36e60ca28c31d494046e747759cb9b507a7d177bb64787c39e"
dependencies = [
"const_format",
"git2",
"is_debug",
"jiff",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@@ -6237,7 +6215,7 @@ dependencies = [
"parking_lot 0.12.5", "parking_lot 0.12.5",
"pin-project", "pin-project",
"rand 0.9.2", "rand 0.9.2",
"reqwest 0.12.28", "reqwest",
"ringbuf", "ringbuf",
"rubato", "rubato",
"rusty_pool", "rusty_pool",
@@ -6522,7 +6500,7 @@ dependencies = [
"futures-util", "futures-util",
"hls_m3u8", "hls_m3u8",
"patricia_tree 0.8.0", "patricia_tree 0.8.0",
"reqwest 0.12.28", "reqwest",
"tokio", "tokio",
"tracing", "tracing",
"url", "url",
@@ -7013,6 +6991,7 @@ dependencies = [
"tokio", "tokio",
"tokio-rustls 0.26.4", "tokio-rustls 0.26.4",
"tokio-util", "tokio-util",
"webpki-roots 0.26.11",
] ]
[[package]] [[package]]
@@ -7726,19 +7705,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "wasm-streams"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "wasmparser" name = "wasmparser"
version = "0.244.0" version = "0.244.0"
@@ -7771,15 +7737,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "webpki-root-certs"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.26.11" version = "0.26.11"
@@ -7956,15 +7913,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@@ -8001,21 +7949,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"
@@ -8073,12 +8006,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.48.5"
@@ -8097,12 +8024,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@@ -8121,12 +8042,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@@ -8157,12 +8072,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@@ -8181,12 +8090,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@@ -8205,12 +8108,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
@@ -8229,12 +8126,6 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"

View File

@@ -56,6 +56,7 @@ patricia_tree = "0.10.1"
rhai = "1.23.6" rhai = "1.23.6"
rustls = "0.23" rustls = "0.23"
secrecy = { version = "0.10.3", features = ["serde"] } secrecy = { version = "0.10.3", features = ["serde"] }
shadow-rs = { version = "2.0.0", default-features = false }
snafu = { version = "0.8.9", features = ["futures"] } snafu = { version = "0.8.9", features = ["futures"] }
songbird = { version = "0.6.0", default-features = false, features = [ songbird = { version = "0.6.0", default-features = false, features = [
"driver", "driver",
@@ -97,3 +98,4 @@ yoke = "0.8.2"
[build-dependencies] [build-dependencies]
capnpc = "0.25.3" capnpc = "0.25.3"
shadow-rs = "2.0.0"

View File

@@ -17,6 +17,10 @@ ARG PROTOC_VERSION=31.1-r1
RUN --mount=type=cache,sharing=locked,target=/var/cache/apk \ RUN --mount=type=cache,sharing=locked,target=/var/cache/apk \
apk add --update protoc=${PROTOC_VERSION} apk add --update protoc=${PROTOC_VERSION}
ARG ZLIB_STATIC_VERSION=1.3.2-r0
RUN --mount=type=cache,sharing=locked,target=/var/cache/apk \
apk add --update zlib-static=${ZLIB_STATIC_VERSION}
RUN \ RUN \
# This one would be nice if it worked: # This one would be nice if it worked:
# --mount=type=bind,source=.,target=/root/app \ # --mount=type=bind,source=.,target=/root/app \

View File

@@ -1,7 +1,14 @@
use shadow_rs::{BuildPattern, ShadowBuilder};
fn main() { fn main() {
capnpc::CompilerCommand::new() capnpc::CompilerCommand::new()
.file("bot.capnp") .file("bot.capnp")
.file("user.capnp") .file("user.capnp")
.run() .run()
.expect("couldn't compile capnproto schemas"); .expect("couldn't compile capnproto schemas");
ShadowBuilder::builder()
.build_pattern(BuildPattern::RealTime)
.build()
.expect("couldn't embed build time information");
} }

175
src/bot_data.rs Normal file
View File

@@ -0,0 +1,175 @@
use async_compression::futures::{bufread::BrotliDecoder, write::BrotliEncoder};
use capnp::message::TypedBuilder;
use futures::{AsyncReadExt, AsyncWriteExt};
use opendal::Operator;
use snafu::{ResultExt as _, Snafu};
use crate::{OperatorExt, bot_capnp, option_ext::OptionExt as _};
#[derive(Debug, Clone)]
pub struct BotDataManager {
operator: Operator,
}
impl BotDataManager {
pub fn new(operator: Operator) -> Self {
Self { operator }
}
}
const PATH: &str = "data.bin.brotli";
#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum WithError {
/// couldn't read data for this bot from the storage operator
ReadError { source: opendal::Error },
/// couldn't decompress the bot data from storage
DecompressionError { source: std::io::Error },
/// couldn't deserialize the bot data
DeserializeError { source: capnp::Error },
}
impl BotDataManager {
pub async fn with<R>(
&self,
f: impl FnOnce(bot_capnp::bot::Reader<'_>) -> R,
) -> Result<R, WithError> {
let compressed_buffer = self
.operator
.async_reader_if_exists(PATH)
.await
.context(with_error::ReadSnafu)?;
let decompressed_reader = compressed_buffer.map(BrotliDecoder::new);
let decompressed = decompressed_reader
.map_async(|mut reader| async move {
let mut vec = Vec::new();
reader.read_to_end(&mut vec).await?;
Ok(vec)
})
.await
.transpose()
.context(with_error::DecompressionSnafu)?;
let mut message = TypedBuilder::<bot_capnp::bot::Owned>::new_default();
let fallback = message.init_root();
let mut bot_data = fallback.into_reader();
let message_reader;
if let Some(mut bytes) = decompressed.as_deref() {
message_reader = capnp::serialize::read_message_from_flat_slice_no_alloc(
&mut bytes,
Default::default(),
)
.context(with_error::DeserializeSnafu)?;
bot_data = message_reader
.get_root()
.context(with_error::DeserializeSnafu)?;
}
Ok(f(bot_data))
}
}
#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum UpdateError {
/// couldn't read data for this bot from the storage operator
ReadError { source: opendal::Error },
/// couldn't decompress the bot data from storage
DecompressionError { source: std::io::Error },
/// couldn't deserialize the bot data
DeserializeError { source: capnp::Error },
/// couldn't serialize the (modified) bot data
SerializeError { source: capnp::Error },
/// couldn't create a writer for this bot data in the storage operator
WriterError { source: opendal::Error },
/// couldn't write (modified) data for this bot to the storage operator
WriteError { source: std::io::Error },
/// couldn't finalize writing (modified) data for this bot to the storage operator
FinalizeError { source: std::io::Error },
}
impl BotDataManager {
pub async fn update<R>(
&self,
f: impl FnOnce(bot_capnp::bot::Builder<'_>) -> R,
) -> Result<R, UpdateError> {
let compressed_buffer = self
.operator
.async_reader_if_exists(PATH)
.await
.context(update_error::ReadSnafu)?;
let decompressed_reader = compressed_buffer.map(BrotliDecoder::new);
let decompressed = decompressed_reader
.map_async(|mut reader| async move {
let mut vec = Vec::new();
reader.read_to_end(&mut vec).await?;
Ok(vec)
})
.await
.transpose()
.context(update_error::DecompressionSnafu)?;
let mut message = TypedBuilder::<bot_capnp::bot::Owned>::new_default();
let ret = if let Some(mut bytes) = decompressed.as_deref() {
let message_reader = capnp::serialize::read_message_from_flat_slice_no_alloc(
&mut bytes,
Default::default(),
)
.context(update_error::DeserializeSnafu)?;
let bot_data = message_reader
.get_root()
.context(update_error::DeserializeSnafu)?;
message
.set_root(bot_data)
.context(update_error::DeserializeSnafu)?;
f(
message.get_root().unwrap(), // this is logically impossible
)
} else {
f(message.init_root())
};
let mut buffer = Vec::new();
capnp::serialize::write_message(&mut buffer, message.borrow_inner())
.context(update_error::SerializeSnafu)?;
let compressed_writer = self
.operator
.writer(PATH)
.await
.context(update_error::WriterSnafu)?
.into_futures_async_write();
let mut decompressed_writer = BrotliEncoder::new(compressed_writer);
decompressed_writer
.write_all(&buffer)
.await
.context(update_error::WriteSnafu)?;
decompressed_writer
.close()
.await
.context(update_error::FinalizeSnafu)?;
Ok(ret)
}
}

View File

@@ -1,222 +0,0 @@
use std::sync::LazyLock;
use async_compression::futures::bufread::BrotliDecoder;
use capnp::message::ReaderOptions;
use futures::{AsyncReadExt, TryStreamExt};
use opendal::ErrorKind;
use snafu::{OptionExt, Snafu};
use twilight_model::{
application::{
command::{Command, CommandType},
interaction::Interaction,
},
channel::message::{Embed, MessageFlags},
http::interaction::{InteractionResponse, InteractionResponseType},
id::{Id, marker::UserMarker},
};
use twilight_util::builder::{
InteractionResponseDataBuilder,
command::CommandBuilder,
embed::{EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder},
};
use crate::{bot_capnp, command::State};
const NAME: &str = "debug";
const DESCRIPTION: &str =
"(Only the bot owner can use this) Show various information for debugging purposes";
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
.validate()
.expect("command wasn't correct")
.build()
});
#[derive(Debug, Snafu)]
enum NoPermission {
/// there isn't a user who invoked this command
NoUser,
/// the user isn't allowed to use this command because they're not the bot owner
NotInvokedByBotOwner,
}
fn no_permission_to_embed(error: NoPermission) -> Embed {
match error {
NoPermission::NoUser => {
EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build()
},
NoPermission::NotInvokedByBotOwner => {
EmbedBuilder::new().title("No permission to see debug info").description("Only the owner of this bot is allowed to see its information for debugging purposes.").validate().unwrap().build()
},
}
}
fn check_permission(
interaction: &Interaction,
bot_owner_user_id: Id<UserMarker>,
) -> Result<(), NoPermission> {
let user_id = interaction
.member
.as_ref()
.and_then(|member| member.user.as_ref().map(|user| user.id))
.context(NoUserSnafu)?;
if user_id != bot_owner_user_id {
return Err(NoPermission::NotInvokedByBotOwner);
}
Ok(())
}
#[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) {
if let Err(no_permission) = check_permission(&interaction, state.discord_bot_owner_user_id) {
state
.discord_client
.interaction(state.discord_application_id)
.create_response(
interaction.id,
&interaction.token,
&InteractionResponse {
kind: InteractionResponseType::ChannelMessageWithSource,
data: Some(
InteractionResponseDataBuilder::new()
.embeds([no_permission_to_embed(no_permission)])
.flags(MessageFlags::EPHEMERAL)
.build(),
),
},
)
.await
.expect("TODO");
return;
}
state
.discord_client
.interaction(state.discord_application_id)
.create_response(
interaction.id,
&interaction.token,
&InteractionResponse {
kind: InteractionResponseType::ChannelMessageWithSource,
data: Some(
InteractionResponseDataBuilder::new()
.flags(MessageFlags::EPHEMERAL)
.content("some debug info is coming your way!")
.build(),
),
},
)
.await
.expect("TODO");
let heat_script_description = {
let compressed_result = state
.bot_data
.reader("data.bin.brotli")
.await
.expect("TODO")
.into_futures_async_read(..)
.await;
let mut buf = Vec::default();
let mut message = capnp::message::TypedBuilder::<bot_capnp::bot::Owned>::new_default();
let fallback = message.init_root();
let message_reader;
let mut bot_data = fallback.into_reader();
match compressed_result {
Ok(compressed) => {
let mut decompressed = BrotliDecoder::new(compressed);
decompressed.read_to_end(&mut buf).await.expect("TODO");
message_reader =
capnp::serialize_packed::read_message(&*buf, ReaderOptions::default())
.expect("TODO");
bot_data = message_reader
.get_root::<bot_capnp::bot::Reader>()
.expect("TODO");
}
Err(error) if error.kind() == ErrorKind::NotFound => {
tracing::error!("TODO: proceeding with fallback");
}
Err(error) => {
tracing::error!(?error, "TODO");
return;
}
}
let heat_script_option = bot_data
.has_heat_script()
.then(|| bot_data.get_heat_script().expect("TODO"));
let heat_script_option =
heat_script_option.map(|heat_script| heat_script.to_str().expect("TODO"));
heat_script_option.map_or("none set yet".into(), |heat_script| {
format!("```\n{heat_script}\n```")
})
};
state
.discord_client
.interaction(state.discord_application_id)
.create_followup(&interaction.token)
.embeds(&[EmbedBuilder::new()
.field(EmbedFieldBuilder::new("Heat Script", heat_script_description).build())
.validate()
.unwrap()
.build()])
.flags(MessageFlags::EPHEMERAL)
.await
.expect("TODO");
let mut user_id_stream = state.user_data_manager.list().await.expect("TODO");
while let Some(user_id) = user_id_stream.try_next().await.expect("TODO") {
let (consent, notification_script) = state
.user_data_manager
.with(user_id, |user_data| {
let consent = user_data.get_voice_recording_consent().unwrap();
let notification_script = user_data.has_notification_script().then_some(
user_data
.get_notification_script()
.expect("TODO")
.to_string()
.expect("TODO"),
);
(consent, notification_script)
})
.await
.expect("TODO");
let user_mention = format!("<@{user_id}>");
state
.discord_client
.interaction(state.discord_application_id)
.create_followup(&interaction.token)
.embeds(&[EmbedBuilder::new()
.author(EmbedAuthorBuilder::new(user_mention))
.field(EmbedFieldBuilder::new("Consent", format!("{consent:?}")).build())
.field(
EmbedFieldBuilder::new(
"Notification Script",
format!("{notification_script:?}"),
)
.build(),
)
.validate()
.unwrap()
.build()])
.flags(MessageFlags::EPHEMERAL)
.await
.expect("TODO");
}
}

167
src/command/info.rs Normal file
View File

@@ -0,0 +1,167 @@
use futures::TryStreamExt;
use std::sync::LazyLock;
use twilight_model::{
application::{
command::{Command, CommandType},
interaction::Interaction,
},
channel::message::MessageFlags,
http::interaction::{InteractionResponse, InteractionResponseType},
};
use twilight_util::builder::{
InteractionResponseDataBuilder,
command::CommandBuilder,
embed::{EmbedAuthorBuilder, EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder},
};
use crate::{build_info, command::State};
const NAME: &str = "info";
const DESCRIPTION: &str = "Show various information";
pub static COMMAND: LazyLock<Command> = LazyLock::new(|| {
CommandBuilder::new(NAME, DESCRIPTION, CommandType::ChatInput)
.validate()
.expect("command wasn't correct")
.build()
});
#[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) {
let revision = build_info::COMMIT_HASH;
let bot_owner_user_id = state.discord_bot_owner_user_id;
let is_bot_owner =
interaction
.member
.as_ref()
.and_then(|member| member.user.as_ref().map(|user| user.id))
.map(|user_id| user_id == bot_owner_user_id)
.unwrap_or(false);
let bot_owner_mention = format!("<@{}>", bot_owner_user_id);
let opt_in_mention = format!(
"</{}:{}>",
state.discord_opt_in_command_name, state.discord_opt_in_command_id
);
let opt_out_mention = format!(
"</{}:{}>",
state.discord_opt_out_command_name, state.discord_opt_out_command_id
);
state
.discord_client
.interaction(state.discord_application_id)
.create_response(
interaction.id,
&interaction.token,
&InteractionResponse {
kind: InteractionResponseType::ChannelMessageWithSource,
data: Some(
InteractionResponseDataBuilder::new().embeds([
EmbedBuilder::new()
.title("About This Bot")
.description(format!("This bot intends to record VCs it's in. You can opt out with {opt_out_mention} or explicitly opt in with {opt_in_mention} (I'd appreciate this one). Here are some pledges backed by faith (because there is no way to verify them yourself) in {bot_owner_mention}:"))
.field(
EmbedFieldBuilder::new("Recordings are never shared", "Audio recordings are only stored on my home server and desktop computer and will never be uploaded to services or hardware that is owned by another person: not even curated clips, and not even to people who were in the recording. When transcription to text is implemented, this will only be run on my personally owned devices and not on any internet or cloud offering.").build()
)
.field(
EmbedFieldBuilder::new("You won't be \"audited\"", "I will not reference things said in past recordings with the goal of \"making a point\", nor pull them up on the spot (even by the request of the person who said it). Ideally, these are just peace of mind for me that I'm not missing out by not being in a Discord call all the time and can take my life back, so using them in an unhealthy way isn't in my interest.").build()
)
.field(
EmbedFieldBuilder::new("Code is publicly available", format!("The latest source code is at https://gitea.katniss.top/jacob/fomo-reducer so that I don't have to write guarantees about the technology here (e.g. what data is acquired, how it's used or stored) and you can just check it yourself. To that end: this bot is running revision `{revision}` at the time of this message.")).build()
)
.footer(
EmbedFooterBuilder::new("Thanks for your patience and understanding as I have bad and unusual mental health and it's crazy that I need this. This - especially if I learn if I can record streams or webcams so I don't miss out on those experiences either - should be the end of abrasion and force about how we spend our time. Again, thank you, I appreciate it.")
)
.validate()
.unwrap()
.build()
])
.flags(MessageFlags::EPHEMERAL)
.build()
)
})
.await
.expect("TODO");
if is_bot_owner {
let heat_script_description = state
.bot_data_manager
.with(|bot_data| {
let heat_script_option = bot_data.has_heat_script().then(|| {
bot_data
.get_heat_script()
.expect("TODO")
.to_str()
.expect("TODO")
});
heat_script_option.map_or("none set yet".into(), |heat_script| {
format!("```\n{heat_script}\n```")
})
})
.await
.expect("TODO");
state
.discord_client
.interaction(state.discord_application_id)
.create_followup(&interaction.token)
.embeds(&[EmbedBuilder::new()
.title("Bot Data")
.field(EmbedFieldBuilder::new("Heat Script", heat_script_description).build())
.validate()
.unwrap()
.build()])
.flags(MessageFlags::EPHEMERAL)
.await
.expect("TODO");
let mut user_id_stream = state.user_data_manager.list().await.expect("TODO");
while let Some(user_id) = user_id_stream.try_next().await.expect("TODO") {
let (consent, notification_script) = state
.user_data_manager
.with(user_id, |user_data| {
let consent = user_data.get_voice_recording_consent().unwrap();
let notification_script = user_data.has_notification_script().then_some(
user_data
.get_notification_script()
.expect("TODO")
.to_string()
.expect("TODO"),
);
(consent, notification_script)
})
.await
.expect("TODO");
let user_mention = format!("<@{user_id}>");
state
.discord_client
.interaction(state.discord_application_id)
.create_followup(&interaction.token)
.embeds(&[EmbedBuilder::new()
.author(EmbedAuthorBuilder::new(user_mention))
.field(EmbedFieldBuilder::new("Consent", format!("{consent:?}")).build())
.field(
EmbedFieldBuilder::new(
"Notification Script",
format!("{notification_script:?}"),
)
.build(),
)
.validate()
.unwrap()
.build()])
.flags(MessageFlags::EPHEMERAL)
.await
.expect("TODO");
}
}
}

View File

@@ -27,9 +27,7 @@ use twilight_model::{
}, },
}; };
use twilight_util::builder::{ use twilight_util::builder::{
InteractionResponseDataBuilder, InteractionResponseDataBuilder, command::CommandBuilder, embed::EmbedBuilder,
command::CommandBuilder,
embed::{EmbedBuilder, EmbedFieldBuilder, EmbedFooterBuilder},
}; };
const NAME: &str = "join"; const NAME: &str = "join";
@@ -50,9 +48,6 @@ enum GetGuildAndVoiceChannelIdError {
/// there is no user who invoked this command /// there is no user who invoked this command
NoUser, NoUser,
/// there are no voice chats in this guild
NoVCsInGuild,
/// the user is not in a voice chat in this guild /// the user is not in a voice chat in this guild
UserNotInVC, UserNotInVC,
} }
@@ -70,9 +65,11 @@ fn get_guild_and_voice_channel_id(
.and_then(|member| member.user.as_ref().map(|user| user.id)) .and_then(|member| member.user.as_ref().map(|user| user.id))
.context(NoUserSnafu)?; .context(NoUserSnafu)?;
let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; let &voice_channel_id = vcs
.get(&guild_id)
let &voice_channel_id = guild_vcs.get_left_for(&user_id).context(UserNotInVCSnafu)?; .context(UserNotInVCSnafu)?
.get_left_for(&user_id)
.context(UserNotInVCSnafu)?;
Ok((guild_id, voice_channel_id)) Ok((guild_id, voice_channel_id))
} }
@@ -85,9 +82,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
GetGuildAndVoiceChannelIdError::NoUser => { GetGuildAndVoiceChannelIdError::NoUser => {
EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build()
}, },
GetGuildAndVoiceChannelIdError::NoVCsInGuild => {
EmbedBuilder::new().title("No VCs in this server").description("This bot can't find a VC to join because there aren't any in this server right now.").validate().unwrap().build()
},
GetGuildAndVoiceChannelIdError::UserNotInVC => { GetGuildAndVoiceChannelIdError::UserNotInVC => {
EmbedBuilder::new().title("You're not in a VC").description("This bot can't follow you into VC if you aren't in one in this server.").validate().unwrap().build() EmbedBuilder::new().title("You're not in a VC").description("This bot can't follow you into VC if you aren't in one in this server.").validate().unwrap().build()
}, },
@@ -240,9 +234,9 @@ impl EventHandler for Handler {
#[tracing::instrument(skip(state))] #[tracing::instrument(skip(state))]
pub async fn handle(state: State, interaction: Interaction) { pub async fn handle(state: State, interaction: Interaction) {
let vcs = state.vcs; let guild_and_voice_channel_id_res =
get_guild_and_voice_channel_id(&interaction, &state.vcs_watcher.borrow());
let (guild_id, voice_channel_id) = match get_guild_and_voice_channel_id(&interaction, &vcs) { let (guild_id, voice_channel_id) = match guild_and_voice_channel_id_res {
Ok((guild_id, voice_channel_id)) => (guild_id, voice_channel_id), Ok((guild_id, voice_channel_id)) => (guild_id, voice_channel_id),
Err(error) => { Err(error) => {
state state
@@ -321,8 +315,11 @@ pub async fn handle(state: State, interaction: Interaction) {
} }
let channel_mention = format!("<#{voice_channel_id}>"); let channel_mention = format!("<#{voice_channel_id}>");
let bot_owner_mention = format!("<@{}>", state.discord_bot_owner_user_id);
let info_mention = format!(
"</{}:{}>",
state.discord_info_command_name, state.discord_info_command_id
);
let opt_in_mention = format!( let opt_in_mention = format!(
"</{}:{}>", "</{}:{}>",
state.discord_opt_in_command_name, state.discord_opt_in_command_id state.discord_opt_in_command_name, state.discord_opt_in_command_id
@@ -340,19 +337,7 @@ pub async fn handle(state: State, interaction: Interaction) {
).embeds(Some(&[ ).embeds(Some(&[
EmbedBuilder::new() EmbedBuilder::new()
.title("Joined VC to record") .title("Joined VC to record")
.description(format!("This bot joined {channel_mention} and intends to record. You can opt out with {opt_out_mention} or explicitly opt in with {opt_in_mention} (I'd appreciate this one). Here are some pledges backed by faith (because there is no way to verify them yourself) in {bot_owner_mention}:")) .description(format!("This bot joined {channel_mention} and intends to record. You can opt out with {opt_out_mention} or explicitly opt in with {opt_in_mention} (I'd appreciate this one). Please use {info_mention} for more information about this bot."))
.field(
EmbedFieldBuilder::new("Recordings are never shared", "Audio recordings are only stored on my home server and desktop computer and will never be uploaded to services or hardware that is owned by another person: not even curated clips, and not even to people who were in the recording. When transcription to text is implemented, this will only be run on my personally owned devices and not on any internet or cloud offering.").build()
)
.field(
EmbedFieldBuilder::new("You won't be \"audited\"", "I will not reference things said in past recordings with the goal of \"making a point\", nor pull them up on the spot (even by the request of the person who said it). Ideally, these are just peace of mind for me that I'm not missing out by not being in a Discord call all the time and can take my life back, so using them in an unhealthy way isn't in my interest.").build()
)
.field(
EmbedFieldBuilder::new("Code is publicly available", "The latest source code is at https://gitea.katniss.top/jacob/fomo-reducer so that I don't have to write guarantees about the technology here (e.g. what data is acquired, how it's used or stored) and you can just check it yourself.").build()
)
.footer(
EmbedFooterBuilder::new("Thanks for your patience and understanding as I have bad and unusual mental health and it's crazy that I need this. This - especially if I learn if I can record streams or webcams so I don't miss out on those experiences either - should be the end of abrasion and force about how we spend our time. Again, thank you, I appreciate it.")
)
.validate() .validate()
.unwrap() .unwrap()
.build() .build()

View File

@@ -37,9 +37,6 @@ pub enum GetGuildAndVoiceChannelIdError {
/// there is no user who invoked this command /// there is no user who invoked this command
NoUser, NoUser,
/// there are no voice chats in this guild
NoVCsInGuild,
/// the bot is not in a voice chat in this guild /// the bot is not in a voice chat in this guild
BotNotInVC, BotNotInVC,
} }
@@ -58,9 +55,9 @@ pub fn get_user_and_guild_and_voice_channel_id(
let guild_id = interaction.guild_id.context(NotInGuildSnafu)?; let guild_id = interaction.guild_id.context(NotInGuildSnafu)?;
let guild_vcs = vcs.get(&guild_id).context(NoVCsInGuildSnafu)?; let &voice_channel_id = vcs
.get(&guild_id)
let &voice_channel_id = guild_vcs .context(BotNotInVCSnafu)?
.get_left_for(&bot_user_id) .get_left_for(&bot_user_id)
.context(BotNotInVCSnafu)?; .context(BotNotInVCSnafu)?;
@@ -75,9 +72,6 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
GetGuildAndVoiceChannelIdError::NoUser => { GetGuildAndVoiceChannelIdError::NoUser => {
EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build() EmbedBuilder::new().title("Not invoked by a user").description("This command works by joining the same VC as the user, but this bot didn't receive any user data. So did no user invoke it?! (This error should be impossible!)").validate().unwrap().build()
}, },
GetGuildAndVoiceChannelIdError::NoVCsInGuild => {
EmbedBuilder::new().title("No VCs in this server").description("This bot can't leave VC because there aren't any in this server right now (therefore the bot must not be in any).").validate().unwrap().build()
},
GetGuildAndVoiceChannelIdError::BotNotInVC => { GetGuildAndVoiceChannelIdError::BotNotInVC => {
EmbedBuilder::new().title("Not in a VC").description("This bot can't leave VC if it isn't in one in this server.").validate().unwrap().build() EmbedBuilder::new().title("Not in a VC").description("This bot can't leave VC if it isn't in one in this server.").validate().unwrap().build()
}, },
@@ -86,11 +80,12 @@ fn get_guild_and_vc_error_to_embed(error: GetGuildAndVoiceChannelIdError) -> Emb
#[tracing::instrument] #[tracing::instrument]
pub async fn handle(state: State, interaction: Interaction) { pub async fn handle(state: State, interaction: Interaction) {
let (user_id, guild_id, voice_channel_id) = match get_user_and_guild_and_voice_channel_id( let user_and_guild_and_voice_channel_id_res = get_user_and_guild_and_voice_channel_id(
state.discord_user_id, state.discord_user_id,
&interaction, &interaction,
&state.vcs, &state.vcs_watcher.borrow(),
) { );
let (user_id, guild_id, voice_channel_id) = match user_and_guild_and_voice_channel_id_res {
Ok((user_id, guild_id, voice_channel_id)) => (user_id, guild_id, voice_channel_id), Ok((user_id, guild_id, voice_channel_id)) => (user_id, guild_id, voice_channel_id),
Err(error) => { Err(error) => {
state state

View File

@@ -16,9 +16,9 @@ use twilight_model::{
}, },
}; };
use crate::{GuildVoiceChannelToTextChannel, UserDataManager, VCs}; use crate::{BotDataManager, GuildVoiceChannelToTextChannel, UserDataManager, VCsWatcher};
pub mod debug; pub mod info;
pub mod join; pub mod join;
pub mod leave; pub mod leave;
pub mod opt_in; pub mod opt_in;
@@ -28,11 +28,13 @@ pub mod opt_out;
pub struct State { pub struct State {
pub audio_channels: Channels, pub audio_channels: Channels,
pub audio_sample_rate: SampleRate, pub audio_sample_rate: SampleRate,
pub bot_data: Operator, pub bot_data_manager: BotDataManager,
pub cancellation_token: CancellationToken, pub cancellation_token: CancellationToken,
pub discord_application_id: Id<ApplicationMarker>, pub discord_application_id: Id<ApplicationMarker>,
pub discord_bot_owner_user_id: Id<UserMarker>, pub discord_bot_owner_user_id: Id<UserMarker>,
pub discord_client: Arc<twilight_http::Client>, pub discord_client: Arc<twilight_http::Client>,
pub discord_info_command_id: Id<CommandMarker>,
pub discord_info_command_name: Arc<str>,
pub discord_opt_in_command_id: Id<CommandMarker>, pub discord_opt_in_command_id: Id<CommandMarker>,
pub discord_opt_in_command_name: Arc<str>, pub discord_opt_in_command_name: Arc<str>,
pub discord_opt_out_command_id: Id<CommandMarker>, pub discord_opt_out_command_id: Id<CommandMarker>,
@@ -42,7 +44,7 @@ pub struct State {
pub recording_data: Operator, pub recording_data: Operator,
pub songbird: Arc<Songbird>, pub songbird: Arc<Songbird>,
pub user_data_manager: UserDataManager, pub user_data_manager: UserDataManager,
pub vcs: Arc<VCs>, pub vcs_watcher: VCsWatcher,
} }
type Return = (); type Return = ();
@@ -58,7 +60,7 @@ where
pub fn all() -> Vec<(&'static Command, BoxedHandler)> { pub fn all() -> Vec<(&'static Command, BoxedHandler)> {
vec![ vec![
(&debug::COMMAND, box_handler(debug::handle)), (&info::COMMAND, box_handler(info::handle)),
(&join::COMMAND, box_handler(join::handle)), (&join::COMMAND, box_handler(join::handle)),
(&leave::COMMAND, box_handler(leave::handle)), (&leave::COMMAND, box_handler(leave::handle)),
(&opt_in::COMMAND, box_handler(opt_in::handle)), (&opt_in::COMMAND, box_handler(opt_in::handle)),

View File

@@ -5,6 +5,7 @@ use twilight_model::{
command::{Command, CommandType}, command::{Command, CommandType},
interaction::Interaction, interaction::Interaction,
}, },
channel::message::MessageFlags,
http::interaction::{InteractionResponse, InteractionResponseType}, http::interaction::{InteractionResponse, InteractionResponseType},
}; };
use twilight_util::builder::{InteractionResponseDataBuilder, command::CommandBuilder}; use twilight_util::builder::{InteractionResponseDataBuilder, command::CommandBuilder};
@@ -79,6 +80,7 @@ pub async fn handle(state: State, interaction: Interaction) {
.content(format!( .content(format!(
"opted you in, your previous consent was {previous_consent:?}" "opted you in, your previous consent was {previous_consent:?}"
)) ))
.flags(MessageFlags::EPHEMERAL)
.build(), .build(),
), ),
}, },

View File

@@ -5,6 +5,7 @@ use twilight_model::{
command::{Command, CommandType}, command::{Command, CommandType},
interaction::Interaction, interaction::Interaction,
}, },
channel::message::MessageFlags,
http::interaction::{InteractionResponse, InteractionResponseType}, http::interaction::{InteractionResponse, InteractionResponseType},
}; };
use twilight_util::builder::{InteractionResponseDataBuilder, command::CommandBuilder}; use twilight_util::builder::{InteractionResponseDataBuilder, command::CommandBuilder};
@@ -79,6 +80,7 @@ pub async fn handle(state: State, interaction: Interaction) {
.content(format!( .content(format!(
"opted you out, your previous consent was {previous_consent:?}" "opted you out, your previous consent was {previous_consent:?}"
)) ))
.flags(MessageFlags::EPHEMERAL)
.build(), .build(),
), ),
}, },

View File

@@ -1,3 +1,4 @@
mod bot_data;
pub mod command; pub mod command;
mod one_to_many; mod one_to_many;
mod one_to_many_with_data; mod one_to_many_with_data;
@@ -10,13 +11,15 @@ mod user_data;
mod vc_user; mod vc_user;
capnp::generated_code!(mod bot_capnp); capnp::generated_code!(mod bot_capnp);
capnp::generated_code!(mod user_capnp); capnp::generated_code!(mod user_capnp);
shadow_rs::shadow!(build_info);
pub use bot_data::BotDataManager;
pub use command::{Router as CommandRouter, State, all as all_commands}; pub use command::{Router as CommandRouter, State, all as all_commands};
pub use one_to_many::OneToManyUniqueBTreeMap; pub use one_to_many::OneToManyUniqueBTreeMap;
pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData; pub use one_to_many_with_data::OneToManyUniqueBTreeMapWithData;
pub use one_to_one::OneToOneBTreeMap; pub use one_to_one::OneToOneBTreeMap;
pub use operator_ext::OperatorExt; pub use operator_ext::OperatorExt;
pub use storage::Storage; pub use storage::Storage;
pub use track_vcs::{GuildVoiceChannelToTextChannel, VCs, initialize_vcs, update_vcs}; pub use track_vcs::{GuildVoiceChannelToTextChannel, VCs, VCsWatcher, initialize_vcs, update_vcs};
pub use user_data::UserDataManager; pub use user_data::UserDataManager;
pub use vc_user::{UserInVCData, VoiceStatus}; pub use vc_user::{UserInVCData, VoiceStatus};

View File

@@ -1,7 +1,6 @@
use clap::Parser; use clap::Parser;
use fomo_reducer::{ use fomo_reducer::{
CommandRouter, GuildVoiceChannelToTextChannel, State, Storage, UserDataManager, all_commands, BotDataManager, CommandRouter, GuildVoiceChannelToTextChannel, State, Storage, UserDataManager, VCsWatcher, all_commands, command, initialize_vcs, update_vcs
command, initialize_vcs, update_vcs,
}; };
use secrecy::{ExposeSecret, SecretString}; use secrecy::{ExposeSecret, SecretString};
use snafu::{OptionExt, ResultExt, Snafu}; use snafu::{OptionExt, ResultExt, Snafu};
@@ -296,6 +295,9 @@ async fn main() -> Result<(), MainError> {
.map(|command| (command.name.clone(), command)), .map(|command| (command.name.clone(), command)),
); );
let discord_info_command = discord_command_name_to_returned_command
.remove(&command::info::COMMAND.name)
.expect("TODO");
let discord_opt_in_command = discord_command_name_to_returned_command let discord_opt_in_command = discord_command_name_to_returned_command
.remove(&command::opt_in::COMMAND.name) .remove(&command::opt_in::COMMAND.name)
.expect("TODO"); .expect("TODO");
@@ -303,9 +305,11 @@ async fn main() -> Result<(), MainError> {
.remove(&command::opt_out::COMMAND.name) .remove(&command::opt_out::COMMAND.name)
.expect("TODO"); .expect("TODO");
let discord_info_command_id = discord_info_command.id.expect("TODO");
let discord_opt_in_command_id = discord_opt_in_command.id.expect("TODO"); let discord_opt_in_command_id = discord_opt_in_command.id.expect("TODO");
let discord_opt_out_command_id = discord_opt_out_command.id.expect("TODO"); let discord_opt_out_command_id = discord_opt_out_command.id.expect("TODO");
let discord_info_command_name = discord_info_command.name.into();
let discord_opt_in_command_name = discord_opt_in_command.name.into(); let discord_opt_in_command_name = discord_opt_in_command.name.into();
let discord_opt_out_command_name = discord_opt_out_command.name.into(); let discord_opt_out_command_name = discord_opt_out_command.name.into();
@@ -316,12 +320,13 @@ async fn main() -> Result<(), MainError> {
let discord_client = Arc::new(discord_client); let discord_client = Arc::new(discord_client);
let songbird = Arc::new(songbird); let songbird = Arc::new(songbird);
let vcs = Arc::new(vcs); let vcs_watcher = VCsWatcher::new(vcs);
let bot_data = bot_data.into_inner(); let bot_data = bot_data.into_inner();
let recording_data = recording_data.into_inner(); let recording_data = recording_data.into_inner();
let user_data = user_data.into_inner(); let user_data = user_data.into_inner();
let bot_data_manager = BotDataManager::new(bot_data);
let user_data_manager = UserDataManager::new(user_data); let user_data_manager = UserDataManager::new(user_data);
let discord_voice_channel_corresponding_text_channel = { let discord_voice_channel_corresponding_text_channel = {
@@ -343,11 +348,13 @@ async fn main() -> Result<(), MainError> {
let state = State { let state = State {
audio_channels, audio_channels,
audio_sample_rate, audio_sample_rate,
bot_data, bot_data_manager,
cancellation_token: cancellation_token.clone(), cancellation_token: cancellation_token.clone(),
discord_application_id, discord_application_id,
discord_bot_owner_user_id, discord_bot_owner_user_id,
discord_client, discord_client,
discord_info_command_id,
discord_info_command_name,
discord_opt_in_command_id, discord_opt_in_command_id,
discord_opt_in_command_name, discord_opt_in_command_name,
discord_opt_out_command_id, discord_opt_out_command_id,
@@ -357,7 +364,7 @@ async fn main() -> Result<(), MainError> {
recording_data, recording_data,
songbird, songbird,
user_data_manager, user_data_manager,
vcs, vcs_watcher,
}; };
if let Some(discord_status) = discord_status { if let Some(discord_status) = discord_status {
@@ -454,7 +461,7 @@ async fn handle_event(command_router: Arc<CommandRouter>, state: State, event: E
match event { match event {
Event::VoiceStateUpdate(voice_state_update) => { Event::VoiceStateUpdate(voice_state_update) => {
update_vcs(&voice_state_update, &state.vcs); state.vcs_watcher.send_modify(|vcs| update_vcs(&voice_state_update, vcs));
} }
Event::InteractionCreate(interaction_create) => { Event::InteractionCreate(interaction_create) => {
let InteractionCreate(interaction) = *interaction_create; let InteractionCreate(interaction) = *interaction_create;

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use dashmap::DashMap;
use futures::{StreamExt, stream::FuturesUnordered}; use futures::{StreamExt, stream::FuturesUnordered};
use tokio::sync::watch;
use twilight_model::{ use twilight_model::{
gateway::payload::incoming::VoiceStateUpdate, gateway::payload::incoming::VoiceStateUpdate,
id::{ id::{
@@ -15,8 +15,9 @@ use crate::{OneToManyUniqueBTreeMapWithData, OneToOneBTreeMap, UserInVCData, Voi
pub type GuildVoiceChannelToTextChannel = pub type GuildVoiceChannelToTextChannel =
BTreeMap<Id<GuildMarker>, OneToOneBTreeMap<Id<ChannelMarker>, Id<ChannelMarker>>>; BTreeMap<Id<GuildMarker>, OneToOneBTreeMap<Id<ChannelMarker>, Id<ChannelMarker>>>;
type VCsInGuild = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>; pub type VCsInGuild = OneToManyUniqueBTreeMapWithData<Id<ChannelMarker>, Id<UserMarker>, UserInVCData>;
pub type VCs = DashMap<Id<GuildMarker>, VCsInGuild>; pub type VCs = BTreeMap<Id<GuildMarker>, VCsInGuild>;
pub type VCsWatcher = watch::Sender<VCs>;
#[tracing::instrument(skip(discord_client), ret)] #[tracing::instrument(skip(discord_client), ret)]
async fn initialize_user_in_vc( async fn initialize_user_in_vc(
@@ -92,7 +93,7 @@ pub async fn initialize_vcs(discord_client: &twilight_http::Client) -> VCs {
} }
#[tracing::instrument(skip(vcs))] #[tracing::instrument(skip(vcs))]
pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) { pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &mut VCs) {
let user_id = voice_state_update.user_id; let user_id = voice_state_update.user_id;
match voice_state_update.guild_id { match voice_state_update.guild_id {
Some(guild_id) => match voice_state_update.channel_id { Some(guild_id) => match voice_state_update.channel_id {
@@ -107,9 +108,7 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) {
.build(); .build();
let user_in_vc_data = voice_status.into(); let user_in_vc_data = voice_status.into();
vcs.entry(guild_id) vcs.entry(guild_id).or_default().insert(channel_id, user_id, user_in_vc_data);
.or_default()
.insert(channel_id, user_id, user_in_vc_data);
tracing::info!( tracing::info!(
?guild_id, ?guild_id,
@@ -120,9 +119,7 @@ pub fn update_vcs(voice_state_update: &VoiceStateUpdate, vcs: &VCs) {
} }
None => { None => {
if let Some(mut channel_vcers) = vcs.get_mut(&guild_id) { vcs.entry(guild_id).or_default().remove_right(&user_id);
channel_vcers.remove_right(&user_id);
}
tracing::info!(?guild_id, ?user_id, "disconnected"); tracing::info!(?guild_id, ?user_id, "disconnected");
} }