diff --git a/.gitignore b/.gitignore
index 4e4e595149..bb4aadfc07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ coverage
 !/.config/helm_values_example.yml
 !/.config/LICENSE
 /docker-compose.yml
+/compose.yml
 /custom/*
 !/custom/LICENSE
 
diff --git a/Cargo.lock b/Cargo.lock
index 49f5a07a93..88b6db1a06 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -92,7 +92,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -143,7 +143,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -154,7 +154,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -235,6 +235,7 @@ dependencies = [
  "url",
  "urlencoding",
  "web-push",
+ "zhconv",
 ]
 
 [[package]]
@@ -398,9 +399,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
 
 [[package]]
 name = "bytes"
-version = "1.6.0"
+version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
 
 [[package]]
 name = "castaway"
@@ -410,13 +411,12 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
 
 [[package]]
 name = "cc"
-version = "1.1.0"
+version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8"
+checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
 dependencies = [
  "jobserver",
  "libc",
- "once_cell",
 ]
 
 [[package]]
@@ -500,6 +500,16 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "const-oid"
 version = "0.6.2"
@@ -629,7 +639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
 dependencies = [
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -681,6 +691,12 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "daachorse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b7ef7a4be509357f4804d0a22e830daddb48f19fd604e4ad32ddce04a94c36"
+
 [[package]]
 name = "der"
 version = "0.4.5"
@@ -772,7 +788,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1188,6 +1204,12 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
 [[package]]
 name = "hkdf"
 version = "0.12.4"
@@ -1388,7 +1410,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1415,12 +1437,12 @@ dependencies = [
 
 [[package]]
 name = "image"
-version = "0.25.1"
+version = "0.25.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
+checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
 dependencies = [
  "bytemuck",
- "byteorder",
+ "byteorder-lite",
  "color_quant",
  "gif",
  "image-webp",
@@ -1435,12 +1457,12 @@ dependencies = [
 
 [[package]]
 name = "image-webp"
-version = "0.1.2"
+version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d"
+checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
 dependencies = [
  "byteorder-lite",
- "thiserror",
+ "quick-error",
 ]
 
 [[package]]
@@ -1467,7 +1489,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1496,7 +1518,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1518,6 +1540,8 @@ dependencies = [
  "mime",
  "once_cell",
  "polling",
+ "serde",
+ "serde_json",
  "slab",
  "sluice",
  "tracing",
@@ -1526,6 +1550,15 @@ dependencies = [
  "waker-fn",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "itertools"
 version = "0.12.1"
@@ -1642,9 +1675,9 @@ dependencies = [
 
 [[package]]
 name = "libloading"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
+checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
 dependencies = [
  "cfg-if",
  "windows-targets 0.52.6",
@@ -1744,7 +1777,7 @@ dependencies = [
  "convert_case",
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1797,20 +1830,21 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.11"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
 dependencies = [
+ "hermit-abi",
  "libc",
  "wasi",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "napi"
-version = "3.0.0-alpha.6"
+version = "3.0.0-alpha.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e5a69ce63aa1e68c939c5afa3f7be80d0c37eb3755022b064792dc019f08d8e"
+checksum = "743b5a7769f54c95e20a26d9e66d1b43d5622b7dc8ec8f97b51ed8c58633841f"
 dependencies = [
  "bitflags 2.6.0",
  "chrono",
@@ -1831,23 +1865,23 @@ checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a"
 
 [[package]]
 name = "napi-derive"
-version = "3.0.0-alpha.5"
+version = "3.0.0-alpha.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e82f3209766c72466e28f05d8e55931cfda1652877b2cadf4011034890a2770"
+checksum = "c7619cfcc3985e1ed73d147d6950caabaedabcf5c98133502f9d18c3d0061320"
 dependencies = [
  "cfg-if",
  "convert_case",
  "napi-derive-backend",
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "napi-derive-backend"
-version = "2.0.0-alpha.5"
+version = "2.0.0-alpha.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b17d6c84ea7366a126d850e2010f2d8354be1d3f2c62bc20751b08ba5b0a774"
+checksum = "584f6a91c05e8c6bf80622fcc2675c7d27934754d4f1141cfd422d531a3f51fb"
 dependencies = [
  "convert_case",
  "once_cell",
@@ -1855,7 +1889,7 @@ dependencies = [
  "quote",
  "regex",
  "semver",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -1984,7 +2018,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2029,12 +2063,11 @@ dependencies = [
 ]
 
 [[package]]
-name = "num_cpus"
-version = "1.16.0"
+name = "num_threads"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
 dependencies = [
- "hermit-abi",
  "libc",
 ]
 
@@ -2055,9 +2088,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "openssl"
-version = "0.10.64"
+version = "0.10.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
+checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
 dependencies = [
  "bitflags 2.6.0",
  "cfg-if",
@@ -2076,7 +2109,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2096,9 +2129,9 @@ dependencies = [
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.102"
+version = "0.9.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
+checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
 dependencies = [
  "cc",
  "libc",
@@ -2137,7 +2170,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2194,7 +2227,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall 0.5.2",
+ "redox_syscall 0.5.3",
  "smallvec",
  "windows-targets 0.52.6",
 ]
@@ -2295,7 +2328,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2468,7 +2501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
 dependencies = [
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2530,7 +2563,7 @@ dependencies = [
  "built",
  "cfg-if",
  "interpolate_name",
- "itertools",
+ "itertools 0.12.1",
  "libc",
  "libfuzzer-sys",
  "log",
@@ -2616,9 +2649,9 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
 dependencies = [
  "bitflags 2.6.0",
 ]
@@ -2664,9 +2697,9 @@ dependencies = [
 
 [[package]]
 name = "rgb"
-version = "0.8.44"
+version = "0.8.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9"
+checksum = "ade4539f42266ded9e755c605bdddf546242b2c961b03b06a7375260788a0523"
 dependencies = [
  "bytemuck",
 ]
@@ -2798,6 +2831,23 @@ dependencies = [
  "untrusted",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+
+[[package]]
+name = "ruzstd"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3ffab8f9715a0d455df4bbb9d21e91135aab3cd3ca187af0cd0c3c3f868fdc"
+dependencies = [
+ "byteorder",
+ "thiserror-core",
+ "twox-hash",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.18"
@@ -2839,7 +2889,7 @@ dependencies = [
  "proc-macro-error",
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -2860,7 +2910,7 @@ dependencies = [
  "serde",
  "serde_json",
  "sqlx",
- "strum",
+ "strum 0.25.0",
  "thiserror",
  "time",
  "tracing",
@@ -2878,7 +2928,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "sea-bae",
- "syn 2.0.71",
+ "syn 2.0.72",
  "unicode-ident",
 ]
 
@@ -2955,7 +3005,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3381,12 +3431,34 @@ dependencies = [
  "unicode-properties",
 ]
 
+[[package]]
+name = "strum"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
+dependencies = [
+ "strum_macros",
+]
+
 [[package]]
 name = "strum"
 version = "0.25.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
 
+[[package]]
+name = "strum_macros"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "subtle"
 version = "2.6.1"
@@ -3406,9 +3478,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.71"
+version = "2.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
+checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3435,7 +3507,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3485,22 +3557,42 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.62"
+version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
-name = "thiserror-impl"
-version = "1.0.62"
+name = "thiserror-core"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
+checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999"
+dependencies = [
+ "thiserror-core-impl",
+]
+
+[[package]]
+name = "thiserror-core-impl"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3531,7 +3623,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
 dependencies = [
  "deranged",
+ "itoa",
+ "libc",
  "num-conv",
+ "num_threads",
  "powerfmt",
  "serde",
  "time-core",
@@ -3581,31 +3676,30 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.38.0"
+version = "1.39.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
+checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
 dependencies = [
  "backtrace",
  "bytes",
  "libc",
  "mio",
- "num_cpus",
  "pin-project-lite",
  "signal-hook-registry",
  "socket2",
  "tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3647,9 +3741,9 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "0.8.14"
+version = "0.8.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335"
+checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28"
 dependencies = [
  "serde",
  "serde_spanned",
@@ -3668,9 +3762,9 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.22.15"
+version = "0.22.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1"
+checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788"
 dependencies = [
  "indexmap",
  "serde",
@@ -3699,7 +3793,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3733,6 +3827,16 @@ dependencies = [
  "tracing-core",
 ]
 
+[[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -3851,6 +3955,18 @@ version = "0.2.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
+[[package]]
+name = "vergen"
+version = "8.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566"
+dependencies = [
+ "anyhow",
+ "cfg-if",
+ "rustversion",
+ "time",
+]
+
 [[package]]
 name = "version-compare"
 version = "0.2.0"
@@ -3911,7 +4027,7 @@ dependencies = [
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
  "wasm-bindgen-shared",
 ]
 
@@ -3933,7 +4049,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -4169,9 +4285,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winnow"
-version = "0.6.13"
+version = "0.6.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
+checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f"
 dependencies = [
  "memchr",
 ]
@@ -4214,7 +4330,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
  "synstructure 0.13.1",
 ]
 
@@ -4235,7 +4351,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -4255,7 +4371,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
  "synstructure 0.13.1",
 ]
 
@@ -4284,7 +4400,57 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.71",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "zhconv"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a5764e8c3c48dce7dd281cdae65c785536d1da3078b484c2254e7bea7b42323"
+dependencies = [
+ "console_error_panic_hook",
+ "daachorse",
+ "hex-literal",
+ "itertools 0.10.5",
+ "lazy_static",
+ "once_cell",
+ "regex",
+ "ruzstd",
+ "sha2",
+ "strum 0.24.1",
+ "vergen",
+ "wasm-bindgen",
+ "zstd",
+]
+
+[[package]]
+name = "zstd"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "6.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
+dependencies = [
+ "libc",
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.12+zstd.1.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
+dependencies = [
+ "cc",
+ "pkg-config",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 6c46d7cdc6..2cf48527f2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,8 +10,8 @@ rust-version = "1.74"
 macros = { path = "packages/macro-rs/macros" }
 macros-impl = { path = "packages/macro-rs/macros-impl" }
 
-napi = "3.0.0-alpha.6"
-napi-derive = "3.0.0-alpha.5"
+napi = "3.0.0-alpha.8"
+napi-derive = "3.0.0-alpha.7"
 napi-build = "2.1.3"
 
 argon2 = { version = "0.5.3", default-features = false }
@@ -24,7 +24,7 @@ convert_case = { version = "0.6.0", default-features = false }
 cuid2 = { version = "0.1.2", default-features = false }
 emojis = { version = "0.6.2", default-features = false }
 idna = { version = "1.0.2", default-features = false }
-image = { version = "0.25.1", default-features = false }
+image = { version = "0.25.2", default-features = false }
 isahc = { version = "1.7.2", default-features = false }
 nom-exif = { version = "1.2.6", default-features = false }
 once_cell = { version = "1.19.0", default-features = false }
@@ -39,20 +39,21 @@ sea-orm = { version = "0.12.15", default-features = false }
 serde = { version = "1.0.204", default-features = false }
 serde_json = { version = "1.0.120", default-features = false }
 serde_yaml = { version = "0.9.34", default-features = false }
-syn = { version = "2.0.71", default-features = false }
+syn = { version = "2.0.72", default-features = false }
 sysinfo = { version = "0.30.13", default-features = false }
-thiserror = { version = "1.0.62", default-features = false }
-tokio = { version = "1.38.0", default-features = false }
+thiserror = { version = "1.0.63", default-features = false }
+tokio = { version = "1.39.1", default-features = false }
 tokio-test = { version = "0.4.4", default-features = false }
 tracing = { version = "0.1.40", default-features = false }
 tracing-subscriber = { version = "0.3.18", default-features = false }
 url = { version = "2.5.2", default-features = false }
 urlencoding = { version = "2.1.3", default-features = false }
 web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false }
+zhconv = "0.3.1"
 
 # subdependencies
 ## explicitly list OpenSSL to use the vendored version
-openssl = "0.10.64"
+openssl = "0.10.66"
 
 ## some subdependencies require higher Rust version than 1.74 (our MSRV)
 ## cargo update && cargo update ravif --precise 0.11.5 && cargo update bitstream-io --precise 2.3.0
diff --git a/docs/api-change.md b/docs/api-change.md
index 3082ba295c..d5a7a25f78 100644
--- a/docs/api-change.md
+++ b/docs/api-change.md
@@ -2,6 +2,10 @@
 
 Breaking changes are indicated by the :warning: icon.
 
+## v20240725
+
+- Added `i/export-followers` endpoint.
+
 ## v20240714
 
 - The old Mastodon API has been replaced with a new implementation based on Iceshrimp’s.
diff --git a/docs/changelog.md b/docs/changelog.md
index bd4974f490..8b37ce6616 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,6 +5,13 @@ Critical security updates are indicated by the :warning: icon.
 - Server administrators must check [notice-for-admins.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/notice-for-admins.md) as well.
 - Third-party client/bot developers may want to check [api-change.md](https://firefish.dev/firefish/firefish/-/blob/main/docs/api-change.md) as well.
 
+## [v20240725](https://firefish.dev/firefish/firefish/-/merge_requests/11196/commits)
+
+- Add followers list export feature
+- Add description about excluding conditions (e.g., 'firefish -info.firefish.dev', '(sleepy OR eepy) -morning') in post search
+	- Technically this is not a new feature
+- Fix bugs
+
 ## [v20240714](https://firefish.dev/firefish/firefish/-/merge_requests/11146/commits)
 
 - Mastodon API implementation was ported from Iceshrimp, with added Firefish extensions including push notifications, post languages, schedule post support, and more. (#10880)
diff --git a/docs/notice-for-admins.md b/docs/notice-for-admins.md
index 868c30ac6c..366b0263e0 100644
--- a/docs/notice-for-admins.md
+++ b/docs/notice-for-admins.md
@@ -6,6 +6,14 @@ You can skip intermediate versions when upgrading from an old version, but pleas
 
 Please take a look at #10947.
 
+## v20240725
+
+### For LibreTranslate self-hosters
+
+Previously, neither the DeepL API nor the LibreTranslate API provided traditional Chinese translations, so we used to provide traditional Chinese post translations using manual conversion from simplified Chinese translations.
+
+However, now that LibreTranslate API supports traditional Chinese translations, we have removed the manual conversion process for LibreTranslate. So, if you are hosting your LibreTranslate instance, please ensure your LibreTranslate version is new enough to support traditional Chinese.
+
 ## v20240714
 
 ### For systemd/pm2 users
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 12b356a05e..89aede8cdd 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -2240,12 +2240,14 @@ searchWordsDescription: "Per cercar publicacions, escriu el terme a buscar. Sepa
   les paraules amb espais per fer condicions AND o escriules dins de cometes per fer
   una cerca OR.\nPer exemple, 'dia nit' trobarà publicacions que continguin tan 'dia'
   com 'nit', i 'dia OR nit' trobara publicacions que continguin tant 'dia' com 'nit'
-  (o ambdues).\nPots combinar condicions AND/OR per exemple '(dia OR nit) endormiscar'.\n
-  Si vols cercar per una seqüencia de paraules (per exemple una frase) has d'escriure-les
-  entre cometes dobles, per no fer una cerca amb condicionant AND: \"Avui he aprés\"\
-  \n \nSi vols anar a una pàgina d'usuari o publicació en concret, escriu la adreça
-  URL o la ID en aquest camp i fes clic al botó 'Ves a'. Fent clic a 'Cerca' trobarà
-  publicacions que, literalment , continguin la ID/adreça URL."
+  (o ambdues).\nPots filtrar certes paraules en els resultats de la cerca, com 'endormiscat
+  -matí -esmorzar'. Encara més, pots combinar aquestes condicions AND/OR/exclude d'aquesta
+  manera '(mati OR nit) endormiscat -esmorzar'.\n Si vols cercar per una seqüencia
+  de paraules (per exemple una frase) has d'escriure-les entre cometes dobles, per
+  no fer una cerca amb condicionant AND: \"Avui he aprés\"\n \nSi vols anar a una
+  pàgina d'usuari o publicació en concret, escriu la adreça URL o la ID en aquest
+  camp i fes clic al botó 'Ves a'. Fent clic a 'Cerca' trobarà publicacions que, literalment
+  , continguin la ID/adreça URL."
 searchPostsWithFiles: Només publicacions amb fitxers
 searchCwAndAlt: Inclou avisos de contingut i arxius amb descripcions
 searchUsers: Publicat per (opcional)
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 3bafec3eb8..d89ff098ac 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1211,10 +1211,11 @@ searchWordsDescription: "Enter the search term here to search for posts. Separat
   words with a space for an AND search, or 'OR' (without quotes) between words for
   an OR search.\nFor example, 'morning night' will find posts that contain both 'morning'
   and 'night', and 'morning OR night' will find posts that contain either 'morning'
-  or 'night' (or both).\nYou can also combine AND/OR conditions like '(morning OR
-  night) sleepy'.\nIf you want to search for a sequence of words (e.g., a sentence),
+  or 'night' (or both).\nYou can also filter out certain word(s) from the search results, like
+  'sleepy -morning -breakfast'. Moreover, you can combine these AND/OR/exclude conditions like
+  '(morning OR night) sleepy -breakfast'.\nIf you want to search for a sequence of words (e.g., a sentence),
   you must put it in double quotes, not to make it an AND search: \"Today I learned\"\
-  \n\n If you want to go to a specific user page or post page, enter the ID or URL
+  \n\nIf you want to go to a specific user page or post page, enter the ID or URL
   in this field and click the 'Lookup' button. Clicking 'Search' will search for posts
   that literally contain the ID/URL."
 searchUsers: "Posted by (optional)"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 39f725c5c5..0bdc02ee48 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -2195,3 +2195,29 @@ squareCatAvatars: Mostrar avatares cuadrados para las cuentas de gatos
 useThisAccountConfirm: ¿Quieres continuar con esta cuenta?
 i18nServerInfo: Nuevos clientes estarán en {language} por defecto.
 media: Medios
+ipFirstAcknowledged: Fecha de la primera adquisición de la dirección IP
+driveCapacityOverride: Anulación de la capacidad de accionamiento
+useCdn: Obtener activos de CDN
+replaceChatButtonWithAccountButton: Reemplazar boton del chat con el boton de cambio
+  de cuenta
+forMobile: Móvil
+emojiModPerm: Permiso de gestión de emoji personalizado
+replaceWidgetsButtonWithReloadButton: Reemplazar botón del los widgets con el boton
+  de recarga
+inputAccountId: Introduce tu cuenta (ej., @firefish@info.firefish.dev)
+remoteFollow: Seguimiento remoto
+useCdnDescription: Cargar algunos activos estáticos como Twemoji desde JSDelivr CDN
+  en lugar de este servidor de Firefish.
+suggested: Sugerido
+noLanguage: Ningún idioma
+showPreviewByDefault: Mostrar vista previa en el formulario de publicación por defecto
+preventMisclick: Prevención de clic accidental
+announcement: Anuncio
+moderationNote: Nota de moderación
+getQrCode: Mostrar código QR
+copyRemoteFollowUrl: Copiar URL de seguimiento remoto
+hideFollowButtons: Ocultar botón de seguimiento en una posición en la que se pueda
+  hacer clic erróneamente
+searchEngine: Motor de búsqueda usado en la barra de búsqueda MFM
+postSearch: Búsqueda de publicaciones en este servidor
+showBigPostButton: Mostrar un gran botón de Publicar en el formulario de publicación
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 22d9d78e65..1f63d19fa9 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -2222,11 +2222,12 @@ searchWordsDescription: "Masukkan kata kunci di sini untuk mencari postingan. Pi
   kata dengan spasi untuk pencarian AND (dan), atau 'OR' ('atau', tanpa tanda kutip)
   di antara kata-kata untuk pencarian OR.\nMisalnya, 'pagi malam' akan menemukan postingan
   yang mengandung 'pagi' dan 'malam', dan 'pagi OR malam' akan menemukan postingan
-  yang mengandung 'pagi' atau 'malam' (atau keduanya).\nAnda juga dapat menggabungkan
-  kondisi AND/OR seperti '(pagi OR malam) mengantuk'.\n\n Jika kamu ingin membuka
-  halaman pengguna atau halaman postingan tertentu, masukkan ID atau URL pada kolom
-  ini dan klik tombol 'Cari'. Mengeklik 'Cari' akan mencari postingan yang secara
-  harfiah mengandung ID/URL."
+  yang mengandung 'pagi' atau 'malam' (atau keduanya).\nAnda juga dapat memfilter
+  kata tertentu dari hasil pencarian, seperti 'mengantuk -pagi -sarapan'. Selain itu,
+  Anda dapat menggabungkan AND/OR/tidak sertakan ini seperti '(pagi OR malam) mengantuk'
+  -sarapan.\n\n Jika kamu ingin membuka halaman pengguna atau halaman postingan tertentu,
+  masukkan ID atau URL pada kolom ini dan klik tombol 'Cari'. Mengeklik 'Cari' akan
+  mencari postingan yang secara harfiah mengandung ID/URL."
 pullToRefreshThreshold: Jarak penarikan untuk memuat ulang
 releaseToReload: Lepaskan untuk memuat ulang
 reloading: Memuat ulang
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 354e47b9b4..4e8c4edc57 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1011,10 +1011,10 @@ reloading: "読み込み中"
 enableTimelineStreaming: "タイムラインを自動で更新する"
 searchWords: "検索語句・照会するIDやURL"
 searchWordsDescription: "投稿を検索するには、ここに検索語句を入力してください。空白区切りでAND検索になり、ORを挟むとOR検索になります。\n
-  例えば「朝 夜」と入力すると「朝」と「夜」が両方含まれた投稿を検索し、「朝 OR 夜」と入力すると「朝」または「夜」(または両方)が含まれた投稿を検索します。\n
-  「(朝 OR 夜) 眠い」のように、AND検索とOR検索を同時に行うこともできます。\n空白を含む文字列をAND検索ではなくそのまま検索したい場合、\"明日 買うもの\"\
-  \ のように二重引用符 (\") で囲む必要があります。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID (@user@example.com)
-  や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
+  例えば「朝 夜」と入力すると「朝」と「夜」が両方含まれた投稿を検索し、「朝 OR 夜」と入力すると「朝」または「夜」(または両方)が含まれた投稿を検索します。また、「眠い
+  -朝 -夜」のように特定の単語を除外した検索も可能です。\n 「(朝 OR 夜) 眠い -朝ごはん」のように、AND・OR・除外の条件を組み合わせることもできます。\n\
+  空白を含む文字列をAND検索ではなくそのまま検索したい場合、\"明日 買うもの\" のように二重引用符 (\") で囲む必要があります。\n\n特定のユーザーや投稿のページに飛びたい場合には、この欄にID
+  (@user@example.com) や投稿のURLを入力し「照会」を押してください。「検索」を押すとそのIDやURLが文字通り含まれる投稿を検索します。"
 searchUsers: "投稿元(省略可)"
 searchUsersDescription: "投稿検索で投稿者を絞りたい場合、@user@example.com(ローカルユーザーなら @user)の形式で投稿者のIDを入力してください。ユーザーIDではなくドメイン名
   (example.com) を指定すると、そのサーバーの投稿を検索します。\n\nme とだけ入力すると、自分の投稿を検索します。この検索結果には未収載・フォロワー限定・ダイレクト・秘密を含む全ての投稿が含まれます。\n
diff --git a/package.json b/package.json
index bf1646b4f9..36623033f2 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
 {
 	"name": "firefish",
-	"version": "20240714",
+	"version": "20240725",
 	"repository": {
 		"type": "git",
 		"url": "https://firefish.dev/firefish/firefish.git"
 	},
-	"packageManager": "pnpm@9.5.0",
+	"packageManager": "pnpm@9.6.0",
 	"private": true,
 	"scripts": {
 		"rebuild": "pnpm run clean && pnpm run build",
@@ -47,8 +47,8 @@
 		"@biomejs/cli-darwin-x64": "1.8.3",
 		"@biomejs/cli-linux-arm64": "1.8.3",
 		"@biomejs/cli-linux-x64": "1.8.3",
-		"@types/node": "20.14.10",
+		"@types/node": "20.14.12",
 		"execa": "9.3.0",
-		"pnpm": "9.5.0"
+		"pnpm": "9.6.0"
 	}
 }
diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml
index 59bacd04a4..f6176ec0a4 100644
--- a/packages/backend-rs/Cargo.toml
+++ b/packages/backend-rs/Cargo.toml
@@ -29,7 +29,7 @@ cuid2 = { workspace = true }
 emojis = { workspace = true }
 idna = { workspace = true, features = ["std", "compiled_data"] }
 image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
-isahc = { workspace = true, features = ["http2", "text-decoding"] }
+isahc = { workspace = true, features = ["http2", "text-decoding", "json"] }
 nom-exif = { workspace = true }
 once_cell = { workspace = true }
 openssl = { workspace = true, features = ["vendored"] }
@@ -49,6 +49,7 @@ tracing-subscriber = { workspace = true, features = ["ansi"] }
 url = { workspace = true }
 urlencoding = { workspace = true }
 web-push = { workspace = true, features = ["isahc-client"] }
+zhconv = { workspace = true }
 
 [dev-dependencies]
 pretty_assertions = { workspace = true, features = ["std"] }
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index 7f21436eb9..3385d524a0 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -270,8 +270,6 @@ export declare function cpuInfo(): Cpu
 
 export declare function cpuUsage(): number
 
-export const DAY: number
-
 export interface DbConfig {
   host: string
   port: number
@@ -378,17 +376,6 @@ export declare function fetchMeta(): Promise<Meta>
 /** Fetches and returns the NodeInfo (version 2.0) of a remote server. */
 export declare function fetchNodeinfo(host: string): Promise<Nodeinfo>
 
-/**
- * List of file types allowed to be viewed directly in the browser
- *
- * Anything not included here will be responded as application/octet-stream
- * SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
- * * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
- * * <https://github.com/sindresorhus/file-type/blob/main/core.js>
- * * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
- */
-export const FILE_TYPE_BROWSERSAFE: string[]
-
 export interface Following {
   id: string
   createdAt: DateTimeWithTimeZone
@@ -490,8 +477,6 @@ export interface Hashtag {
   attachedRemoteUsersCount: number
 }
 
-export const HOUR: number
-
 export interface IdConfig {
   length?: number
   fingerprint?: string
@@ -555,7 +540,7 @@ export type InternalActor =  'instance'|
  * `host` - punycoded instance host
  *
  * # Example
- * ```no_run
+ * ```ignore
  * # use backend_rs::misc::check_server_block::is_allowed_server;
  * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
  * assert_eq!(true, is_allowed_server("allowed.com").await?);
@@ -575,7 +560,7 @@ export declare function isAllowedServer(host: string): Promise<boolean>
  * `host` - punycoded instance host
  *
  * # Example
- * ```no_run
+ * ```ignore
  * # use backend_rs::misc::check_server_block::is_blocked_server;
  * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
  * assert_eq!(true, is_blocked_server("blocked.com").await?);
@@ -606,7 +591,7 @@ export declare function isSelfHost(host?: string | undefined | null): boolean
  * `host` - punycoded instance host
  *
  * # Example
- * ```no_run
+ * ```ignore
  * # use backend_rs::misc::check_server_block::is_silenced_server;
  * # async fn f() -> Result<(), Box<dyn std::error::Error>> {
  * assert_eq!(true, is_silenced_server("silenced.com").await?);
@@ -752,8 +737,6 @@ export interface Migrations {
   name: string
 }
 
-export const MINUTE: number
-
 export interface ModerationLog {
   id: string
   createdAt: DateTimeWithTimeZone
@@ -1223,8 +1206,6 @@ export interface ReplyMuting {
 /** Returns `true` if `src` does not contain suspicious characters like `%`. */
 export declare function safeForSql(src: string): boolean
 
-export const SECOND: number
-
 export declare function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
 
 export interface ServerConfig {
@@ -1349,6 +1330,13 @@ export declare function toDbReaction(reaction?: string | undefined | null, host?
 
 export declare function toPuny(host: string): string
 
+export declare function translate(text: string, sourceLang: string | undefined | null, targetLang: string): Promise<Translation>
+
+export interface Translation {
+  sourceLang: string
+  text: string
+}
+
 export declare function unwatchNote(watcherId: string, noteId: string): Promise<void>
 
 export declare function updateAntennaCache(): Promise<void>
@@ -1413,10 +1401,6 @@ export interface User {
   readCatLanguage: boolean
 }
 
-export const USER_ACTIVE_THRESHOLD: number
-
-export const USER_ONLINE_THRESHOLD: number
-
 export type UserEmojiModPerm =  'add'|
 'full'|
 'mod'|
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 139739ac00..4d6d72e1cb 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -336,7 +336,7 @@ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
     nativeBinding = require('./backend-rs.wasi.cjs')
   } catch (err) {
     if (process.env.NAPI_RS_FORCE_WASI) {
-      console.error(err)
+      loadErrors.push(err)
     }
   }
   if (!nativeBinding) {
@@ -344,7 +344,7 @@ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
       nativeBinding = require('backend-rs-wasm32-wasi')
     } catch (err) {
       if (process.env.NAPI_RS_FORCE_WASI) {
-        console.error(err)
+        loadErrors.push(err)
       }
     }
   }
@@ -370,7 +370,6 @@ module.exports.countLocalUsers = nativeBinding.countLocalUsers
 module.exports.countReactions = nativeBinding.countReactions
 module.exports.cpuInfo = nativeBinding.cpuInfo
 module.exports.cpuUsage = nativeBinding.cpuUsage
-module.exports.DAY = nativeBinding.DAY
 module.exports.decodeReaction = nativeBinding.decodeReaction
 module.exports.DriveFileEvent = nativeBinding.DriveFileEvent
 module.exports.DriveFileUsageHint = nativeBinding.DriveFileUsageHint
@@ -378,7 +377,6 @@ module.exports.DriveFolderEvent = nativeBinding.DriveFolderEvent
 module.exports.extractHost = nativeBinding.extractHost
 module.exports.fetchMeta = nativeBinding.fetchMeta
 module.exports.fetchNodeinfo = nativeBinding.fetchNodeinfo
-module.exports.FILE_TYPE_BROWSERSAFE = nativeBinding.FILE_TYPE_BROWSERSAFE
 module.exports.formatMilliseconds = nativeBinding.formatMilliseconds
 module.exports.generateSecureRandomString = nativeBinding.generateSecureRandomString
 module.exports.generateUserToken = nativeBinding.generateUserToken
@@ -391,7 +389,6 @@ module.exports.getNoteSummary = nativeBinding.getNoteSummary
 module.exports.getTimestamp = nativeBinding.getTimestamp
 module.exports.greet = nativeBinding.greet
 module.exports.hashPassword = nativeBinding.hashPassword
-module.exports.HOUR = nativeBinding.HOUR
 module.exports.Inbound = nativeBinding.Inbound
 module.exports.initializeRustLogger = nativeBinding.initializeRustLogger
 module.exports.InternalActor = nativeBinding.InternalActor
@@ -408,7 +405,6 @@ module.exports.latestVersion = nativeBinding.latestVersion
 module.exports.loadConfig = nativeBinding.loadConfig
 module.exports.memoryUsage = nativeBinding.memoryUsage
 module.exports.metaToPugArgs = nativeBinding.metaToPugArgs
-module.exports.MINUTE = nativeBinding.MINUTE
 module.exports.MutedNoteReason = nativeBinding.MutedNoteReason
 module.exports.nodeinfo_2_0 = nativeBinding.nodeinfo_2_0
 module.exports.nodeinfo_2_1 = nativeBinding.nodeinfo_2_1
@@ -433,7 +429,6 @@ module.exports.PushSubscriptionType = nativeBinding.PushSubscriptionType
 module.exports.RelayStatus = nativeBinding.RelayStatus
 module.exports.removeOldAttestationChallenges = nativeBinding.removeOldAttestationChallenges
 module.exports.safeForSql = nativeBinding.safeForSql
-module.exports.SECOND = nativeBinding.SECOND
 module.exports.sendPushNotification = nativeBinding.sendPushNotification
 module.exports.shouldNyaify = nativeBinding.shouldNyaify
 module.exports.showServerInfo = nativeBinding.showServerInfo
@@ -443,13 +438,12 @@ module.exports.storageUsage = nativeBinding.storageUsage
 module.exports.stringToAcct = nativeBinding.stringToAcct
 module.exports.toDbReaction = nativeBinding.toDbReaction
 module.exports.toPuny = nativeBinding.toPuny
+module.exports.translate = nativeBinding.translate
 module.exports.unwatchNote = nativeBinding.unwatchNote
 module.exports.updateAntennaCache = nativeBinding.updateAntennaCache
 module.exports.updateAntennasOnNewNote = nativeBinding.updateAntennasOnNewNote
 module.exports.updateMetaCache = nativeBinding.updateMetaCache
 module.exports.updateNodeinfoCache = nativeBinding.updateNodeinfoCache
-module.exports.USER_ACTIVE_THRESHOLD = nativeBinding.USER_ACTIVE_THRESHOLD
-module.exports.USER_ONLINE_THRESHOLD = nativeBinding.USER_ONLINE_THRESHOLD
 module.exports.UserEmojiModPerm = nativeBinding.UserEmojiModPerm
 module.exports.UserProfileFfvisibility = nativeBinding.UserProfileFfvisibility
 module.exports.UserProfileMutingNotificationTypes = nativeBinding.UserProfileMutingNotificationTypes
diff --git a/packages/backend-rs/package.json b/packages/backend-rs/package.json
index 6488ef49ed..a33effccb0 100644
--- a/packages/backend-rs/package.json
+++ b/packages/backend-rs/package.json
@@ -8,7 +8,7 @@
 		"binaryName": "backend-rs"
 	},
 	"devDependencies": {
-		"@napi-rs/cli": "3.0.0-alpha.58"
+		"@napi-rs/cli": "3.0.0-alpha.62"
 	},
 	"scripts": {
 		"build": "napi build --features napi --no-const-enum --platform --release --output-dir ./built/",
diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs
deleted file mode 100644
index 7e6b50fa52..0000000000
--- a/packages/backend-rs/src/config/constant.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-//! This module is used in the TypeScript backend only.
-
-#[macros::ts_export]
-pub const SECOND: i32 = 1000;
-#[macros::ts_export]
-pub const MINUTE: i32 = 60 * SECOND;
-#[macros::ts_export]
-pub const HOUR: i32 = 60 * MINUTE;
-#[macros::ts_export]
-pub const DAY: i32 = 24 * HOUR;
-
-#[macros::ts_export]
-pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
-#[macros::ts_export]
-pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
-
-/// List of file types allowed to be viewed directly in the browser
-///
-/// Anything not included here will be responded as application/octet-stream
-/// SVG is not allowed because it generates XSS (TODO: fix this and later allow it to be viewed directly)
-/// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
-/// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
-/// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
-#[macros::ts_export]
-pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
-    // Images
-    "image/png",
-    "image/gif", // TODO: deprecated, but still used by old posts, new gifs should be converted to webp in the future
-    "image/jpeg",
-    "image/webp", // TODO: make this the default image format
-    "image/apng",
-    "image/bmp",
-    "image/tiff",
-    "image/x-icon",
-    "image/avif", // not as good supported now, but its good to introduce initial support for the future
-    // OggS
-    "audio/opus",
-    "video/ogg",
-    "audio/ogg",
-    "application/ogg",
-    // ISO/IEC base media file format
-    "video/quicktime",
-    "video/mp4",     // TODO: we need to check for av1 later
-    "video/vnd.avi", // also av1
-    "audio/mp4",
-    "video/x-m4v",
-    "audio/x-m4a",
-    "video/3gpp",
-    "video/3gpp2",
-    "video/3gp2",
-    "audio/3gpp",
-    "audio/3gpp2",
-    "audio/3gp2",
-    "video/mpeg",
-    "audio/mpeg",
-    "video/webm",
-    "audio/webm",
-    "audio/aac",
-    "audio/x-flac",
-    "audio/flac",
-    "audio/vnd.wave",
-    "audio/mod",
-    "audio/x-mod",
-    "audio/s3m",
-    "audio/x-s3m",
-    "audio/xm",
-    "audio/x-xm",
-    "audio/it",
-    "audio/x-it",
-];
diff --git a/packages/backend-rs/src/config/mod.rs b/packages/backend-rs/src/config/mod.rs
index a1cfb7fd75..e447d4b059 100644
--- a/packages/backend-rs/src/config/mod.rs
+++ b/packages/backend-rs/src/config/mod.rs
@@ -3,6 +3,5 @@
 pub use meta::local_server_info;
 pub use server::CONFIG;
 
-pub mod constant;
 pub mod meta;
 pub mod server;
diff --git a/packages/backend-rs/src/database/cache.rs b/packages/backend-rs/src/database/cache.rs
index 515053ef6c..d56da7f680 100644
--- a/packages/backend-rs/src/database/cache.rs
+++ b/packages/backend-rs/src/database/cache.rs
@@ -14,7 +14,7 @@ pub enum Category {
     Test,
 }
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("failed to execute Redis command")]
     Redis(#[from] RedisError),
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
index 7fa7255b47..a17271f44f 100644
--- a/packages/backend-rs/src/database/redis.rs
+++ b/packages/backend-rs/src/database/redis.rs
@@ -82,7 +82,7 @@ async fn init_conn_pool() -> Result<(), RedisError> {
     Ok(())
 }
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum RedisConnError {
     #[error("failed to initialize Redis connection pool")]
     Redis(RedisError),
diff --git a/packages/backend-rs/src/federation/internal_actor/cache.rs b/packages/backend-rs/src/federation/internal_actor/cache.rs
index 80f0ad22bc..de250f3d20 100644
--- a/packages/backend-rs/src/federation/internal_actor/cache.rs
+++ b/packages/backend-rs/src/federation/internal_actor/cache.rs
@@ -7,7 +7,7 @@ use crate::{database::db_conn, model::entity::user};
 use sea_orm::prelude::*;
 use std::sync::Mutex;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error(transparent)]
     #[doc = "database error"]
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index c237a893fb..e7bdb0b071 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -7,7 +7,7 @@ use isahc::AsyncReadResponseExt;
 use serde::Deserialize;
 
 /// Errors that can occur while fetching NodeInfo from a remote server
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("failed to acquire an HTTP client")]
     HttpClient(#[from] http_client::Error),
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index c74fd55e10..4ab7ef65fa 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -152,8 +152,8 @@ pub async fn nodeinfo_2_0() -> Result<Nodeinfo20, DbErr> {
     Ok(nodeinfo_2_1().await?.into())
 }
 
-#[cfg(feature = "napi")]
-#[derive(thiserror::Error, Debug)]
+#[cfg(any(test, doctest, feature = "napi"))]
+#[macros::errors]
 pub enum Error {
     #[doc = "database error"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs
index ecc50132ad..7456cc6f8a 100644
--- a/packages/backend-rs/src/misc/check_server_block.rs
+++ b/packages/backend-rs/src/misc/check_server_block.rs
@@ -8,7 +8,7 @@
 /// `host` - punycoded instance host
 ///
 /// # Example
-/// ```no_run
+/// ```ignore
 /// # use backend_rs::misc::check_server_block::is_blocked_server;
 /// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
 /// assert_eq!(true, is_blocked_server("blocked.com").await?);
@@ -35,7 +35,7 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
 /// `host` - punycoded instance host
 ///
 /// # Example
-/// ```no_run
+/// ```ignore
 /// # use backend_rs::misc::check_server_block::is_silenced_server;
 /// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
 /// assert_eq!(true, is_silenced_server("silenced.com").await?);
@@ -63,7 +63,7 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
 /// `host` - punycoded instance host
 ///
 /// # Example
-/// ```no_run
+/// ```ignore
 /// # use backend_rs::misc::check_server_block::is_allowed_server;
 /// # async fn f() -> Result<(), Box<dyn std::error::Error>> {
 /// assert_eq!(true, is_allowed_server("allowed.com").await?);
diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs
index 0de4d1912d..fff1f9fb4c 100644
--- a/packages/backend-rs/src/misc/convert_host.rs
+++ b/packages/backend-rs/src/misc/convert_host.rs
@@ -2,7 +2,7 @@
 // We may want to (re)implement these functions in the `federation` module
 // in a Rusty way (e.g., traits of actor type) if needed.
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[doc = "UTS #46 process has failed"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index bc5222f054..939f17f755 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -1,11 +1,11 @@
 use crate::{database::cache, util::http_client};
-use image::{io::Reader, ImageError, ImageFormat};
+use image::{ImageError, ImageFormat, ImageReader};
 use isahc::AsyncReadResponseExt;
 use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag};
 use std::io::Cursor;
 use tokio::sync::Mutex;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("Redis cache operation has failed")]
     Cache(#[from] cache::Error),
@@ -87,7 +87,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
 
     let image_bytes = response.bytes().await?;
 
-    let reader = Reader::new(Cursor::new(&image_bytes)).with_guessed_format()?;
+    let reader = ImageReader::new(Cursor::new(&image_bytes)).with_guessed_format()?;
 
     let format = reader.format();
     if format.is_none() || !BROWSER_SAFE_IMAGE_TYPES.contains(&format.unwrap()) {
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 1f330f4d2e..f49ac81099 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -4,7 +4,7 @@ use crate::{database::cache, util::http_client};
 use isahc::AsyncReadResponseExt;
 use serde::Deserialize;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("Redis cache operation has failed")]
     Cache(#[from] cache::Error),
diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs
index afa3b1396e..4c715f965c 100644
--- a/packages/backend-rs/src/misc/mod.rs
+++ b/packages/backend-rs/src/misc/mod.rs
@@ -17,4 +17,5 @@ pub mod reaction;
 pub mod remove_old_attestation_challenges;
 pub mod should_nyaify;
 pub mod system_info;
+pub mod translate;
 pub mod user;
diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index bc2025f275..8d6be4101e 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -15,7 +15,7 @@ pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Er
         .to_string())
 }
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("failed to verify password against bcrypt hash")]
     Bcrypt(#[from] bcrypt::BcryptError),
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index e6b699d59a..afae0a66ac 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -53,7 +53,7 @@ pub fn count_reactions(reactions: &HashMap<String, u32>) -> HashMap<String, u32>
     res
 }
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[doc = "UTS #46 process has failed"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/misc/should_nyaify.rs b/packages/backend-rs/src/misc/should_nyaify.rs
index bf4166f18d..8476678c77 100644
--- a/packages/backend-rs/src/misc/should_nyaify.rs
+++ b/packages/backend-rs/src/misc/should_nyaify.rs
@@ -6,7 +6,7 @@ use crate::{
 };
 use sea_orm::{DbErr, EntityTrait, QuerySelect, SelectColumns};
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[doc = "database error"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/misc/translate.rs b/packages/backend-rs/src/misc/translate.rs
new file mode 100644
index 0000000000..7e9d5a005f
--- /dev/null
+++ b/packages/backend-rs/src/misc/translate.rs
@@ -0,0 +1,272 @@
+use crate::{
+    config::{local_server_info, server, CONFIG},
+    util::http_client,
+};
+
+#[macros::errors]
+pub enum Error {
+    #[doc = "database error"]
+    #[error(transparent)]
+    Db(#[from] sea_orm::DbErr),
+    #[error("failed to acquire an HTTP client")]
+    HttpClient(#[from] http_client::Error),
+    #[error("invalid http request body")]
+    InvalidRequestBody(#[from] isahc::http::Error),
+    #[error("http request failed")]
+    HttpRequest(#[from] isahc::Error),
+    #[error("failed to serialize the request body")]
+    Serialize(#[from] serde_json::Error),
+    #[error("Libretranslate API url is not set")]
+    MissingApiUrl,
+    #[error("DeepL API key is not set")]
+    MissingApiKey,
+    #[error("no response")]
+    NoResponse,
+    #[error("translator is not set")]
+    NoTranslator,
+}
+
+#[macros::export(object)]
+pub struct Translation {
+    pub source_lang: String,
+    pub text: String,
+}
+
+#[inline]
+fn is_zh_hant_tw(lang: &str) -> bool {
+    ["zh-tw", "zh-hant", "zh-hant-tw"].contains(&lang.to_ascii_lowercase().as_str())
+}
+
+#[macros::export]
+pub async fn translate(
+    text: &str,
+    source_lang: Option<&str>,
+    target_lang: &str,
+) -> Result<Translation, Error> {
+    let config = local_server_info().await?;
+
+    let translation = if let Some(api_key) = config.deepl_auth_key {
+        deepl_translate::translate(
+            text,
+            source_lang,
+            target_lang,
+            &api_key,
+            config.deepl_is_pro,
+        )
+        .await?
+    } else if let Some(api_url) = config.libre_translate_api_url {
+        libre_translate::translate(
+            text,
+            source_lang,
+            target_lang,
+            &api_url,
+            config.libre_translate_api_key.as_deref(),
+        )
+        .await?
+    } else if let Some(server::DeepLConfig {
+        auth_key, is_pro, ..
+    }) = CONFIG.deepl.as_ref()
+    {
+        deepl_translate::translate(
+            text,
+            source_lang,
+            target_lang,
+            auth_key.as_ref().ok_or(Error::MissingApiKey)?,
+            is_pro.unwrap_or(false),
+        )
+        .await?
+    } else if let Some(server::LibreTranslateConfig {
+        api_url, api_key, ..
+    }) = CONFIG.libre_translate.as_ref()
+    {
+        libre_translate::translate(
+            text,
+            source_lang,
+            target_lang,
+            api_url.as_ref().ok_or(Error::MissingApiUrl)?,
+            api_key.as_deref(),
+        )
+        .await?
+    } else {
+        return Err(Error::NoTranslator);
+    };
+
+    Ok(translation)
+}
+
+mod deepl_translate {
+    use crate::util::http_client;
+    use isahc::{AsyncReadResponseExt, Request};
+    use serde::Deserialize;
+    use serde_json::json;
+
+    #[derive(Deserialize)]
+    struct Response {
+        translations: Vec<Translation>,
+    }
+
+    #[derive(Deserialize, Clone)]
+    struct Translation {
+        detected_source_language: Option<String>,
+        text: String,
+    }
+
+    pub(super) async fn translate(
+        text: &str,
+        source_lang: Option<&str>,
+        target_lang: &str,
+        api_key: &str,
+        is_pro: bool,
+    ) -> Result<super::Translation, super::Error> {
+        let client = http_client::client()?;
+
+        let api_url = if is_pro {
+            "https://api.deepl.com/v2/translate"
+        } else {
+            "https://api-free.deepl.com/v2/translate"
+        };
+
+        let to_zh_hant_tw = super::is_zh_hant_tw(target_lang);
+
+        let mut target_lang = target_lang.split('-').collect::<Vec<&str>>()[0];
+
+        // DeepL API requires us to specify "en-US" or "en-GB" for English
+        // translations ("en" does not work), so we need to address it
+        if target_lang == "en" {
+            target_lang = "en-US";
+        }
+
+        let body = if let Some(source_lang) = source_lang {
+            let source_lang = source_lang.split('-').collect::<Vec<&str>>()[0];
+
+            json!({
+                "text": [text],
+                "source_lang": source_lang,
+                "target_lang": target_lang
+            })
+        } else {
+            json!({
+                "text": [text],
+                "target_lang": target_lang
+            })
+        };
+
+        let request = Request::post(api_url)
+            .header("Authorization", format!("DeepL-Auth-Key {}", api_key))
+            .header("Content-Type", "application/json")
+            .body(serde_json::to_string(&body)?)?;
+
+        let response = client.send_async(request).await?.json::<Response>().await?;
+
+        let result = response
+            .translations
+            .first()
+            .ok_or(super::Error::NoResponse)?
+            .to_owned();
+
+        let mut translation = super::Translation {
+            source_lang: source_lang
+                .map(|s| s.to_owned())
+                .or(result.detected_source_language)
+                .and_then(|lang| {
+                    if lang.is_ascii() {
+                        Some(lang.to_ascii_lowercase())
+                    } else {
+                        None
+                    }
+                })
+                .unwrap_or_else(|| "unknown".to_owned()),
+            text: result.text,
+        };
+
+        // DeepL translate don't provide zh-Hant-TW translations at this moment,
+        // so we convert zh-Hans-CN translations into zh-Hant-TW using zhconv.
+        if to_zh_hant_tw {
+            translation.text = zhconv::zhconv(&translation.text, zhconv::Variant::ZhTW);
+        }
+
+        Ok(translation)
+    }
+}
+
+mod libre_translate {
+    use crate::util::http_client;
+    use isahc::{AsyncReadResponseExt, Request};
+    use serde::Deserialize;
+    use serde_json::json;
+
+    #[derive(Deserialize, Clone)]
+    #[serde(rename_all = "camelCase")]
+    struct Translation {
+        translated_text: String,
+        detected_language: DetectedLanguage,
+    }
+
+    #[derive(Deserialize, Clone)]
+    struct DetectedLanguage {
+        language: String,
+    }
+
+    pub(super) async fn translate(
+        text: &str,
+        source_lang: Option<&str>,
+        target_lang: &str,
+        api_url: &str,
+        api_key: Option<&str>,
+    ) -> Result<super::Translation, super::Error> {
+        let client = http_client::client()?;
+
+        let target_lang = if super::is_zh_hant_tw(target_lang) {
+            "zt"
+        } else {
+            target_lang.split('-').collect::<Vec<&str>>()[0]
+        };
+
+        let body = if let Some(source_lang) = source_lang {
+            let source_lang = source_lang.split('-').collect::<Vec<&str>>()[0];
+
+            json!({
+                "q": [text],
+                "source": source_lang,
+                "target": target_lang,
+                "format": "text",
+                "alternatives": 1,
+                "api_key": api_key.unwrap_or_default()
+            })
+        } else {
+            json!({
+                "q": [text],
+                "source": "auto",
+                "target": target_lang,
+                "format": "text",
+                "alternatives": 1,
+                "api_key": api_key.unwrap_or_default()
+            })
+        };
+
+        let request = Request::post(api_url)
+            .header("Content-Type", "application/json")
+            .body(serde_json::to_string(&body)?)?;
+
+        let result = client
+            .send_async(request)
+            .await?
+            .json::<Translation>()
+            .await?;
+
+        Ok(super::Translation {
+            source_lang: source_lang
+                .map(|s| s.to_owned())
+                .or(Some(result.detected_language.language))
+                .and_then(|lang| {
+                    if lang.is_ascii() {
+                        Some(lang.to_ascii_lowercase())
+                    } else {
+                        None
+                    }
+                })
+                .unwrap_or_else(|| "unknown".to_owned()),
+            text: result.translated_text,
+        })
+    }
+}
diff --git a/packages/backend-rs/src/service/antenna/check_hit.rs b/packages/backend-rs/src/service/antenna/check_hit.rs
index 794b2f794e..0ef8f13595 100644
--- a/packages/backend-rs/src/service/antenna/check_hit.rs
+++ b/packages/backend-rs/src/service/antenna/check_hit.rs
@@ -6,7 +6,7 @@ use crate::{
 };
 use sea_orm::{prelude::*, QuerySelect};
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum AntennaCheckError {
     #[doc = "database error"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/service/antenna/process_new_note.rs b/packages/backend-rs/src/service/antenna/process_new_note.rs
index 0588c3c3f3..d9099f867f 100644
--- a/packages/backend-rs/src/service/antenna/process_new_note.rs
+++ b/packages/backend-rs/src/service/antenna/process_new_note.rs
@@ -13,7 +13,7 @@ use crate::{
 use redis::{streams::StreamMaxlen, AsyncCommands, RedisError};
 use sea_orm::prelude::*;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[doc = "database error"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs
index cfe1d99ae8..cbf3be0793 100644
--- a/packages/backend-rs/src/service/push_notification.rs
+++ b/packages/backend-rs/src/service/push_notification.rs
@@ -13,7 +13,7 @@ use sea_orm::prelude::*;
 use serde::Deserialize;
 use web_push::*;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[doc = "database error"]
     #[error(transparent)]
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index 25f5802eef..d1acbfa4a7 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -62,7 +62,7 @@ pub enum ChatEvent {
     Typing,
 }
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("failed to execute a Redis command")]
     Redis(#[from] RedisError),
diff --git a/packages/backend-rs/src/util/error_chain.rs b/packages/backend-rs/src/util/error_chain.rs
index d19c99ae76..59af604578 100644
--- a/packages/backend-rs/src/util/error_chain.rs
+++ b/packages/backend-rs/src/util/error_chain.rs
@@ -34,7 +34,7 @@ mod unit_test {
         #[error("unexpected string '{0}'")]
         struct InnerError2(String);
 
-        #[derive(thiserror::Error, Debug)]
+        #[macros::errors]
         enum ErrorVariants {
             #[error("error 1 occured")]
             Error1(#[from] InnerError1),
diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs
index 3b56d57c6e..0347334b35 100644
--- a/packages/backend-rs/src/util/http_client.rs
+++ b/packages/backend-rs/src/util/http_client.rs
@@ -5,7 +5,7 @@ use isahc::{config::*, HttpClient};
 use once_cell::sync::OnceCell;
 use std::time::Duration;
 
-#[derive(thiserror::Error, Debug)]
+#[macros::errors]
 pub enum Error {
     #[error("HTTP request failed")]
     Isahc(#[from] isahc::Error),
diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json
deleted file mode 100644
index f836f9e900..0000000000
--- a/packages/backend/.mocharc.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-	"extension": ["ts","js","cjs","mjs"],
-	"node-option": [
-		"experimental-specifier-resolution=node",
-		"loader=./test/loader.js"
-	],
-	"slow": 1000,
-	"timeout": 30000,
-	"exit": true
-}
diff --git a/packages/backend/check_connect.js b/packages/backend/check_connect.js
deleted file mode 100644
index 7c1e716b3e..0000000000
--- a/packages/backend/check_connect.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { loadConfig } from "./built/config.js";
-import { createRedisConnection } from "./built/redis.js";
-
-const config = loadConfig();
-const redis = createRedisConnection(config);
-
-redis.on("connect", () => redis.disconnect());
-redis.on("error", (e) => {
-	throw e;
-});
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 2df88fbe60..2355f2d62f 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -10,33 +10,30 @@
 		"migration:run": "typeorm migration:run --dataSource ./built/ormconfig.js",
 		"migration:revert": "typeorm migration:revert --dataSource ./built/ormconfig.js",
 		"migration:new": "pnpm node ./scripts/create-migration.mjs",
-		"check:connect": "node ./check_connect.js",
 		"build": "pnpm tsc --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
 		"build:debug": "pnpm tsc --sourceMap --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
 		"watch": "pnpm tsc --project tsconfig.json --watch ; pnpm tsc-alias --project tsconfig.json --watch",
 		"lint": "pnpm biome check --write **/*.ts",
-		"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
-		"test": "pnpm run mocha",
 		"format": "pnpm biome format * --write"
 	},
 	"dependencies": {
-		"@bull-board/api": "5.21.0",
-		"@bull-board/koa": "5.21.0",
-		"@bull-board/ui": "5.21.0",
+		"@bull-board/api": "5.21.1",
+		"@bull-board/koa": "5.21.1",
+		"@bull-board/ui": "5.21.1",
 		"@discordapp/twemoji": "15.0.3",
 		"@koa/cors": "5.0.0",
 		"@koa/multer": "3.0.2",
 		"@koa/router": "12.0.1",
 		"@ladjs/koa-views": "9.0.0",
 		"@peertube/http-signature": "1.7.0",
-		"@redocly/openapi-core": "1.18.0",
+		"@redocly/openapi-core": "1.18.1",
 		"@sinonjs/fake-timers": "11.2.2",
 		"adm-zip": "0.5.14",
 		"ajv": "8.17.1",
 		"archiver": "7.0.1",
 		"async-lock": "1.4.1",
 		"async-mutex": "0.5.0",
-		"aws-sdk": "2.1659.0",
+		"aws-sdk": "2.1662.0",
 		"axios": "1.7.2",
 		"backend-rs": "workspace:*",
 		"blurhash": "2.0.5",
@@ -50,20 +47,19 @@
 		"date-fns": "3.6.0",
 		"decompress": "4.2.1",
 		"deep-email-validator": "0.1.21",
-		"deepl-node": "1.13.0",
 		"escape-regexp": "0.0.1",
 		"feed": "4.2.2",
-		"file-type": "19.1.1",
+		"file-type": "19.3.0",
 		"firefish-js": "workspace:*",
 		"fluent-ffmpeg": "2.1.3",
 		"form-data": "4.0.0",
-		"got": "14.4.1",
+		"got": "14.4.2",
 		"gunzip-maybe": "1.4.2",
 		"hpagent": "1.2.0",
 		"ioredis": "5.4.1",
 		"ip-cidr": "4.0.1",
 		"is-svg": "5.0.1",
-		"jsdom": "24.1.0",
+		"jsdom": "24.1.1",
 		"json5": "2.2.3",
 		"jsonld": "8.3.2",
 		"jsrsasign": "11.1.0",
@@ -79,12 +75,11 @@
 		"koa-send": "5.0.1",
 		"mfm-js": "0.24.0",
 		"mime-types": "2.1.35",
-		"msgpackr": "1.10.2",
+		"msgpackr": "1.11.0",
 		"multer": "1.4.5-lts.1",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.2",
 		"nodemailer": "6.9.14",
-		"opencc-js": "1.0.5",
 		"otpauth": "9.3.1",
 		"parse5": "7.1.2",
 		"pg": "8.12.0",
@@ -103,7 +98,7 @@
 		"rndstr": "1.0.0",
 		"rss-parser": "3.13.0",
 		"sanitize-html": "2.13.0",
-		"semver": "7.6.2",
+		"semver": "7.6.3",
 		"sharp": "0.33.4",
 		"stringz": "2.1.0",
 		"summaly": "2.7.0",
@@ -140,14 +135,13 @@
 		"@types/koa__cors": "5.0.0",
 		"@types/koa__multer": "2.0.7",
 		"@types/koa__router": "12.0.4",
-		"@types/mocha": "10.0.7",
-		"@types/node": "20.14.10",
+		"@types/node": "20.14.12",
 		"@types/node-fetch": "2.6.11",
 		"@types/nodemailer": "6.4.15",
 		"@types/oauth": "0.9.5",
 		"@types/opencc-js": "1.0.3",
 		"@types/pg": "8.11.6",
-		"@types/probe-image-size": "7.2.4",
+		"@types/probe-image-size": "7.2.5",
 		"@types/pug": "2.0.10",
 		"@types/punycode": "2.1.4",
 		"@types/qrcode": "1.5.5",
@@ -165,15 +159,14 @@
 		"@types/websocket": "1.0.10",
 		"@types/ws": "8.5.11",
 		"cross-env": "7.0.3",
-		"mocha": "10.6.0",
 		"pug": "3.0.3",
 		"strict-event-emitter-types": "2.0.0",
 		"ts-loader": "9.5.1",
 		"ts-node": "10.9.2",
 		"tsc-alias": "1.8.10",
 		"tsconfig-paths": "4.2.0",
-		"type-fest": "4.21.0",
-		"typescript": "5.5.3",
+		"type-fest": "4.23.0",
+		"typescript": "5.5.4",
 		"webpack": "5.93.0",
 		"ws": "8.18.0"
 	}
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
new file mode 100644
index 0000000000..fee1e3e1ea
--- /dev/null
+++ b/packages/backend/src/const.ts
@@ -0,0 +1,98 @@
+import { config } from "@/config.js";
+
+// If you change DB_* values, you must also change the DB schema.
+/**
+ * Maximum note text length that can be stored in DB.
+ * Surrogate pairs count as one
+ *
+ * NOTE: this can hypothetically be pushed further
+ * (up to 250000000), but will likely cause truncations
+ * and incompatibilities with other servers,
+ * as well as potential performance issues.
+ */
+export const DB_MAX_NOTE_TEXT_LENGTH = 100000;
+
+/**
+ * Maximum image description length that can be stored in DB.
+ * Surrogate pairs count as one
+ */
+export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192;
+
+
+export const MAX_NOTE_TEXT_LENGTH = Math.min(
+	config.maxNoteLength ?? 3000,
+	DB_MAX_NOTE_TEXT_LENGTH,
+);
+export const MAX_CAPTION_TEXT_LENGTH = Math.min(
+	config.maxCaptionLength ?? 1500,
+	DB_MAX_IMAGE_COMMENT_LENGTH,
+);
+
+export const SECOND = 1000;
+export const MINUTE = 60 * SECOND;
+export const HOUR = 60 * MINUTE;
+export const DAY = 24 * HOUR;
+
+export const USER_ONLINE_THRESHOLD = 10 * MINUTE;
+export const USER_ACTIVE_THRESHOLD = 3 * DAY;
+
+// List of file types allowed to be viewed directly in the browser
+// Anything not included here will be responded as application/octet-stream
+// SVG is not allowed because it generates XSS <- we need to fix this and later allow it to be viewed directly
+export const FILE_TYPE_BROWSERSAFE = [
+	// Images
+	"image/png",
+	"image/gif", // TODO: deprecated, but still used by old notes, new gifs should be converted to webp in the future
+	"image/jpeg",
+	"image/webp", // TODO: make this the default image format
+	"image/apng",
+	"image/bmp",
+	"image/tiff",
+	"image/x-icon",
+	"image/avif", // not as good supported now, but its good to introduce initial support for the future
+
+	// OggS
+	"audio/opus",
+	"video/ogg",
+	"audio/ogg",
+	"application/ogg",
+
+	// ISO/IEC base media file format
+	"video/quicktime",
+	"video/mp4", // TODO: we need to check for av1 later
+	"video/vnd.avi", // also av1
+	"audio/mp4",
+	"video/x-m4v",
+	"audio/x-m4a",
+	"video/3gpp",
+	"video/3gpp2",
+	"video/3gp2",
+	"audio/3gpp",
+	"audio/3gpp2",
+	"audio/3gp2",
+
+	"video/mpeg",
+	"audio/mpeg",
+
+	"video/webm",
+	"audio/webm",
+
+	"audio/aac",
+	"audio/x-flac",
+	"audio/flac",
+	"audio/vnd.wave",
+
+	"audio/mod",
+	"audio/x-mod",
+	"audio/s3m",
+	"audio/x-s3m",
+	"audio/xm",
+	"audio/x-xm",
+	"audio/it",
+	"audio/x-it",
+];
+/*
+https://github.com/sindresorhus/file-type/blob/main/supported.js
+https://github.com/sindresorhus/file-type/blob/main/core.js
+https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
+*/
diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index d1030f5125..77813eba72 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -209,7 +209,7 @@ export const db = new DataSource({
 					family: config.redis.family == null ? 0 : config.redis.family,
 					username: config.redis.user ?? "default",
 					password: config.redis.pass,
-					keyPrefix: `${config.redis.prefix}:query:`,
+					keyPrefix: `${config.redisKeyPrefix}:query:`,
 					db: config.redis.db || 0,
 					tls: config.redis.tls,
 				},
diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts
index 83fdae442e..a8ba62ec20 100644
--- a/packages/backend/src/misc/is-mime-image.ts
+++ b/packages/backend/src/misc/is-mime-image.ts
@@ -1,4 +1,4 @@
-import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 
 const dictionary = {
 	"safe-file": FILE_TYPE_BROWSERSAFE,
diff --git a/packages/backend/src/misc/skipped-instances.ts b/packages/backend/src/misc/skipped-instances.ts
index 4e720ce2d4..c57c2b9c57 100644
--- a/packages/backend/src/misc/skipped-instances.ts
+++ b/packages/backend/src/misc/skipped-instances.ts
@@ -1,5 +1,6 @@
 import { Brackets } from "typeorm";
-import { isBlockedServer, DAY } from "backend-rs";
+import { isBlockedServer } from "backend-rs";
+import { DAY } from "@/const.js";
 import { Instances } from "@/models/index.js";
 import type { Instance } from "@/models/entities/instance.js";
 
diff --git a/packages/backend/src/misc/translate.ts b/packages/backend/src/misc/translate.ts
deleted file mode 100644
index d1bcd5e72e..0000000000
--- a/packages/backend/src/misc/translate.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import fetch from "node-fetch";
-import { Converter } from "opencc-js";
-import { getAgentByUrl } from "@/misc/fetch.js";
-import { fetchMeta } from "backend-rs";
-import type { PostLanguage } from "firefish-js";
-import * as deepl from "deepl-node";
-
-// DeepL translate and LibreTranslate don't provide
-// zh-Hant-TW translations, so we convert zh-Hans-CN
-// translations into zh-Hant-TW using opencc-js.
-function convertChinese(convert: boolean, src: string) {
-	if (!convert) return src;
-	const converter = Converter({ from: "cn", to: "twp" });
-	return converter(src);
-}
-
-function stem(lang: PostLanguage): string {
-	let toReturn = lang as string;
-	if (toReturn.includes("-")) toReturn = toReturn.split("-")[0];
-	if (toReturn.includes("_")) toReturn = toReturn.split("_")[0];
-	return toReturn;
-}
-
-export async function translate(
-	text: string,
-	from: PostLanguage | null,
-	to: PostLanguage,
-) {
-	const instance = await fetchMeta();
-
-	if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
-		throw Error("No translator is set up on this server.");
-	}
-
-	const source = from == null ? null : stem(from);
-	const target = stem(to);
-
-	if (instance.libreTranslateApiUrl != null) {
-		const jsonBody = {
-			q: text,
-			source: source ?? "auto",
-			target,
-			format: "text",
-			api_key: instance.libreTranslateApiKey ?? "",
-		};
-
-		const url = new URL(instance.libreTranslateApiUrl);
-		if (url.pathname.endsWith("/")) {
-			url.pathname = url.pathname.slice(0, -1);
-		}
-		if (!url.pathname.endsWith("/translate")) {
-			url.pathname += "/translate";
-		}
-		const res = await fetch(url.toString(), {
-			method: "POST",
-			headers: {
-				"Content-Type": "application/json",
-			},
-			body: JSON.stringify(jsonBody),
-			agent: getAgentByUrl,
-		});
-
-		const json = (await res.json()) as {
-			detectedLanguage?: {
-				confidence: number;
-				language: string;
-			};
-			translatedText: string;
-		};
-
-		return {
-			sourceLang: source ?? json.detectedLanguage?.language,
-			text: convertChinese(
-				["zh-hant", "zh-TW"].includes(to),
-				json.translatedText,
-			),
-		};
-	}
-
-	const deeplTranslator = new deepl.Translator(instance.deeplAuthKey ?? "");
-	const result = await deeplTranslator.translateText(
-		text,
-		source as deepl.SourceLanguageCode | null,
-		// DeepL API requires us to specify "en-US" or "en-GB" for English
-		// translations ("en" does not work), so we need to address it
-		(target === "en" ? to : target) as deepl.TargetLanguageCode,
-	);
-
-	return {
-		sourceLang: source ?? result.detectedSourceLang,
-		text: convertChinese(["zh-hant", "zh-TW"].includes(to), result.text),
-	};
-}
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index e6c8f2517c..c04fb9750c 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -7,7 +7,7 @@ import type { Packed } from "@/misc/schema.js";
 import type { Promiseable } from "@/prelude/await-all.js";
 import { awaitAll } from "@/prelude/await-all.js";
 import { populateEmojis } from "@/misc/populate-emojis.js";
-import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "backend-rs";
+import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "@/const.js";
 import { Cache } from "@/misc/cache.js";
 import { db } from "@/db/postgre.js";
 import { isActor, getApId } from "@/remote/activitypub/type.js";
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index f82f8cda3b..b46e7acc1f 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -256,6 +256,25 @@ export function createExportFollowingJob(
 	);
 }
 
+export function createExportFollowersJob(
+	user: ThinUser,
+	excludeMuting = false,
+	excludeInactive = false,
+) {
+	return dbQueue.add(
+		"exportFollowers",
+		{
+			user: user,
+			excludeMuting,
+			excludeInactive,
+		},
+		{
+			removeOnComplete: true,
+			removeOnFail: true,
+		},
+	);
+}
+
 export function createExportMuteJob(user: ThinUser) {
 	return dbQueue.add(
 		"exportMute",
diff --git a/packages/backend/src/queue/processors/db/export-followers.ts b/packages/backend/src/queue/processors/db/export-followers.ts
new file mode 100644
index 0000000000..74b48c4199
--- /dev/null
+++ b/packages/backend/src/queue/processors/db/export-followers.ts
@@ -0,0 +1,115 @@
+import type Bull from "bull";
+import * as fs from "node:fs";
+
+import { queueLogger } from "../../logger.js";
+import { addFile } from "@/services/drive/add-file.js";
+import { format as dateFormat } from "date-fns";
+import { getFullApAccount } from "backend-rs";
+import { createTemp } from "@/misc/create-temp.js";
+import { Users, Followings, Mutings } from "@/models/index.js";
+import { In, MoreThan, Not } from "typeorm";
+import type { DbUserJobData } from "@/queue/types.js";
+import type { Following } from "@/models/entities/following.js";
+import { inspect } from "node:util";
+
+const logger = queueLogger.createSubLogger("export-followers");
+
+export async function exportFollowers(
+	job: Bull.Job<DbUserJobData>,
+	done: () => void,
+): Promise<void> {
+	logger.info(`Exporting followers of ${job.data.user.id} ...`);
+
+	const user = await Users.findOneBy({ id: job.data.user.id });
+	if (user == null) {
+		done();
+		return;
+	}
+
+	// Create temp file
+	const [path, cleanup] = await createTemp();
+
+	logger.info(`temp file created: ${path}`);
+
+	try {
+		const stream = fs.createWriteStream(path, { flags: "a" });
+
+		let cursor: Following["id"] | null = null;
+
+		const mutings = job.data.excludeMuting
+			? await Mutings.findBy({
+					muterId: user.id,
+				})
+			: [];
+
+		while (true) {
+			const followers = (await Followings.find({
+				where: {
+					followeeId: user.id,
+					...(mutings.length > 0
+						? { followerId: Not(In(mutings.map((x) => x.muteeId))) }
+						: {}),
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			})) as Following[];
+
+			if (followers.length === 0) {
+				break;
+			}
+
+			cursor = followers[followers.length - 1].id;
+
+			for (const follower of followers) {
+				const u = await Users.findOneBy({ id: follower.followerId });
+				if (u == null) {
+					continue;
+				}
+
+				if (
+					job.data.excludeInactive &&
+					u.updatedAt &&
+					Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90
+				) {
+					continue;
+				}
+
+				const content = getFullApAccount(u.username, u.host);
+				await new Promise<void>((res, rej) => {
+					stream.write(`${content}\n`, (err) => {
+						if (err) {
+							logger.warn(`failed to export followers of ${job.data.user.id}`);
+							logger.info(inspect(err));
+							rej(err);
+						} else {
+							res();
+						}
+					});
+				});
+			}
+		}
+
+		stream.end();
+		logger.info(`Exported to: ${path}`);
+
+		const fileName = `followers-${dateFormat(
+			new Date(),
+			"yyyy-MM-dd-HH-mm-ss",
+		)}.csv`;
+		const driveFile = await addFile({
+			user,
+			path,
+			name: fileName,
+			force: true,
+		});
+
+		logger.info(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
+	}
+
+	done();
+}
diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts
index d8cc9298f9..448b37d7e0 100644
--- a/packages/backend/src/queue/processors/db/index.ts
+++ b/packages/backend/src/queue/processors/db/index.ts
@@ -3,6 +3,7 @@ import type { DbJobData } from "@/queue/types.js";
 import { deleteDriveFiles } from "./delete-drive-files.js";
 import { exportCustomEmojis } from "./export-custom-emojis.js";
 import { exportNotes } from "./export-notes.js";
+import { exportFollowers } from "./export-followers.js";
 import { exportFollowing } from "./export-following.js";
 import { exportMute } from "./export-mute.js";
 import { exportBlocking } from "./export-blocking.js";
@@ -22,6 +23,7 @@ const jobs = {
 	deleteDriveFiles,
 	exportCustomEmojis,
 	exportNotes,
+	exportFollowers,
 	exportFollowing,
 	exportMute,
 	exportBlocking,
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 2309e3665c..80be763b13 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -202,7 +202,7 @@ export default async (
 		.finally(() => {
 			const after = performance.now();
 			const time = after - before;
-			if (time > 1000) {
+			if (time > 2000) {
 				apiLogger.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
 			}
 		});
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 587a68206e..174e4921b9 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -174,6 +174,7 @@ import * as ep___i_authorizedApps from "./endpoints/i/authorized-apps.js";
 import * as ep___i_changePassword from "./endpoints/i/change-password.js";
 import * as ep___i_deleteAccount from "./endpoints/i/delete-account.js";
 import * as ep___i_exportBlocking from "./endpoints/i/export-blocking.js";
+import * as ep___i_exportFollowers from "./endpoints/i/export-followers.js";
 import * as ep___i_exportFollowing from "./endpoints/i/export-following.js";
 import * as ep___i_exportMute from "./endpoints/i/export-mute.js";
 import * as ep___i_exportNotes from "./endpoints/i/export-notes.js";
@@ -523,6 +524,7 @@ const eps = [
 	["i/change-password", ep___i_changePassword],
 	["i/delete-account", ep___i_deleteAccount],
 	["i/export-blocking", ep___i_exportBlocking],
+	["i/export-followers", ep___i_exportFollowers],
 	["i/export-following", ep___i_exportFollowing],
 	["i/export-mute", ep___i_exportMute],
 	["i/export-notes", ep___i_exportNotes],
diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts
index 72baadb676..5159ce3b4a 100644
--- a/packages/backend/src/server/api/endpoints/ap/get.ts
+++ b/packages/backend/src/server/api/endpoints/ap/get.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import Resolver from "@/remote/activitypub/resolver.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["federation"],
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index a24654b2dd..bf7d1a1986 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -4,7 +4,8 @@ import { createNote } from "@/remote/activitypub/models/note.js";
 import DbResolver from "@/remote/activitypub/db-resolver.js";
 import Resolver from "@/remote/activitypub/resolver.js";
 import { ApiError } from "@/server/api/error.js";
-import { MINUTE, extractHost, isBlockedServer } from "backend-rs";
+import { MINUTE } from "@/const.js";
+import { extractHost, isBlockedServer } from "backend-rs";
 import { Users, Notes } from "@/models/index.js";
 import type { Note } from "@/models/entities/note.js";
 import type { CacheableLocalUser, User } from "@/models/entities/user.js";
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index a2c58aea50..944eb327be 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -3,7 +3,7 @@ import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Blockings, NoteWatchings, Users } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["account"],
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index bdd930f0df..5304bcc7bc 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -3,7 +3,7 @@ import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Blockings, Users } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["account"],
diff --git a/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts b/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts
index 2ab190ed0b..f6c5573af7 100644
--- a/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts
+++ b/packages/backend/src/server/api/endpoints/compatibility/custom-emojis.ts
@@ -1,7 +1,7 @@
 import { Emojis } from "@/models/index.js";
 import type { Emoji } from "@/models/entities/emoji.js";
 import { IsNull, In } from "typeorm";
-import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 import define from "@/server/api/define.js";
 
 export const meta = {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index e7d73513dd..f4ee2546f5 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -2,7 +2,8 @@ import { addFile } from "@/services/drive/add-file.js";
 import { DriveFiles } from "@/models/index.js";
 import { config } from "@/config.js";
 import { IdentifiableError } from "@/misc/identifiable-error.js";
-import { MINUTE, fetchMeta } from "backend-rs";
+import { fetchMeta } from "backend-rs";
+import { MINUTE } from "@/const.js";
 import define from "@/server/api/define.js";
 import { apiLogger } from "@/server/api/logger.js";
 import { ApiError } from "@/server/api/error.js";
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index fea6bf8a3a..cdfcb03089 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -2,7 +2,7 @@ import { uploadFromUrl } from "@/services/drive/upload-from-url.js";
 import define from "@/server/api/define.js";
 import { DriveFiles } from "@/models/index.js";
 import { publishMainStream } from "@/services/stream.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["drive"],
diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
index 8ff6afa320..ca013314e1 100644
--- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
+++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
@@ -1,6 +1,6 @@
 import { createExportCustomEmojisJob } from "@/queue/index.js";
 import define from "@/server/api/define.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 29d5d6c84c..e80c5a4350 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -4,7 +4,7 @@ import { ApiError } from "@/server/api/error.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Followings, Users } from "@/models/index.js";
 import { IdentifiableError } from "@/misc/identifiable-error.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["following", "users"],
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index 95fd254f48..e8019b1c32 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -3,7 +3,7 @@ import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Followings, Users } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["following", "users"],
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index 1baa8df64f..37478b2c93 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -3,7 +3,7 @@ import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
 import { getUser } from "@/server/api/common/getters.js";
 import { Followings, Users } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["following", "users"],
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index eda2d81e5c..ee237c7abb 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -1,6 +1,7 @@
 import define from "@/server/api/define.js";
 import { DriveFiles, GalleryPosts } from "@/models/index.js";
-import { HOUR, genIdAt } from "backend-rs";
+import { genIdAt } from "backend-rs";
+import { HOUR } from "@/const.js";
 import { GalleryPost } from "@/models/entities/gallery-post.js";
 import type { DriveFile } from "@/models/entities/drive-file.js";
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index a8a3d46890..16c629706c 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -1,7 +1,7 @@
 import define from "@/server/api/define.js";
 import { DriveFiles, GalleryPosts } from "@/models/index.js";
 import type { DriveFile } from "@/models/entities/drive-file.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["gallery"],
diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
index d6cfa14ece..633e75335a 100644
--- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts
+++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
@@ -1,5 +1,5 @@
 import { MoreThan } from "typeorm";
-import { USER_ONLINE_THRESHOLD } from "backend-rs";
+import { USER_ONLINE_THRESHOLD } from "@/const.js";
 import { Users } from "@/models/index.js";
 import define from "@/server/api/define.js";
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
index 68c875aa00..30e74ab2f5 100644
--- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import { createExportBlockingJob } from "@/queue/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/export-followers.ts b/packages/backend/src/server/api/endpoints/i/export-followers.ts
new file mode 100644
index 0000000000..2fe45455ff
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/export-followers.ts
@@ -0,0 +1,25 @@
+import define from "@/server/api/define.js";
+import { createExportFollowersJob } from "@/queue/index.js";
+import { HOUR } from "@/const.js";
+
+export const meta = {
+	secure: true,
+	requireCredential: true,
+	limit: {
+		duration: HOUR,
+		max: 1,
+	},
+} as const;
+
+export const paramDef = {
+	type: "object",
+	properties: {
+		excludeMuting: { type: "boolean", default: false },
+		excludeInactive: { type: "boolean", default: false },
+	},
+	required: [],
+} as const;
+
+export default define(meta, paramDef, async (ps, user) => {
+	createExportFollowersJob(user, ps.excludeMuting, ps.excludeInactive);
+});
diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts
index 5bce16f0ef..07d2997a18 100644
--- a/packages/backend/src/server/api/endpoints/i/export-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-following.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import { createExportFollowingJob } from "@/queue/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts
index 1b1fcf7241..7d22a073e6 100644
--- a/packages/backend/src/server/api/endpoints/i/export-mute.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import { createExportMuteJob } from "@/queue/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts
index 9984c7656b..f167bb83cb 100644
--- a/packages/backend/src/server/api/endpoints/i/export-notes.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import { createExportNotesJob } from "@/queue/index.js";
-import { DAY } from "backend-rs";
+import { DAY } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
index e3b30d2d98..b68d889dcd 100644
--- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
@@ -1,6 +1,6 @@
 import define from "@/server/api/define.js";
 import { createExportUserListsJob } from "@/queue/index.js";
-import { MINUTE } from "backend-rs";
+import { MINUTE } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index b7cb6e6518..58314aced3 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -2,7 +2,7 @@ import define from "@/server/api/define.js";
 import { createImportBlockingJob } from "@/queue/index.js";
 import { ApiError } from "@/server/api/error.js";
 import { DriveFiles } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index 8c20ff3591..b7c475698c 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -2,7 +2,7 @@ import define from "@/server/api/define.js";
 import { createImportFollowingJob } from "@/queue/index.js";
 import { ApiError } from "@/server/api/error.js";
 import { DriveFiles } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 5ff4c94a29..494fb0d420 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -2,7 +2,7 @@ import define from "@/server/api/define.js";
 import { createImportMutingJob } from "@/queue/index.js";
 import { ApiError } from "@/server/api/error.js";
 import { DriveFiles } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/import-posts.ts b/packages/backend/src/server/api/endpoints/i/import-posts.ts
index 01f61138f5..1d81f69c0c 100644
--- a/packages/backend/src/server/api/endpoints/i/import-posts.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-posts.ts
@@ -2,7 +2,8 @@ import define from "@/server/api/define.js";
 import { createImportPostsJob } from "@/queue/index.js";
 import { ApiError } from "@/server/api/error.js";
 import { DriveFiles } from "@/models/index.js";
-import { fetchMeta, DAY } from "backend-rs";
+import { fetchMeta } from "backend-rs";
+import { DAY } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 03aa14ac2b..ed82a96054 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -2,7 +2,7 @@ import define from "@/server/api/define.js";
 import { createImportUserListsJob } from "@/queue/index.js";
 import { ApiError } from "@/server/api/error.js";
 import { DriveFiles } from "@/models/index.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	secure: true,
diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
index 62b1cd96ad..63d21d93e3 100644
--- a/packages/backend/src/server/api/endpoints/i/known-as.ts
+++ b/packages/backend/src/server/api/endpoints/i/known-as.ts
@@ -4,7 +4,8 @@ import { resolveUser } from "@/remote/resolve-user.js";
 import acceptAllFollowRequests from "@/services/following/requests/accept-all.js";
 import { publishToFollowers } from "@/services/i/update.js";
 import { publishMainStream } from "@/services/stream.js";
-import { stringToAcct, DAY } from "backend-rs";
+import { stringToAcct } from "backend-rs";
+import { DAY } from "@/const.js";
 import { apiLogger } from "@/server/api/logger.js";
 import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index 90a0051759..e7808d7b0a 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -1,6 +1,7 @@
 import type { User } from "@/models/entities/user.js";
 import { resolveUser } from "@/remote/resolve-user.js";
-import { stringToAcct, DAY } from "backend-rs";
+import { stringToAcct } from "backend-rs";
+import { DAY } from "@/const.js";
 import DeliverManager from "@/remote/activitypub/deliver-manager.js";
 import { renderActivity } from "@/remote/activitypub/renderer/index.js";
 import define from "@/server/api/define.js";
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index 393a358545..d894eb07e9 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -6,7 +6,8 @@ import { Users, UserProfiles } from "@/models/index.js";
 import { sendEmail } from "@/services/send-email.js";
 import { ApiError } from "@/server/api/error.js";
 import { validateEmailForAccount } from "@/services/validate-email-for-account.js";
-import { HOUR, verifyPassword } from "backend-rs";
+import { verifyPassword } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	requireCredential: true,
diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
index 1b9734c85c..76691dd34d 100644
--- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts
@@ -2,7 +2,7 @@ import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
 import { MessagingMessages } from "@/models/index.js";
 import { deleteMessage } from "@/services/messages/delete.js";
-import { SECOND, HOUR } from "backend-rs";
+import { SECOND, HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["messaging"],
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 17f9a7ece9..0d19716806 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -15,7 +15,7 @@ import { config } from "@/config.js";
 import { noteVisibilities } from "@/types.js";
 import { ApiError } from "@/server/api/error.js";
 import define from "@/server/api/define.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { langmap } from "firefish-js";
 import { createScheduledNoteJob } from "@/queue/index.js";
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index b5a77af094..54aad1ebad 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -3,7 +3,7 @@ import { Users } from "@/models/index.js";
 import define from "@/server/api/define.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { ApiError } from "@/server/api/error.js";
-import { SECOND, HOUR } from "backend-rs";
+import { SECOND, HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["notes"],
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 4698d3d473..5481242cb3 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -18,7 +18,8 @@ import { config } from "@/config.js";
 import { noteVisibilities } from "@/types.js";
 import { ApiError } from "@/server/api/error.js";
 import define from "@/server/api/define.js";
-import { genId, HOUR } from "backend-rs";
+import { genId } from "backend-rs";
+import { HOUR } from "@/const.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { Poll } from "@/models/entities/poll.js";
 import * as mfm from "mfm-js";
diff --git a/packages/backend/src/server/api/endpoints/notes/make-private.ts b/packages/backend/src/server/api/endpoints/notes/make-private.ts
index 795863b003..5ddf1f3bf1 100644
--- a/packages/backend/src/server/api/endpoints/notes/make-private.ts
+++ b/packages/backend/src/server/api/endpoints/notes/make-private.ts
@@ -3,7 +3,7 @@ import { Notes } from "@/models/index.js";
 import define from "@/server/api/define.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { ApiError } from "@/server/api/error.js";
-import { SECOND, HOUR } from "backend-rs";
+import { SECOND, HOUR } from "@/const.js";
 import { publishNoteStream } from "@/services/stream.js";
 
 export const meta = {
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index b5f942f650..f135bd7ffa 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -2,7 +2,7 @@ import deleteReaction from "@/services/note/reaction/delete.js";
 import define from "@/server/api/define.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { ApiError } from "@/server/api/error.js";
-import { SECOND, HOUR } from "backend-rs";
+import { SECOND, HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["reactions", "notes"],
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index 8b84baa0b2..c939ab231a 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -1,7 +1,6 @@
 import { ApiError } from "@/server/api/error.js";
 import { getNote } from "@/server/api/common/getters.js";
-import { translate } from "@/misc/translate.js";
-import type { PostLanguage } from "firefish-js";
+import { translate } from "backend-rs";
 import define from "@/server/api/define.js";
 
 export const meta = {
@@ -47,7 +46,7 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	return translate(
 		note.text,
-		note.lang as PostLanguage | null,
-		ps.targetLang as PostLanguage,
+		note.lang as string | null,
+		ps.targetLang,
 	);
 });
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 1b8f544f7b..3f6f260eeb 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -3,7 +3,7 @@ import { Notes, Users } from "@/models/index.js";
 import define from "@/server/api/define.js";
 import { getNote } from "@/server/api/common/getters.js";
 import { ApiError } from "@/server/api/error.js";
-import { SECOND, HOUR } from "backend-rs";
+import { SECOND, HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["notes"],
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 4677352258..e7eb771f31 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -1,5 +1,6 @@
 import { Pages, DriveFiles } from "@/models/index.js";
-import { genIdAt, HOUR } from "backend-rs";
+import { genIdAt } from "backend-rs";
+import { HOUR } from "@/const.js";
 import { Page } from "@/models/entities/page.js";
 import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index 7a4c47a81d..9509ebfb81 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -2,7 +2,7 @@ import { Not } from "typeorm";
 import { Pages, DriveFiles } from "@/models/index.js";
 import define from "@/server/api/define.js";
 import { ApiError } from "@/server/api/error.js";
-import { HOUR } from "backend-rs";
+import { HOUR } from "@/const.js";
 
 export const meta = {
 	tags: ["pages"],
diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts
index ed269de15f..ae6048194b 100644
--- a/packages/backend/src/server/api/endpoints/request-reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts
@@ -3,7 +3,8 @@ import { IsNull } from "typeorm";
 import { config } from "@/config.js";
 import { Users, UserProfiles, PasswordResetRequests } from "@/models/index.js";
 import { sendEmail } from "@/services/send-email.js";
-import { HOUR, genIdAt } from "backend-rs";
+import { genIdAt } from "backend-rs";
+import { HOUR } from "@/const.js";
 import define from "@/server/api/define.js";
 
 export const meta = {
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index 1ff242b097..4ea0d618ed 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -5,7 +5,7 @@ import {
 	generateBlockedUserQuery,
 	generateBlockQueryForUsers,
 } from "@/server/api/common/generate-block-query.js";
-import { DAY } from "backend-rs";
+import { DAY } from "@/const.js";
 
 export const meta = {
 	tags: ["users"],
diff --git a/packages/backend/src/server/api/mastodon/helpers/misc.ts b/packages/backend/src/server/api/mastodon/helpers/misc.ts
index 96d6251252..5d43423815 100644
--- a/packages/backend/src/server/api/mastodon/helpers/misc.ts
+++ b/packages/backend/src/server/api/mastodon/helpers/misc.ts
@@ -1,5 +1,5 @@
 import { config } from "@/config.js";
-import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 import { countLocalUsers, fetchMeta } from "backend-rs";
 import {
 	AnnouncementReads,
diff --git a/packages/backend/src/server/api/mastodon/helpers/note.ts b/packages/backend/src/server/api/mastodon/helpers/note.ts
index 81df7c1924..15ae9b5d4a 100644
--- a/packages/backend/src/server/api/mastodon/helpers/note.ts
+++ b/packages/backend/src/server/api/mastodon/helpers/note.ts
@@ -40,7 +40,7 @@ import {
 	getStubMastoContext,
 	type MastoContext,
 } from "@/server/api/mastodon/index.js";
-import { translate } from "@/misc/translate.js";
+import { translate } from "backend-rs";
 import { createScheduledNoteJob } from "@/queue/index.js";
 
 export class NoteHelpers {
diff --git a/packages/backend/src/server/api/mastodon/helpers/user.ts b/packages/backend/src/server/api/mastodon/helpers/user.ts
index 5190bf6713..609b2224cf 100644
--- a/packages/backend/src/server/api/mastodon/helpers/user.ts
+++ b/packages/backend/src/server/api/mastodon/helpers/user.ts
@@ -306,6 +306,7 @@ export class UserHelpers {
 	}
 
 	public static async getUserFromAcct(acct: string): Promise<User> {
+		if (acct.startsWith("@")) acct = acct.slice(1);
 		const split = acct.toLowerCase().split("@");
 		if (split.length > 2) throw new Error("Invalid acct");
 		return split[1] == null
diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 4cfe6095cd..cef94d2659 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -15,7 +15,7 @@ import { convertToWebp } from "@/services/drive/image-processor.js";
 import { GenerateVideoThumbnail } from "@/services/drive/generate-video-thumbnail.js";
 import { StatusError } from "@/misc/fetch.js";
 import { ByteRangeReadable } from "./byte-range-readable.js";
-import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 import { inspect } from "node:util";
 
 const _filename = fileURLToPath(import.meta.url);
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index 23c33178ab..d8fa9d32a5 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -30,6 +30,7 @@ import { koaBody } from "koa-body";
 import removeTrailingSlash from "koa-remove-trailing-slashes";
 import { setupEndpointsAuthRoot } from "@/server/api/mastodon/endpoints/auth.js";
 import { CatchErrorsMiddleware } from "@/server/api/mastodon/middleware/catch-errors.js";
+import { inspect } from "node:util";
 
 export const serverLogger = new Logger("server", "gray", false);
 
diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts
index 4ede3d4bdb..bb43c21f83 100644
--- a/packages/backend/src/server/proxy/proxy-media.ts
+++ b/packages/backend/src/server/proxy/proxy-media.ts
@@ -9,7 +9,7 @@ import { createTemp } from "@/misc/create-temp.js";
 import { downloadUrl } from "@/misc/download-url.js";
 import { detectType } from "@/misc/get-file-info.js";
 import { StatusError } from "@/misc/fetch.js";
-import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 import { serverLogger } from "../index.js";
 import { isMimeImage } from "@/misc/is-mime-image.js";
 import { inspect } from "node:util";
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index af6e237be6..fe15a70a79 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -16,13 +16,12 @@ import { KoaAdapter } from "@bull-board/koa";
 
 import { In, IsNull } from "typeorm";
 import {
-	MINUTE,
-	DAY,
 	getNoteSummary,
 	stringToAcct,
 	fetchMeta,
 	metaToPugArgs,
 } from "backend-rs";
+import { MINUTE, DAY } from "@/const.js";
 import { config } from "@/config.js";
 import {
 	Users,
diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts
index 9f4d088ef8..c729a5e7a0 100644
--- a/packages/backend/src/services/drive/add-file.ts
+++ b/packages/backend/src/services/drive/add-file.ts
@@ -7,11 +7,11 @@ import sharp from "sharp";
 import { IsNull } from "typeorm";
 import { publishMainStream } from "@/services/stream.js";
 import {
-	FILE_TYPE_BROWSERSAFE,
 	fetchMeta,
 	genId,
 	publishToDriveFileStream,
 } from "backend-rs";
+import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
 import { contentDisposition } from "@/misc/content-disposition.js";
 import { getFileInfo } from "@/misc/get-file-info.js";
 import {
diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts
deleted file mode 100644
index 21d5a3b61b..0000000000
--- a/packages/backend/test/activitypub.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import rndstr from "rndstr";
-import { initDb } from "../src/db/postgre.js";
-import { initTestDb } from "./utils.js";
-
-describe("ActivityPub", () => {
-	before(async () => {
-		//await initTestDb();
-		await initDb();
-	});
-
-	describe("Parse minimum object", () => {
-		const host = "https://host1.test";
-		const preferredUsername = `${rndstr("A-Z", 4)}${rndstr("a-z", 4)}`;
-		const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
-
-		const actor = {
-			"@context": "https://www.w3.org/ns/activitystreams",
-			id: actorId,
-			type: "Person",
-			preferredUsername,
-			inbox: `${actorId}/inbox`,
-			outbox: `${actorId}/outbox`,
-		};
-
-		const post = {
-			"@context": "https://www.w3.org/ns/activitystreams",
-			id: `${host}/users/${rndstr("0-9a-z", 8)}`,
-			type: "Note",
-			attributedTo: actor.id,
-			to: "https://www.w3.org/ns/activitystreams#Public",
-			content: "あ",
-		};
-
-		it("Minimum Actor", async () => {
-			const { MockResolver } = await import("./misc/mock-resolver.js");
-			const { createPerson } = await import(
-				"../src/remote/activitypub/models/person.js"
-			);
-
-			const resolver = new MockResolver();
-			resolver._register(actor.id, actor);
-
-			const user = await createPerson(actor.id, resolver);
-
-			assert.deepStrictEqual(user.uri, actor.id);
-			assert.deepStrictEqual(user.username, actor.preferredUsername);
-			assert.deepStrictEqual(user.inbox, actor.inbox);
-		});
-
-		it("Minimum Note", async () => {
-			const { MockResolver } = await import("./misc/mock-resolver.js");
-			const { createNote } = await import(
-				"../src/remote/activitypub/models/note.js"
-			);
-
-			const resolver = new MockResolver();
-			resolver._register(actor.id, actor);
-			resolver._register(post.id, post);
-
-			const note = await createNote(post.id, resolver, true);
-
-			assert.deepStrictEqual(note?.uri, post.id);
-			assert.deepStrictEqual(note.visibility, "public");
-			assert.deepStrictEqual(note.text, post.content);
-		});
-	});
-
-	describe("Truncate long name", () => {
-		const host = "https://host1.test";
-		const preferredUsername = `${rndstr("A-Z", 4)}${rndstr("a-z", 4)}`;
-		const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
-
-		const name = rndstr("0-9a-z", 129);
-
-		const actor = {
-			"@context": "https://www.w3.org/ns/activitystreams",
-			id: actorId,
-			type: "Person",
-			preferredUsername,
-			name,
-			inbox: `${actorId}/inbox`,
-			outbox: `${actorId}/outbox`,
-		};
-
-		it("Actor", async () => {
-			const { MockResolver } = await import("./misc/mock-resolver.js");
-			const { createPerson } = await import(
-				"../src/remote/activitypub/models/person.js"
-			);
-
-			const resolver = new MockResolver();
-			resolver._register(actor.id, actor);
-
-			const user = await createPerson(actor.id, resolver);
-
-			assert.deepStrictEqual(user.name, actor.name.substr(0, 128));
-		});
-	});
-});
diff --git a/packages/backend/test/ap-request.ts b/packages/backend/test/ap-request.ts
deleted file mode 100644
index 0f1e0c58bb..0000000000
--- a/packages/backend/test/ap-request.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import * as assert from "node:assert";
-import httpSignature from "@peertube/http-signature";
-import { genRsaKeyPair } from "../src/misc/gen-key-pair.js";
-import {
-	createSignedGet,
-	createSignedPost,
-} from "../src/remote/activitypub/ap-request.js";
-
-export const buildParsedSignature = (
-	signingString: string,
-	signature: string,
-	algorithm: string,
-) => {
-	return {
-		scheme: "Signature",
-		params: {
-			keyId: "KeyID", // dummy, not used for verify
-			algorithm: algorithm,
-			headers: ["(request-target)", "date", "host", "digest"], // dummy, not used for verify
-			signature: signature,
-		},
-		signingString: signingString,
-		algorithm: algorithm.toUpperCase(),
-		keyId: "KeyID", // dummy, not used for verify
-	};
-};
-
-describe("ap-request", () => {
-	it("createSignedPost with verify", async () => {
-		const keypair = await genRsaKeyPair();
-		const key = { keyId: "x", privateKeyPem: keypair.privateKey };
-		const url = "https://example.com/inbox";
-		const activity = { a: 1 };
-		const body = JSON.stringify(activity);
-		const headers = {
-			"User-Agent": "UA",
-		};
-
-		const req = createSignedPost({
-			key,
-			url,
-			body,
-			additionalHeaders: headers,
-		});
-
-		const parsed = buildParsedSignature(
-			req.signingString,
-			req.signature,
-			"rsa-sha256",
-		);
-
-		const result = httpSignature.verifySignature(parsed, keypair.publicKey);
-		assert.deepStrictEqual(result, true);
-	});
-
-	it("createSignedGet with verify", async () => {
-		const keypair = await genRsaKeyPair();
-		const key = { keyId: "x", privateKeyPem: keypair.privateKey };
-		const url = "https://example.com/outbox";
-		const headers = {
-			"User-Agent": "UA",
-		};
-
-		const req = createSignedGet({ key, url, additionalHeaders: headers });
-
-		const parsed = buildParsedSignature(
-			req.signingString,
-			req.signature,
-			"rsa-sha256",
-		);
-
-		const result = httpSignature.verifySignature(parsed, keypair.publicKey);
-		assert.deepStrictEqual(result, true);
-	});
-});
diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts
deleted file mode 100644
index 67ef6e2894..0000000000
--- a/packages/backend/test/api-visibility.ts
+++ /dev/null
@@ -1,535 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	post,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-} from "./utils.js";
-
-describe("API visibility", () => {
-	let p: childProcess.ChildProcess;
-
-	before(async () => {
-		p = await startServer();
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	describe("Note visibility", async () => {
-		//#region vars
-		/** ヒロイン */
-		let alice: any;
-		/** フォロワー */
-		let follower: any;
-		/** 非フォロワー */
-		let other: any;
-		/** 非フォロワーでもリプライやメンションをされた人 */
-		let target: any;
-		/** specified mentionでmentionを飛ばされる人 */
-		let target2: any;
-
-		/** public-post */
-		let pub: any;
-		/** home-post */
-		let home: any;
-		/** followers-post */
-		let fol: any;
-		/** specified-post */
-		let spe: any;
-
-		/** public-reply to target's post */
-		let pubR: any;
-		/** home-reply to target's post */
-		let homeR: any;
-		/** followers-reply to target's post */
-		let folR: any;
-		/** specified-reply to target's post */
-		let speR: any;
-
-		/** public-mention to target */
-		let pubM: any;
-		/** home-mention to target */
-		let homeM: any;
-		/** followers-mention to target */
-		let folM: any;
-		/** specified-mention to target */
-		let speM: any;
-
-		/** reply target post */
-		let tgt: any;
-		//#endregion
-
-		const show = async (noteId: any, by: any) => {
-			return await request(
-				"/notes/show",
-				{
-					noteId,
-				},
-				by,
-			);
-		};
-
-		before(async () => {
-			//#region prepare
-			// signup
-			alice = await signup({ username: "alice" });
-			follower = await signup({ username: "follower" });
-			other = await signup({ username: "other" });
-			target = await signup({ username: "target" });
-			target2 = await signup({ username: "target2" });
-
-			// follow alice <= follower
-			await request("/following/create", { userId: alice.id }, follower);
-
-			// normal posts
-			pub = await post(alice, { text: "x", visibility: "public" });
-			home = await post(alice, { text: "x", visibility: "home" });
-			fol = await post(alice, { text: "x", visibility: "followers" });
-			spe = await post(alice, {
-				text: "x",
-				visibility: "specified",
-				visibleUserIds: [target.id],
-			});
-
-			// replies
-			tgt = await post(target, { text: "y", visibility: "public" });
-			pubR = await post(alice, {
-				text: "x",
-				replyId: tgt.id,
-				visibility: "public",
-			});
-			homeR = await post(alice, {
-				text: "x",
-				replyId: tgt.id,
-				visibility: "home",
-			});
-			folR = await post(alice, {
-				text: "x",
-				replyId: tgt.id,
-				visibility: "followers",
-			});
-			speR = await post(alice, {
-				text: "x",
-				replyId: tgt.id,
-				visibility: "specified",
-			});
-
-			// mentions
-			pubM = await post(alice, {
-				text: "@target x",
-				replyId: tgt.id,
-				visibility: "public",
-			});
-			homeM = await post(alice, {
-				text: "@target x",
-				replyId: tgt.id,
-				visibility: "home",
-			});
-			folM = await post(alice, {
-				text: "@target x",
-				replyId: tgt.id,
-				visibility: "followers",
-			});
-			speM = await post(alice, {
-				text: "@target2 x",
-				replyId: tgt.id,
-				visibility: "specified",
-			});
-			//#endregion
-		});
-
-		//#region show post
-		// public
-		it("[show] public-postを自分が見れる", async(async () => {
-			const res = await show(pub.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-postをフォロワーが見れる", async(async () => {
-			const res = await show(pub.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-postを非フォロワーが見れる", async(async () => {
-			const res = await show(pub.id, other);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-postを未認証が見れる", async(async () => {
-			const res = await show(pub.id, null);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		// home
-		it("[show] home-postを自分が見れる", async(async () => {
-			const res = await show(home.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-postをフォロワーが見れる", async(async () => {
-			const res = await show(home.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-postを非フォロワーが見れる", async(async () => {
-			const res = await show(home.id, other);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-postを未認証が見れる", async(async () => {
-			const res = await show(home.id, null);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		// followers
-		it("[show] followers-postを自分が見れる", async(async () => {
-			const res = await show(fol.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] followers-postをフォロワーが見れる", async(async () => {
-			const res = await show(fol.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] followers-postを非フォロワーが見れない", async(async () => {
-			const res = await show(fol.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] followers-postを未認証が見れない", async(async () => {
-			const res = await show(fol.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		// specified
-		it("[show] specified-postを自分が見れる", async(async () => {
-			const res = await show(spe.id, alice);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-postを指定ユーザーが見れる", async(async () => {
-			const res = await show(spe.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] specified-postをフォロワーが見れない", async(async () => {
-			const res = await show(spe.id, follower);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-postを非フォロワーが見れない", async(async () => {
-			const res = await show(spe.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-postを未認証が見れない", async(async () => {
-			const res = await show(spe.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-		//#endregion
-
-		//#region show reply
-		// public
-		it("[show] public-replyを自分が見れる", async(async () => {
-			const res = await show(pubR.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-replyをされた人が見れる", async(async () => {
-			const res = await show(pubR.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-replyをフォロワーが見れる", async(async () => {
-			const res = await show(pubR.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-replyを非フォロワーが見れる", async(async () => {
-			const res = await show(pubR.id, other);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] public-replyを未認証が見れる", async(async () => {
-			const res = await show(pubR.id, null);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		// home
-		it("[show] home-replyを自分が見れる", async(async () => {
-			const res = await show(homeR.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-replyをされた人が見れる", async(async () => {
-			const res = await show(homeR.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-replyをフォロワーが見れる", async(async () => {
-			const res = await show(homeR.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-replyを非フォロワーが見れる", async(async () => {
-			const res = await show(homeR.id, other);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] home-replyを未認証が見れる", async(async () => {
-			const res = await show(homeR.id, null);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		// followers
-		it("[show] followers-replyを自分が見れる", async(async () => {
-			const res = await show(folR.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] followers-replyを非フォロワーでもリプライされていれば見れる", async(async () => {
-			const res = await show(folR.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] followers-replyをフォロワーが見れる", async(async () => {
-			const res = await show(folR.id, follower);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] followers-replyを非フォロワーが見れない", async(async () => {
-			const res = await show(folR.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] followers-replyを未認証が見れない", async(async () => {
-			const res = await show(folR.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		// specified
-		it("[show] specified-replyを自分が見れる", async(async () => {
-			const res = await show(speR.id, alice);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] specified-replyを指定ユーザーが見れる", async(async () => {
-			const res = await show(speR.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] specified-replyをされた人が指定されてなくても見れる", async(async () => {
-			const res = await show(speR.id, target);
-			assert.strictEqual(res.body.text, "x");
-		}));
-
-		it("[show] specified-replyをフォロワーが見れない", async(async () => {
-			const res = await show(speR.id, follower);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-replyを非フォロワーが見れない", async(async () => {
-			const res = await show(speR.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-replyを未認証が見れない", async(async () => {
-			const res = await show(speR.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-		//#endregion
-
-		//#region show mention
-		// public
-		it("[show] public-mentionを自分が見れる", async(async () => {
-			const res = await show(pubM.id, alice);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] public-mentionをされた人が見れる", async(async () => {
-			const res = await show(pubM.id, target);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] public-mentionをフォロワーが見れる", async(async () => {
-			const res = await show(pubM.id, follower);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] public-mentionを非フォロワーが見れる", async(async () => {
-			const res = await show(pubM.id, other);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] public-mentionを未認証が見れる", async(async () => {
-			const res = await show(pubM.id, null);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		// home
-		it("[show] home-mentionを自分が見れる", async(async () => {
-			const res = await show(homeM.id, alice);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] home-mentionをされた人が見れる", async(async () => {
-			const res = await show(homeM.id, target);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] home-mentionをフォロワーが見れる", async(async () => {
-			const res = await show(homeM.id, follower);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] home-mentionを非フォロワーが見れる", async(async () => {
-			const res = await show(homeM.id, other);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] home-mentionを未認証が見れる", async(async () => {
-			const res = await show(homeM.id, null);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		// followers
-		it("[show] followers-mentionを自分が見れる", async(async () => {
-			const res = await show(folM.id, alice);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] followers-mentionをメンションされていれば非フォロワーでも見れる", async(async () => {
-			const res = await show(folM.id, target);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] followers-mentionをフォロワーが見れる", async(async () => {
-			const res = await show(folM.id, follower);
-			assert.strictEqual(res.body.text, "@target x");
-		}));
-
-		it("[show] followers-mentionを非フォロワーが見れない", async(async () => {
-			const res = await show(folM.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] followers-mentionを未認証が見れない", async(async () => {
-			const res = await show(folM.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		// specified
-		it("[show] specified-mentionを自分が見れる", async(async () => {
-			const res = await show(speM.id, alice);
-			assert.strictEqual(res.body.text, "@target2 x");
-		}));
-
-		it("[show] specified-mentionを指定ユーザーが見れる", async(async () => {
-			const res = await show(speM.id, target);
-			assert.strictEqual(res.body.text, "@target2 x");
-		}));
-
-		it("[show] specified-mentionをされた人が指定されてなかったら見れない", async(async () => {
-			const res = await show(speM.id, target2);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-mentionをフォロワーが見れない", async(async () => {
-			const res = await show(speM.id, follower);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-mentionを非フォロワーが見れない", async(async () => {
-			const res = await show(speM.id, other);
-			assert.strictEqual(res.status, 404);
-		}));
-
-		it("[show] specified-mentionを未認証が見れない", async(async () => {
-			const res = await show(speM.id, null);
-			assert.strictEqual(res.status, 404);
-		}));
-		//#endregion
-
-		//#region HTL
-		it("[HTL] public-post が 自分が見れる", async(async () => {
-			const res = await request("/notes/timeline", { limit: 100 }, alice);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == pub.id);
-			assert.strictEqual(notes[0].text, "x");
-		}));
-
-		it("[HTL] public-post が 非フォロワーから見れない", async(async () => {
-			const res = await request("/notes/timeline", { limit: 100 }, other);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == pub.id);
-			assert.strictEqual(notes.length, 0);
-		}));
-
-		it("[HTL] followers-post が フォロワーから見れる", async(async () => {
-			const res = await request("/notes/timeline", { limit: 100 }, follower);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == fol.id);
-			assert.strictEqual(notes[0].text, "x");
-		}));
-		//#endregion
-
-		//#region RTL
-		it("[replies] followers-reply が フォロワーから見れる", async(async () => {
-			const res = await request(
-				"/notes/replies",
-				{ noteId: tgt.id, limit: 100 },
-				follower,
-			);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == folR.id);
-			assert.strictEqual(notes[0].text, "x");
-		}));
-
-		it("[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない", async(async () => {
-			const res = await request(
-				"/notes/replies",
-				{ noteId: tgt.id, limit: 100 },
-				other,
-			);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == folR.id);
-			assert.strictEqual(notes.length, 0);
-		}));
-
-		it("[replies] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => {
-			const res = await request(
-				"/notes/replies",
-				{ noteId: tgt.id, limit: 100 },
-				target,
-			);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == folR.id);
-			assert.strictEqual(notes[0].text, "x");
-		}));
-		//#endregion
-
-		//#region MTL
-		it("[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => {
-			const res = await request("/notes/mentions", { limit: 100 }, target);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == folR.id);
-			assert.strictEqual(notes[0].text, "x");
-		}));
-
-		it("[mentions] followers-mention が 非フォロワー (メンション先である) から見れる", async(async () => {
-			const res = await request("/notes/mentions", { limit: 100 }, target);
-			assert.strictEqual(res.status, 200);
-			const notes = res.body.filter((n: any) => n.id == folM.id);
-			assert.strictEqual(notes[0].text, "@target x");
-		}));
-		//#endregion
-	});
-});
diff --git a/packages/backend/test/api.ts b/packages/backend/test/api.ts
deleted file mode 100644
index 86916b1b87..0000000000
--- a/packages/backend/test/api.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	post,
-	react,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-	uploadFile,
-} from "./utils.js";
-
-describe("API", () => {
-	let p: childProcess.ChildProcess;
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-		carol = await signup({ username: "carol" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	describe("General validation", () => {
-		it("wrong type", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				string: 42,
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("missing require param", async(async () => {
-			const res = await request("/test", {
-				string: "a",
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("invalid misskey:id (empty string)", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				id: "",
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("valid misskey:id", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				id: "8wvhjghbxu",
-			});
-			assert.strictEqual(res.status, 200);
-		}));
-
-		it("default value", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				string: "a",
-			});
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.body.default, "hello");
-		}));
-
-		it("can set null even if it has default value", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				nullableDefault: null,
-			});
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.body.nullableDefault, null);
-		}));
-
-		it("cannot set undefined if it has default value", async(async () => {
-			const res = await request("/test", {
-				required: true,
-				nullableDefault: undefined,
-			});
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.body.nullableDefault, "hello");
-		}));
-	});
-});
diff --git a/packages/backend/test/block.ts b/packages/backend/test/block.ts
deleted file mode 100644
index e7bc8a74df..0000000000
--- a/packages/backend/test/block.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	post,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-} from "./utils.js";
-
-describe("Block", () => {
-	let p: childProcess.ChildProcess;
-
-	// alice blocks bob
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-		carol = await signup({ username: "carol" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("Block作成", async(async () => {
-		const res = await request(
-			"/blocking/create",
-			{
-				userId: bob.id,
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-	}));
-
-	it("ブロックされているユーザーをフォローできない", async(async () => {
-		const res = await request("/following/create", { userId: alice.id }, bob);
-
-		assert.strictEqual(res.status, 400);
-		assert.strictEqual(
-			res.body.error.id,
-			"c4ab57cc-4e41-45e9-bfd9-584f61e35ce0",
-		);
-	}));
-
-	it("ブロックされているユーザーにリアクションできない", async(async () => {
-		const note = await post(alice, { text: "hello" });
-
-		const res = await request(
-			"/notes/reactions/create",
-			{ noteId: note.id, reaction: "👍" },
-			bob,
-		);
-
-		assert.strictEqual(res.status, 400);
-		assert.strictEqual(
-			res.body.error.id,
-			"20ef5475-9f38-4e4c-bd33-de6d979498ec",
-		);
-	}));
-
-	it("ブロックされているユーザーに返信できない", async(async () => {
-		const note = await post(alice, { text: "hello" });
-
-		const res = await request(
-			"/notes/create",
-			{ replyId: note.id, text: "yo" },
-			bob,
-		);
-
-		assert.strictEqual(res.status, 400);
-		assert.strictEqual(
-			res.body.error.id,
-			"b390d7e1-8a5e-46ed-b625-06271cafd3d3",
-		);
-	}));
-
-	it("ブロックされているユーザーのノートをRenoteできない", async(async () => {
-		const note = await post(alice, { text: "hello" });
-
-		const res = await request(
-			"/notes/create",
-			{ renoteId: note.id, text: "yo" },
-			bob,
-		);
-
-		assert.strictEqual(res.status, 400);
-		assert.strictEqual(
-			res.body.error.id,
-			"b390d7e1-8a5e-46ed-b625-06271cafd3d3",
-		);
-	}));
-
-	// TODO: ユーザーリストに入れられないテスト
-
-	// TODO: ユーザーリストから除外されるテスト
-
-	it("タイムライン(LTL)にブロックされているユーザーの投稿が含まれない", async(async () => {
-		const aliceNote = await post(alice);
-		const bobNote = await post(bob);
-		const carolNote = await post(carol);
-
-		const res = await request("/notes/local-timeline", {}, bob);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === aliceNote.id),
-			false,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === bobNote.id),
-			true,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === carolNote.id),
-			true,
-		);
-	}));
-});
diff --git a/packages/backend/test/docker-compose.yml b/packages/backend/test/docker-compose.yml
deleted file mode 100644
index 5f95bec4c0..0000000000
--- a/packages/backend/test/docker-compose.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-version: "3"
-
-services:
-  redistest:
-    image: redis:6
-    ports:
-      - "127.0.0.1:56312:6379"
-
-  dbtest:
-    image: postgres:13
-    ports:
-      - "127.0.0.1:54312:5432"
-    environment:
-      POSTGRES_DB: "test-misskey"
-      POSTGRES_HOST_AUTH_METHOD: trust
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
deleted file mode 100644
index 016b9e10d8..0000000000
--- a/packages/backend/test/e2e/users.ts
+++ /dev/null
@@ -1,1505 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import { inspect } from "node:util";
-import {
-	signup,
-	post,
-	page,
-	role,
-	startServer,
-	api,
-	successfulApiCall,
-	uploadFile,
-} from "../utils.js";
-import type { entities } from "firefish-js";
-import type { INestApplicationContext } from "@nestjs/common";
-
-describe("ユーザー", () => {
-	// エンティティとしてのユーザーを主眼においたテストを記述する
-	// (Userを返すエンドポイントとUserエンティティを書き換えるエンドポイントをテストする)
-
-	const stripUndefined = <T extends { [key: string]: any }>(
-		orig: T,
-	): Partial<T> => {
-		return Object.entries({ ...orig })
-			.filter(([, value]) => value !== undefined)
-			.reduce((obj: Partial<T>, [key, value]) => {
-				obj[key as keyof T] = value;
-				return obj;
-			}, {});
-	};
-
-	// BUG misskey-jsとjson-schemaと実際に返ってくるデータが全部違う
-	type UserLite = entities.UserLite & {
-		badgeRoles: any[];
-	};
-
-	type UserDetailedNotMe = UserLite &
-		entities.UserDetailed & {
-			roles: any[];
-		};
-
-	type MeDetailed = UserDetailedNotMe & entities.MeDetailed;
-
-	type User = MeDetailed & { token: string };
-
-	const show = async (
-		id: string,
-		me = root,
-	): Promise<MeDetailed | UserDetailedNotMe> => {
-		return successfulApiCall({
-			endpoint: "users/show",
-			parameters: { userId: id },
-			user: me,
-		}) as any;
-	};
-
-	// UserLiteのキーが過不足なく入っている?
-	const userLite = (user: User): Partial<UserLite> => {
-		return stripUndefined({
-			id: user.id,
-			name: user.name,
-			username: user.username,
-			host: user.host,
-			avatarUrl: user.avatarUrl,
-			avatarBlurhash: user.avatarBlurhash,
-			isBot: user.isBot,
-			isCat: user.isCat,
-			speakAsCat: user.speakAsCat,
-			instance: user.instance,
-			emojis: user.emojis,
-			onlineStatus: user.onlineStatus,
-			badgeRoles: user.badgeRoles,
-
-			// BUG isAdmin/isModeratorはUserLiteではなくMeDetailedOnlyに含まれる。
-			isAdmin: undefined,
-			isModerator: undefined,
-		});
-	};
-
-	// UserDetailedNotMeのキーが過不足なく入っている?
-	const userDetailedNotMe = (user: User): Partial<UserDetailedNotMe> => {
-		return stripUndefined({
-			...userLite(user),
-			url: user.url,
-			uri: user.uri,
-			movedTo: user.movedTo,
-			alsoKnownAs: user.alsoKnownAs,
-			createdAt: user.createdAt,
-			updatedAt: user.updatedAt,
-			lastFetchedAt: user.lastFetchedAt,
-			bannerUrl: user.bannerUrl,
-			bannerBlurhash: user.bannerBlurhash,
-			isLocked: user.isLocked,
-			isSilenced: user.isSilenced,
-			isSuspended: user.isSuspended,
-			description: user.description,
-			location: user.location,
-			birthday: user.birthday,
-			lang: user.lang,
-			fields: user.fields,
-			followersCount: user.followersCount,
-			followingCount: user.followingCount,
-			notesCount: user.notesCount,
-			pinnedNoteIds: user.pinnedNoteIds,
-			pinnedNotes: user.pinnedNotes,
-			pinnedPageId: user.pinnedPageId,
-			pinnedPage: user.pinnedPage,
-			publicReactions: user.publicReactions,
-			ffVisibility: user.ffVisibility,
-			twoFactorEnabled: user.twoFactorEnabled,
-			usePasswordLessLogin: user.usePasswordLessLogin,
-			securityKeys: user.securityKeys,
-			roles: user.roles,
-			memo: user.memo,
-		});
-	};
-
-	// Relations関連のキーが過不足なく入っている?
-	const userDetailedNotMeWithRelations = (
-		user: User,
-	): Partial<UserDetailedNotMe> => {
-		return stripUndefined({
-			...userDetailedNotMe(user),
-			isFollowing: user.isFollowing ?? false,
-			isFollowed: user.isFollowed ?? false,
-			hasPendingFollowRequestFromYou:
-				user.hasPendingFollowRequestFromYou ?? false,
-			hasPendingFollowRequestToYou: user.hasPendingFollowRequestToYou ?? false,
-			isBlocking: user.isBlocking ?? false,
-			isBlocked: user.isBlocked ?? false,
-			isMuted: user.isMuted ?? false,
-			isRenoteMuted: user.isRenoteMuted ?? false,
-		});
-	};
-
-	// MeDetailedのキーが過不足なく入っている?
-	const meDetailed = (user: User, security = false): Partial<MeDetailed> => {
-		return stripUndefined({
-			...userDetailedNotMe(user),
-			avatarId: user.avatarId,
-			bannerId: user.bannerId,
-			isModerator: user.isModerator,
-			isAdmin: user.isAdmin,
-			injectFeaturedNote: user.injectFeaturedNote,
-			receiveAnnouncementEmail: user.receiveAnnouncementEmail,
-			alwaysMarkNsfw: user.alwaysMarkNsfw,
-			autoSensitive: user.autoSensitive,
-			carefulBot: user.carefulBot,
-			autoAcceptFollowed: user.autoAcceptFollowed,
-			noCrawle: user.noCrawle,
-			isIndexable: user.isIndexable,
-			preventAiLearning: user.preventAiLearning,
-			isExplorable: user.isExplorable,
-			isDeleted: user.isDeleted,
-			hideOnlineStatus: user.hideOnlineStatus,
-			hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes,
-			hasUnreadMentions: user.hasUnreadMentions,
-			hasUnreadAnnouncement: user.hasUnreadAnnouncement,
-			hasUnreadAntenna: user.hasUnreadAntenna,
-			hasUnreadChannel: user.hasUnreadChannel,
-			hasUnreadNotification: user.hasUnreadNotification,
-			hasPendingReceivedFollowRequest: user.hasPendingReceivedFollowRequest,
-			mutedWords: user.mutedWords,
-			mutedInstances: user.mutedInstances,
-			mutingNotificationTypes: user.mutingNotificationTypes,
-			emailNotificationTypes: user.emailNotificationTypes,
-			policies: user.policies,
-			...(security
-				? {
-						email: user.email,
-						emailVerified: user.emailVerified,
-						securityKeysList: user.securityKeysList,
-					}
-				: {}),
-		});
-	};
-
-	let app: INestApplicationContext;
-
-	let root: User;
-	let alice: User;
-	let aliceNote: entities.Note;
-	let alicePage: entities.Page;
-	let aliceList: entities.UserList;
-
-	let bob: User;
-	let bobNote: entities.Note;
-
-	let carol: User;
-	let dave: User;
-	let ellen: User;
-	let frank: User;
-
-	let usersReplying: User[];
-
-	let userNoNote: User;
-	let userNotExplorable: User;
-	let userLocking: User;
-	let userAdmin: User;
-	let roleAdmin: any;
-	let userModerator: User;
-	let roleModerator: any;
-	let userRolePublic: User;
-	let rolePublic: any;
-	let userRoleBadge: User;
-	let roleBadge: any;
-	let userSilenced: User;
-	let roleSilenced: any;
-	let userSuspended: User;
-	let userDeletedBySelf: User;
-	let userDeletedByAdmin: User;
-	let userFollowingAlice: User;
-	let userFollowedByAlice: User;
-	let userBlockingAlice: User;
-	let userBlockedByAlice: User;
-	let userMutingAlice: User;
-	let userMutedByAlice: User;
-	let userRnMutingAlice: User;
-	let userRnMutedByAlice: User;
-	let userFollowRequesting: User;
-	let userFollowRequested: User;
-
-	beforeAll(
-		async () => {
-			app = await startServer();
-		},
-		1000 * 60 * 2,
-	);
-
-	beforeAll(
-		async () => {
-			root = await signup({ username: "root" });
-			alice = await signup({ username: "alice" });
-			aliceNote = (await post(alice, { text: "test" })) as any;
-			alicePage = await page(alice);
-			aliceList = (await api("users/list/create", { name: "aliceList" }, alice))
-				.body;
-			bob = await signup({ username: "bob" });
-			bobNote = (await post(bob, { text: "test" })) as any;
-			carol = await signup({ username: "carol" });
-			dave = await signup({ username: "dave" });
-			ellen = await signup({ username: "ellen" });
-			frank = await signup({ username: "frank" });
-
-			// @alice -> @replyingへのリプライ。Promise.allで一気に作るとtimeoutしてしまうのでreduceで一つ一つawaitする
-			usersReplying = await [...Array(10)]
-				.map((_, i) => i)
-				.reduce(
-					async (acc, i) => {
-						const u = await signup({ username: `replying${i}` });
-						for (let j = 0; j < 10 - i; j++) {
-							const p = await post(u, { text: `test${j}` });
-							await post(alice, {
-								text: `@${u.username} test${j}`,
-								replyId: p.id,
-							});
-						}
-
-						return (await acc).concat(u);
-					},
-					Promise.resolve([] as User[]),
-				);
-
-			userNoNote = await signup({ username: "userNoNote" });
-			userNotExplorable = await signup({ username: "userNotExplorable" });
-			await post(userNotExplorable, { text: "test" });
-			await api("i/update", { isExplorable: false }, userNotExplorable);
-			userLocking = await signup({ username: "userLocking" });
-			await post(userLocking, { text: "test" });
-			await api("i/update", { isLocked: true }, userLocking);
-			userAdmin = await signup({ username: "userAdmin" });
-			roleAdmin = await role(root, {
-				isAdministrator: true,
-				name: "Admin Role",
-			});
-			await api(
-				"admin/roles/assign",
-				{ userId: userAdmin.id, roleId: roleAdmin.id },
-				root,
-			);
-			userModerator = await signup({ username: "userModerator" });
-			roleModerator = await role(root, {
-				isModerator: true,
-				name: "Moderator Role",
-			});
-			await api(
-				"admin/roles/assign",
-				{ userId: userModerator.id, roleId: roleModerator.id },
-				root,
-			);
-			userRolePublic = await signup({ username: "userRolePublic" });
-			rolePublic = await role(root, { isPublic: true, name: "Public Role" });
-			await api(
-				"admin/roles/assign",
-				{ userId: userRolePublic.id, roleId: rolePublic.id },
-				root,
-			);
-			userRoleBadge = await signup({ username: "userRoleBadge" });
-			roleBadge = await role(root, { asBadge: true, name: "Badge Role" });
-			await api(
-				"admin/roles/assign",
-				{ userId: userRoleBadge.id, roleId: roleBadge.id },
-				root,
-			);
-			userSilenced = await signup({ username: "userSilenced" });
-			await post(userSilenced, { text: "test" });
-			roleSilenced = await role(
-				root,
-				{},
-				{ canPublicNote: { priority: 0, useDefault: false, value: false } },
-			);
-			await api(
-				"admin/roles/assign",
-				{ userId: userSilenced.id, roleId: roleSilenced.id },
-				root,
-			);
-			userSuspended = await signup({ username: "userSuspended" });
-			await post(userSuspended, { text: "test" });
-			await successfulApiCall({
-				endpoint: "i/update",
-				parameters: { description: "#user_testuserSuspended" },
-				user: userSuspended,
-			});
-			await api("admin/suspend-user", { userId: userSuspended.id }, root);
-			userDeletedBySelf = await signup({
-				username: "userDeletedBySelf",
-				password: "userDeletedBySelf",
-			});
-			await post(userDeletedBySelf, { text: "test" });
-			await api(
-				"i/delete-account",
-				{ password: "userDeletedBySelf" },
-				userDeletedBySelf,
-			);
-			userDeletedByAdmin = await signup({ username: "userDeletedByAdmin" });
-			await post(userDeletedByAdmin, { text: "test" });
-			await api(
-				"admin/delete-account",
-				{ userId: userDeletedByAdmin.id },
-				root,
-			);
-			userFollowingAlice = await signup({ username: "userFollowingAlice" });
-			await post(userFollowingAlice, { text: "test" });
-			await api("following/create", { userId: alice.id }, userFollowingAlice);
-			userFollowedByAlice = await signup({ username: "userFollowedByAlice" });
-			await post(userFollowedByAlice, { text: "test" });
-			await api("following/create", { userId: userFollowedByAlice.id }, alice);
-			userBlockingAlice = await signup({ username: "userBlockingAlice" });
-			await post(userBlockingAlice, { text: "test" });
-			await api("blocking/create", { userId: alice.id }, userBlockingAlice);
-			userBlockedByAlice = await signup({ username: "userBlockedByAlice" });
-			await post(userBlockedByAlice, { text: "test" });
-			await api("blocking/create", { userId: userBlockedByAlice.id }, alice);
-			userMutingAlice = await signup({ username: "userMutingAlice" });
-			await post(userMutingAlice, { text: "test" });
-			await api("mute/create", { userId: alice.id }, userMutingAlice);
-			userMutedByAlice = await signup({ username: "userMutedByAlice" });
-			await post(userMutedByAlice, { text: "test" });
-			await api("mute/create", { userId: userMutedByAlice.id }, alice);
-			userRnMutingAlice = await signup({ username: "userRnMutingAlice" });
-			await post(userRnMutingAlice, { text: "test" });
-			await api("renote-mute/create", { userId: alice.id }, userRnMutingAlice);
-			userRnMutedByAlice = await signup({ username: "userRnMutedByAlice" });
-			await post(userRnMutedByAlice, { text: "test" });
-			await api("renote-mute/create", { userId: userRnMutedByAlice.id }, alice);
-			userFollowRequesting = await signup({ username: "userFollowRequesting" });
-			await post(userFollowRequesting, { text: "test" });
-			userFollowRequested = userLocking;
-			await api(
-				"following/create",
-				{ userId: userFollowRequested.id },
-				userFollowRequesting,
-			);
-		},
-		1000 * 60 * 10,
-	);
-
-	afterAll(async () => {
-		await app.close();
-	});
-
-	beforeEach(async () => {
-		alice = {
-			...alice,
-			...((await successfulApiCall({
-				endpoint: "i",
-				parameters: {},
-				user: alice,
-			})) as any),
-		};
-		aliceNote = await successfulApiCall({
-			endpoint: "notes/show",
-			parameters: { noteId: aliceNote.id },
-			user: alice,
-		});
-	});
-
-	//#region サインアップ(signup)
-
-	test("が作れる。(作りたての状態で自分のユーザー情報が取れる)", async () => {
-		// SignupApiService.ts
-		const response = (await successfulApiCall({
-			endpoint: "signup",
-			parameters: { username: "zoe", password: "password" },
-			user: undefined,
-		})) as unknown as User; // BUG MeDetailedに足りないキーがある
-
-		// signupの時はtokenが含まれる特別なMeDetailedが返ってくる
-		assert.match(response.token, /[a-zA-Z0-9]{16}/);
-
-		// UserLite
-		assert.match(response.id, /[0-9a-z]{10}/);
-		assert.strictEqual(response.name, null);
-		assert.strictEqual(response.username, "zoe");
-		assert.strictEqual(response.host, null);
-		assert.match(response.avatarUrl, /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/);
-		assert.strictEqual(response.avatarBlurhash, null);
-		assert.strictEqual(response.isBot, false);
-		assert.strictEqual(response.isCat, false);
-		assert.strictEqual(response.speakAsCat, false);
-		assert.strictEqual(response.instance, undefined);
-		assert.deepStrictEqual(response.emojis, {});
-		assert.strictEqual(response.onlineStatus, "unknown");
-		assert.deepStrictEqual(response.badgeRoles, []);
-		// UserDetailedNotMeOnly
-		assert.strictEqual(response.url, null);
-		assert.strictEqual(response.uri, null);
-		assert.strictEqual(response.movedTo, null);
-		assert.strictEqual(response.alsoKnownAs, null);
-		assert.strictEqual(
-			response.createdAt,
-			new Date(response.createdAt).toISOString(),
-		);
-		assert.strictEqual(response.updatedAt, null);
-		assert.strictEqual(response.lastFetchedAt, null);
-		assert.strictEqual(response.bannerUrl, null);
-		assert.strictEqual(response.bannerBlurhash, null);
-		assert.strictEqual(response.isLocked, false);
-		assert.strictEqual(response.isSilenced, false);
-		assert.strictEqual(response.isSuspended, false);
-		assert.strictEqual(response.description, null);
-		assert.strictEqual(response.location, null);
-		assert.strictEqual(response.birthday, null);
-		assert.strictEqual(response.lang, null);
-		assert.deepStrictEqual(response.fields, []);
-		assert.strictEqual(response.followersCount, 0);
-		assert.strictEqual(response.followingCount, 0);
-		assert.strictEqual(response.notesCount, 0);
-		assert.deepStrictEqual(response.pinnedNoteIds, []);
-		assert.deepStrictEqual(response.pinnedNotes, []);
-		assert.strictEqual(response.pinnedPageId, null);
-		assert.strictEqual(response.pinnedPage, null);
-		assert.strictEqual(response.publicReactions, false);
-		assert.strictEqual(response.ffVisibility, "public");
-		assert.strictEqual(response.twoFactorEnabled, false);
-		assert.strictEqual(response.usePasswordLessLogin, false);
-		assert.strictEqual(response.securityKeys, false);
-		assert.deepStrictEqual(response.roles, []);
-		assert.strictEqual(response.memo, null);
-
-		// MeDetailedOnly
-		assert.strictEqual(response.avatarId, null);
-		assert.strictEqual(response.bannerId, null);
-		assert.strictEqual(response.isModerator, false);
-		assert.strictEqual(response.isAdmin, false);
-		assert.strictEqual(response.injectFeaturedNote, true);
-		assert.strictEqual(response.receiveAnnouncementEmail, true);
-		assert.strictEqual(response.alwaysMarkNsfw, false);
-		assert.strictEqual(response.autoSensitive, false);
-		assert.strictEqual(response.carefulBot, false);
-		assert.strictEqual(response.autoAcceptFollowed, true);
-		assert.strictEqual(response.noCrawle, false);
-		assert.strictEqual(response.preventAiLearning, true);
-		assert.strictEqual(response.isExplorable, true);
-		assert.strictEqual(response.isDeleted, false);
-		assert.strictEqual(response.hideOnlineStatus, false);
-		assert.strictEqual(response.hasUnreadSpecifiedNotes, false);
-		assert.strictEqual(response.hasUnreadMentions, false);
-		assert.strictEqual(response.hasUnreadAnnouncement, false);
-		assert.strictEqual(response.hasUnreadAntenna, false);
-		assert.strictEqual(response.hasUnreadChannel, false);
-		assert.strictEqual(response.hasUnreadNotification, false);
-		assert.strictEqual(response.hasPendingReceivedFollowRequest, false);
-		assert.deepStrictEqual(response.mutedWords, []);
-		assert.deepStrictEqual(response.mutedInstances, []);
-		assert.deepStrictEqual(response.mutingNotificationTypes, []);
-		assert.deepStrictEqual(response.emailNotificationTypes, [
-			"follow",
-			"receiveFollowRequest",
-		]);
-	});
-
-	//#endregion
-	//#region 自分の情報(i)
-
-	test("を読み取ることができること(自分)、キーが過不足なく入っていること。", async () => {
-		const response = await successfulApiCall({
-			endpoint: "i",
-			parameters: {},
-			user: userNoNote,
-		});
-		const expected = meDetailed(userNoNote, true);
-		expected.loggedInDays = 1; // iはloggedInDaysを更新する
-		assert.deepStrictEqual(response, expected);
-	});
-
-	//#endregion
-	//#region 自分の情報の更新(i/update)
-
-	test.each([
-		{ parameters: (): object => ({ name: null }) },
-		{ parameters: (): object => ({ name: "x".repeat(50) }) },
-		{ parameters: (): object => ({ name: "x" }) },
-		{ parameters: (): object => ({ name: "My name" }) },
-		{ parameters: (): object => ({ description: null }) },
-		{ parameters: (): object => ({ description: "x".repeat(1500) }) },
-		{ parameters: (): object => ({ description: "x" }) },
-		{ parameters: (): object => ({ description: "My description" }) },
-		{ parameters: (): object => ({ location: null }) },
-		{ parameters: (): object => ({ location: "x".repeat(50) }) },
-		{ parameters: (): object => ({ location: "x" }) },
-		{ parameters: (): object => ({ location: "My location" }) },
-		{ parameters: (): object => ({ birthday: "0000-00-00" }) },
-		{ parameters: (): object => ({ birthday: "9999-99-99" }) },
-		{ parameters: (): object => ({ lang: "en-US" }) },
-		{ parameters: (): object => ({ fields: [] }) },
-		{ parameters: (): object => ({ fields: [{ name: "x", value: "x" }] }) },
-		{
-			parameters: (): object => ({
-				fields: [{ name: "x".repeat(3000), value: "x".repeat(3000) }],
-			}),
-		}, // BUG? fieldには制限がない
-		{
-			parameters: (): object => ({
-				fields: Array(16).fill({ name: "x", value: "y" }),
-			}),
-		},
-		{ parameters: (): object => ({ isLocked: true }) },
-		{ parameters: (): object => ({ isLocked: false }) },
-		{ parameters: (): object => ({ isExplorable: false }) },
-		{ parameters: (): object => ({ isExplorable: true }) },
-		{ parameters: (): object => ({ hideOnlineStatus: true }) },
-		{ parameters: (): object => ({ hideOnlineStatus: false }) },
-		{ parameters: (): object => ({ publicReactions: false }) },
-		{ parameters: (): object => ({ publicReactions: true }) },
-		{ parameters: (): object => ({ autoAcceptFollowed: true }) },
-		{ parameters: (): object => ({ autoAcceptFollowed: false }) },
-		{ parameters: (): object => ({ noCrawle: true }) },
-		{ parameters: (): object => ({ noCrawle: false }) },
-		{ parameters: (): object => ({ isIndexable: true }) },
-		{ parameters: (): object => ({ isIndexable: false }) },
-		{ parameters: (): object => ({ preventAiLearning: false }) },
-		{ parameters: (): object => ({ preventAiLearning: true }) },
-		{ parameters: (): object => ({ isBot: true }) },
-		{ parameters: (): object => ({ isBot: false }) },
-		{ parameters: (): object => ({ isCat: true }) },
-		{ parameters: (): object => ({ isCat: false }) },
-		{ parameters: (): object => ({ speakAsCat: true }) },
-		{ parameters: (): object => ({ speakAsCat: false }) },
-		{ parameters: (): object => ({ injectFeaturedNote: true }) },
-		{ parameters: (): object => ({ injectFeaturedNote: false }) },
-		{ parameters: (): object => ({ receiveAnnouncementEmail: true }) },
-		{ parameters: (): object => ({ receiveAnnouncementEmail: false }) },
-		{ parameters: (): object => ({ alwaysMarkNsfw: true }) },
-		{ parameters: (): object => ({ alwaysMarkNsfw: false }) },
-		{ parameters: (): object => ({ autoSensitive: true }) },
-		{ parameters: (): object => ({ autoSensitive: false }) },
-		{ parameters: (): object => ({ ffVisibility: "private" }) },
-		{ parameters: (): object => ({ ffVisibility: "followers" }) },
-		{ parameters: (): object => ({ ffVisibility: "public" }) },
-		{ parameters: (): object => ({ mutedWords: Array(19).fill(["xxxxx"]) }) },
-		{ parameters: (): object => ({ mutedWords: [["x".repeat(194)]] }) },
-		{ parameters: (): object => ({ mutedWords: [] }) },
-		{ parameters: (): object => ({ mutedInstances: ["xxxx.xxxxx"] }) },
-		{ parameters: (): object => ({ mutedInstances: [] }) },
-		{
-			parameters: (): object => ({
-				mutingNotificationTypes: [
-					"follow",
-					"mention",
-					"reply",
-					"renote",
-					"quote",
-					"reaction",
-					"pollEnded",
-					"receiveFollowRequest",
-					"followRequestAccepted",
-					"achievementEarned",
-					"app",
-				],
-			}),
-		},
-		{ parameters: (): object => ({ mutingNotificationTypes: [] }) },
-		{
-			parameters: (): object => ({
-				emailNotificationTypes: [
-					"mention",
-					"reply",
-					"quote",
-					"follow",
-					"receiveFollowRequest",
-				],
-			}),
-		},
-		{ parameters: (): object => ({ emailNotificationTypes: [] }) },
-	] as const)("を書き換えることができる($#)", async ({ parameters }) => {
-		const response = await successfulApiCall({
-			endpoint: "i/update",
-			parameters: parameters(),
-			user: alice,
-		});
-		const expected = { ...meDetailed(alice, true), ...parameters() };
-		assert.deepStrictEqual(response, expected, inspect(parameters()));
-	});
-
-	test("を書き換えることができる(Avatar)", async () => {
-		const aliceFile = (await uploadFile(alice)).body;
-		const parameters = { avatarId: aliceFile.id };
-		const response = await successfulApiCall({
-			endpoint: "i/update",
-			parameters: parameters,
-			user: alice,
-		});
-		assert.match(response.avatarUrl ?? ".", /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/);
-		assert.match(response.avatarBlurhash ?? ".", /[ -~]{54}/);
-		const expected = {
-			...meDetailed(alice, true),
-			avatarId: aliceFile.id,
-			avatarBlurhash: response.avatarBlurhash,
-			avatarUrl: response.avatarUrl,
-		};
-		assert.deepStrictEqual(response, expected, inspect(parameters));
-
-		const parameters2 = { avatarId: null };
-		const response2 = await successfulApiCall({
-			endpoint: "i/update",
-			parameters: parameters2,
-			user: alice,
-		});
-		const expected2 = {
-			...meDetailed(alice, true),
-			avatarId: null,
-			avatarBlurhash: null,
-			avatarUrl: alice.avatarUrl, // 解除した場合、identiconになる
-		};
-		assert.deepStrictEqual(response2, expected2, inspect(parameters));
-	});
-
-	test("を書き換えることができる(Banner)", async () => {
-		const aliceFile = (await uploadFile(alice)).body;
-		const parameters = { bannerId: aliceFile.id };
-		const response = await successfulApiCall({
-			endpoint: "i/update",
-			parameters: parameters,
-			user: alice,
-		});
-		assert.match(response.bannerUrl ?? ".", /^[-a-zA-Z0-9@:%._\+~#&?=\/]+$/);
-		assert.match(response.bannerBlurhash ?? ".", /[ -~]{54}/);
-		const expected = {
-			...meDetailed(alice, true),
-			bannerId: aliceFile.id,
-			bannerBlurhash: response.bannerBlurhash,
-			bannerUrl: response.bannerUrl,
-		};
-		assert.deepStrictEqual(response, expected, inspect(parameters));
-
-		const parameters2 = { bannerId: null };
-		const response2 = await successfulApiCall({
-			endpoint: "i/update",
-			parameters: parameters2,
-			user: alice,
-		});
-		const expected2 = {
-			...meDetailed(alice, true),
-			bannerId: null,
-			bannerBlurhash: null,
-			bannerUrl: null,
-		};
-		assert.deepStrictEqual(response2, expected2, inspect(parameters));
-	});
-
-	//#endregion
-	//#region 自分の情報の更新(i/pin, i/unpin)
-
-	test("を書き換えることができる(ピン止めノート)", async () => {
-		const parameters = { noteId: aliceNote.id };
-		const response = await successfulApiCall({
-			endpoint: "i/pin",
-			parameters,
-			user: alice,
-		});
-		const expected = {
-			...meDetailed(alice, false),
-			pinnedNoteIds: [aliceNote.id],
-			pinnedNotes: [aliceNote],
-		};
-		assert.deepStrictEqual(response, expected);
-
-		const response2 = await successfulApiCall({
-			endpoint: "i/unpin",
-			parameters,
-			user: alice,
-		});
-		const expected2 = meDetailed(alice, false);
-		assert.deepStrictEqual(response2, expected2);
-	});
-
-	//#endregion
-	//#region メモの更新(users/update-memo)
-
-	test.each([
-		{ label: "最大長", memo: "x".repeat(2048) },
-		{ label: "空文字", memo: "", expects: null },
-		{ label: "null", memo: null },
-	])("を書き換えることができる(メモを$labelに)", async ({ memo, expects }) => {
-		const expected = {
-			...(await show(bob.id, alice)),
-			memo: expects === undefined ? memo : expects,
-		};
-		const parameters = { userId: bob.id, memo };
-		await successfulApiCall({
-			endpoint: "users/update-memo",
-			parameters,
-			user: alice,
-		});
-		const response = await show(bob.id, alice);
-		assert.deepStrictEqual(response, expected);
-	});
-
-	//#endregion
-	//#region ユーザー(users)
-
-	test.each([
-		{
-			label: "ID昇順",
-			parameters: { limit: 5 },
-			selector: (u: UserLite): string => u.id,
-		},
-		{
-			label: "フォロワー昇順",
-			parameters: { sort: "+follower" },
-			selector: (u: UserDetailedNotMe): string => String(u.followersCount),
-		},
-		{
-			label: "フォロワー降順",
-			parameters: { sort: "-follower" },
-			selector: (u: UserDetailedNotMe): string => String(u.followersCount),
-		},
-		{
-			label: "登録日時昇順",
-			parameters: { sort: "+createdAt" },
-			selector: (u: UserDetailedNotMe): string => u.createdAt,
-		},
-		{
-			label: "登録日時降順",
-			parameters: { sort: "-createdAt" },
-			selector: (u: UserDetailedNotMe): string => u.createdAt,
-		},
-		{
-			label: "投稿日時昇順",
-			parameters: { sort: "+updatedAt" },
-			selector: (u: UserDetailedNotMe): string => String(u.updatedAt),
-		},
-		{
-			label: "投稿日時降順",
-			parameters: { sort: "-updatedAt" },
-			selector: (u: UserDetailedNotMe): string => String(u.updatedAt),
-		},
-	] as const)(
-		"をリスト形式で取得することができる($label)",
-		async ({ parameters, selector }) => {
-			const response = await successfulApiCall({
-				endpoint: "users",
-				parameters,
-				user: alice,
-			});
-
-			// 結果の並びを事前にアサートするのは困難なので返ってきたidに対応するユーザーが返っており、ソート順が正しいことだけを検証する
-			const users = await Promise.all(response.map((u) => show(u.id, alice)));
-			const expected = users.sort((x, y) => {
-				const index =
-					selector(x) < selector(y) ? -1 : selector(x) > selector(y) ? 1 : 0;
-				return index * (parameters.sort?.startsWith("+") ? -1 : 1);
-			});
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれない",
-			user: (): User => userNotExplorable,
-			excluded: true,
-		},
-		{
-			label: "ミュートユーザーが含まれない",
-			user: (): User => userMutedByAlice,
-			excluded: true,
-		},
-		{
-			label: "ブロックされているユーザーが含まれない",
-			user: (): User => userBlockedByAlice,
-			excluded: true,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれる",
-			user: (): User => userBlockingAlice,
-			excluded: true,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		{
-			label: "サスペンドユーザーが含まれない",
-			user: (): User => userSuspended,
-			excluded: true,
-		},
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"をリスト形式で取得することができ、結果に$label",
-		async ({ user, excluded }) => {
-			const parameters = { limit: 100 };
-			const response = await successfulApiCall({
-				endpoint: "users",
-				parameters,
-				user: alice,
-			});
-			const expected = excluded ?? false ? [] : [await show(user().id, alice)];
-			assert.deepStrictEqual(
-				response.filter((u) => u.id === user().id),
-				expected,
-			);
-		},
-	);
-	test.todo("をリスト形式で取得することができる(リモート, hostname指定)");
-	test.todo("をリスト形式で取得することができる(pagenation)");
-
-	//#endregion
-	//#region ユーザー情報(users/show)
-
-	test.each([
-		{
-			label: "ID指定で自分自身を",
-			parameters: (): object => ({ userId: alice.id }),
-			user: (): User => alice,
-			type: meDetailed,
-		},
-		{
-			label: "ID指定で他人を",
-			parameters: (): object => ({ userId: alice.id }),
-			user: (): User => bob,
-			type: userDetailedNotMeWithRelations,
-		},
-		{
-			label: "ID指定かつ未認証",
-			parameters: (): object => ({ userId: alice.id }),
-			user: undefined,
-			type: userDetailedNotMe,
-		},
-		{
-			label: "@指定で自分自身を",
-			parameters: (): object => ({ username: alice.username }),
-			user: (): User => alice,
-			type: meDetailed,
-		},
-		{
-			label: "@指定で他人を",
-			parameters: (): object => ({ username: alice.username }),
-			user: (): User => bob,
-			type: userDetailedNotMeWithRelations,
-		},
-		{
-			label: "@指定かつ未認証",
-			parameters: (): object => ({ username: alice.username }),
-			user: undefined,
-			type: userDetailedNotMe,
-		},
-	] as const)(
-		"を取得することができる($label)",
-		async ({ parameters, user, type }) => {
-			const response = await successfulApiCall({
-				endpoint: "users/show",
-				parameters: parameters(),
-				user: user?.(),
-			});
-			const expected = type(alice);
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.each([
-		{
-			label: "Administratorになっている",
-			user: (): User => userAdmin,
-			me: (): User => userAdmin,
-			selector: (user: User): unknown => user.isAdmin,
-		},
-		{
-			label: "自分以外から見たときはAdministratorか判定できない",
-			user: (): User => userAdmin,
-			selector: (user: User): unknown => user.isAdmin,
-			expected: (): undefined => undefined,
-		},
-		{
-			label: "Moderatorになっている",
-			user: (): User => userModerator,
-			me: (): User => userModerator,
-			selector: (user: User): unknown => user.isModerator,
-		},
-		{
-			label: "自分以外から見たときはModeratorか判定できない",
-			user: (): User => userModerator,
-			selector: (user: User): unknown => user.isModerator,
-			expected: (): undefined => undefined,
-		},
-		{
-			label: "サイレンスになっている",
-			user: (): User => userSilenced,
-			selector: (user: User): unknown => user.isSilenced,
-		},
-		//{ label: 'サスペンドになっている', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended },
-		{
-			label: "削除済みになっている",
-			user: (): User => userDeletedBySelf,
-			me: (): User => userDeletedBySelf,
-			selector: (user: User): unknown => user.isDeleted,
-		},
-		{
-			label: "自分以外から見たときは削除済みか判定できない",
-			user: (): User => userDeletedBySelf,
-			selector: (user: User): unknown => user.isDeleted,
-			expected: (): undefined => undefined,
-		},
-		{
-			label: "削除済み(byAdmin)になっている",
-			user: (): User => userDeletedByAdmin,
-			me: (): User => userDeletedByAdmin,
-			selector: (user: User): unknown => user.isDeleted,
-		},
-		{
-			label: "自分以外から見たときは削除済み(byAdmin)か判定できない",
-			user: (): User => userDeletedByAdmin,
-			selector: (user: User): unknown => user.isDeleted,
-			expected: (): undefined => undefined,
-		},
-		{
-			label: "フォロー中になっている",
-			user: (): User => userFollowedByAlice,
-			selector: (user: User): unknown => user.isFollowing,
-		},
-		{
-			label: "フォローされている",
-			user: (): User => userFollowingAlice,
-			selector: (user: User): unknown => user.isFollowed,
-		},
-		{
-			label: "ブロック中になっている",
-			user: (): User => userBlockedByAlice,
-			selector: (user: User): unknown => user.isBlocking,
-		},
-		{
-			label: "ブロックされている",
-			user: (): User => userBlockingAlice,
-			selector: (user: User): unknown => user.isBlocked,
-		},
-		{
-			label: "ミュート中になっている",
-			user: (): User => userMutedByAlice,
-			selector: (user: User): unknown => user.isMuted,
-		},
-		{
-			label: "リノートミュート中になっている",
-			user: (): User => userRnMutedByAlice,
-			selector: (user: User): unknown => user.isRenoteMuted,
-		},
-		{
-			label: "フォローリクエスト中になっている",
-			user: (): User => userFollowRequested,
-			me: (): User => userFollowRequesting,
-			selector: (user: User): unknown => user.hasPendingFollowRequestFromYou,
-		},
-		{
-			label: "フォローリクエストされている",
-			user: (): User => userFollowRequesting,
-			me: (): User => userFollowRequested,
-			selector: (user: User): unknown => user.hasPendingFollowRequestToYou,
-		},
-	] as const)(
-		"を取得することができ、$labelこと",
-		async ({ user, me, selector, expected }) => {
-			const response = await successfulApiCall({
-				endpoint: "users/show",
-				parameters: { userId: user().id },
-				user: me?.() ?? alice,
-			});
-			assert.strictEqual(
-				selector(response),
-				(expected ?? ((): true => true))(),
-			);
-		},
-	);
-	test("を取得することができ、Publicなロールがセットされていること", async () => {
-		const response = await successfulApiCall({
-			endpoint: "users/show",
-			parameters: { userId: userRolePublic.id },
-			user: alice,
-		});
-		assert.deepStrictEqual(response.badgeRoles, []);
-		assert.deepStrictEqual(response.roles, [
-			{
-				id: rolePublic.id,
-				name: rolePublic.name,
-				color: rolePublic.color,
-				iconUrl: rolePublic.iconUrl,
-				description: rolePublic.description,
-				isModerator: rolePublic.isModerator,
-				isAdministrator: rolePublic.isAdministrator,
-				displayOrder: rolePublic.displayOrder,
-			},
-		]);
-	});
-	test("を取得することができ、バッヂロールがセットされていること", async () => {
-		const response = await successfulApiCall({
-			endpoint: "users/show",
-			parameters: { userId: userRoleBadge.id },
-			user: alice,
-		});
-		assert.deepStrictEqual(response.badgeRoles, [
-			{
-				name: roleBadge.name,
-				iconUrl: roleBadge.iconUrl,
-				displayOrder: roleBadge.displayOrder,
-			},
-		]);
-		assert.deepStrictEqual(response.roles, []); // バッヂだからといってrolesが取れるとは限らない
-	});
-	test("をID指定のリスト形式で取得することができる(空)", async () => {
-		const parameters = { userIds: [] };
-		const response = await successfulApiCall({
-			endpoint: "users/show",
-			parameters,
-			user: alice,
-		});
-		const expected: [] = [];
-		assert.deepStrictEqual(response, expected);
-	});
-	test("をID指定のリスト形式で取得することができる", async () => {
-		const parameters = { userIds: [bob.id, alice.id, carol.id] };
-		const response = await successfulApiCall({
-			endpoint: "users/show",
-			parameters,
-			user: alice,
-		});
-		const expected = [
-			await successfulApiCall({
-				endpoint: "users/show",
-				parameters: { userId: bob.id },
-				user: alice,
-			}),
-			await successfulApiCall({
-				endpoint: "users/show",
-				parameters: { userId: alice.id },
-				user: alice,
-			}),
-			await successfulApiCall({
-				endpoint: "users/show",
-				parameters: { userId: carol.id },
-				user: alice,
-			}),
-		];
-		assert.deepStrictEqual(response, expected);
-	});
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれる",
-			user: (): User => userNotExplorable,
-		},
-		{ label: "ミュートユーザーが含まれる", user: (): User => userMutedByAlice },
-		{
-			label: "ブロックされているユーザーが含まれる",
-			user: (): User => userBlockedByAlice,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれる",
-			user: (): User => userBlockingAlice,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		{
-			label: "サスペンドユーザーが(モデレーターが見るときは)含まれる",
-			user: (): User => userSuspended,
-			me: (): User => root,
-		},
-		// BUG サスペンドユーザーを一般ユーザーから見るとrootユーザーが返ってくる
-		//{ label: 'サスペンドユーザーが(一般ユーザーが見るときは)含まれない', user: (): User => userSuspended, me: (): User => bob, excluded: true },
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"をID指定のリスト形式で取得することができ、結果に$label",
-		async ({ user, me, excluded }) => {
-			const parameters = { userIds: [user().id] };
-			const response = await successfulApiCall({
-				endpoint: "users/show",
-				parameters,
-				user: me?.() ?? alice,
-			});
-			const expected =
-				excluded ?? false ? [] : [await show(user().id, me?.() ?? alice)];
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.todo("をID指定のリスト形式で取得することができる(リモート)");
-
-	//#endregion
-	//#region 検索(users/search)
-
-	test("を検索することができる", async () => {
-		const parameters = { query: "carol", limit: 10 };
-		const response = await successfulApiCall({
-			endpoint: "users/search",
-			parameters,
-			user: alice,
-		});
-		const expected = [await show(carol.id, alice)];
-		assert.deepStrictEqual(response, expected);
-	});
-	test("を検索することができる(UserLite)", async () => {
-		const parameters = { query: "carol", detail: false, limit: 10 };
-		const response = await successfulApiCall({
-			endpoint: "users/search",
-			parameters,
-			user: alice,
-		});
-		const expected = [userLite(await show(carol.id, alice))];
-		assert.deepStrictEqual(response, expected);
-	});
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれる",
-			user: (): User => userNotExplorable,
-		},
-		{ label: "ミュートユーザーが含まれる", user: (): User => userMutedByAlice },
-		{
-			label: "ブロックされているユーザーが含まれる",
-			user: (): User => userBlockedByAlice,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれる",
-			user: (): User => userBlockingAlice,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		{
-			label: "サスペンドユーザーが含まれない",
-			user: (): User => userSuspended,
-			excluded: true,
-		},
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"を検索することができ、結果に$labelが含まれる",
-		async ({ user, excluded }) => {
-			const parameters = { query: user().username, limit: 1 };
-			const response = await successfulApiCall({
-				endpoint: "users/search",
-				parameters,
-				user: alice,
-			});
-			const expected = excluded ?? false ? [] : [await show(user().id, alice)];
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.todo("を検索することができる(リモート)");
-	test.todo("を検索することができる(pagenation)");
-
-	//#endregion
-	//#region ID指定検索(users/search-by-username-and-host)
-
-	test.each([
-		{
-			label: "自分",
-			parameters: { username: "alice" },
-			user: (): User[] => [alice],
-		},
-		{
-			label: "自分かつusernameが大文字",
-			parameters: { username: "ALICE" },
-			user: (): User[] => [alice],
-		},
-		{
-			label: "ローカルのフォロイーでノートなし",
-			parameters: { username: "userFollowedByAlice" },
-			user: (): User[] => [userFollowedByAlice],
-		},
-		{
-			label: "ローカルでノートなしは検索に載らない",
-			parameters: { username: "userNoNote" },
-			user: (): User[] => [],
-		},
-		{
-			label: "ローカルの他人1",
-			parameters: { username: "bob" },
-			user: (): User[] => [bob],
-		},
-		{
-			label: "ローカルの他人2",
-			parameters: { username: "bob", host: null },
-			user: (): User[] => [bob],
-		},
-		{
-			label: "ローカルの他人3",
-			parameters: { username: "bob", host: "." },
-			user: (): User[] => [bob],
-		},
-		{
-			label: "ローカル",
-			parameters: { host: null, limit: 1 },
-			user: (): User[] => [userFollowedByAlice],
-		},
-		{
-			label: "ローカル",
-			parameters: { host: ".", limit: 1 },
-			user: (): User[] => [userFollowedByAlice],
-		},
-	])("をID&ホスト指定で検索できる($label)", async ({ parameters, user }) => {
-		const response = await successfulApiCall({
-			endpoint: "users/search-by-username-and-host",
-			parameters,
-			user: alice,
-		});
-		const expected = await Promise.all(user().map((u) => show(u.id, alice)));
-		assert.deepStrictEqual(response, expected);
-	});
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれる",
-			user: (): User => userNotExplorable,
-		},
-		{ label: "ミュートユーザーが含まれる", user: (): User => userMutedByAlice },
-		{
-			label: "ブロックされているユーザーが含まれる",
-			user: (): User => userBlockedByAlice,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれる",
-			user: (): User => userBlockingAlice,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		{
-			label: "サスペンドユーザーが含まれない",
-			user: (): User => userSuspended,
-			excluded: true,
-		},
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"をID&ホスト指定で検索でき、結果に$label",
-		async ({ user, excluded }) => {
-			const parameters = { username: user().username };
-			const response = await successfulApiCall({
-				endpoint: "users/search-by-username-and-host",
-				parameters,
-				user: alice,
-			});
-			const expected = excluded ?? false ? [] : [await show(user().id, alice)];
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.todo("をID&ホスト指定で検索できる(リモート)");
-
-	//#endregion
-	//#region ID指定検索(users/get-frequently-replied-users)
-
-	test("がよくリプライをするユーザーのリストを取得できる", async () => {
-		const parameters = { userId: alice.id, limit: 5 };
-		const response = await successfulApiCall({
-			endpoint: "users/get-frequently-replied-users",
-			parameters,
-			user: alice,
-		});
-		const expected = await Promise.all(
-			usersReplying.slice(0, parameters.limit).map(async (s, i) => ({
-				user: await show(s.id, alice),
-				weight: (usersReplying.length - i) / usersReplying.length,
-			})),
-		);
-		assert.deepStrictEqual(response, expected);
-	});
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれる",
-			user: (): User => userNotExplorable,
-		},
-		{ label: "ミュートユーザーが含まれる", user: (): User => userMutedByAlice },
-		{
-			label: "ブロックされているユーザーが含まれる",
-			user: (): User => userBlockedByAlice,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれない",
-			user: (): User => userBlockingAlice,
-			excluded: true,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		//{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true },
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"がよくリプライをするユーザーのリストを取得でき、結果に$label",
-		async ({ user, excluded }) => {
-			const replyTo = (
-				await successfulApiCall({
-					endpoint: "users/notes",
-					parameters: { userId: user().id },
-					user: undefined,
-				})
-			)[0];
-			await post(alice, {
-				text: `@${user().username} test`,
-				replyId: replyTo.id,
-			});
-			const parameters = { userId: alice.id, limit: 100 };
-			const response = await successfulApiCall({
-				endpoint: "users/get-frequently-replied-users",
-				parameters,
-				user: alice,
-			});
-			const expected = excluded ?? false ? [] : [await show(user().id, alice)];
-			assert.deepStrictEqual(
-				response.map((s) => s.user).filter((u) => u.id === user().id),
-				expected,
-			);
-		},
-	);
-
-	//#endregion
-	//#region ハッシュタグ(hashtags/users)
-
-	test.each([
-		{
-			label: "フォロワー昇順",
-			sort: { sort: "+follower" },
-			selector: (u: UserDetailedNotMe): string => String(u.followersCount),
-		},
-		{
-			label: "フォロワー降順",
-			sort: { sort: "-follower" },
-			selector: (u: UserDetailedNotMe): string => String(u.followersCount),
-		},
-		{
-			label: "登録日時昇順",
-			sort: { sort: "+createdAt" },
-			selector: (u: UserDetailedNotMe): string => u.createdAt,
-		},
-		{
-			label: "登録日時降順",
-			sort: { sort: "-createdAt" },
-			selector: (u: UserDetailedNotMe): string => u.createdAt,
-		},
-		{
-			label: "投稿日時昇順",
-			sort: { sort: "+updatedAt" },
-			selector: (u: UserDetailedNotMe): string => String(u.updatedAt),
-		},
-		{
-			label: "投稿日時降順",
-			sort: { sort: "-updatedAt" },
-			selector: (u: UserDetailedNotMe): string => String(u.updatedAt),
-		},
-	] as const)(
-		"をハッシュタグ指定で取得することができる($label)",
-		async ({ sort, selector }) => {
-			const hashtag = "test_hashtag";
-			await successfulApiCall({
-				endpoint: "i/update",
-				parameters: { description: `#${hashtag}` },
-				user: alice,
-			});
-			const parameters = { tag: hashtag, limit: 5, ...sort };
-			const response = await successfulApiCall({
-				endpoint: "hashtags/users",
-				parameters,
-				user: alice,
-			});
-			const users = await Promise.all(response.map((u) => show(u.id, alice)));
-			const expected = users.sort((x, y) => {
-				const index =
-					selector(x) < selector(y) ? -1 : selector(x) > selector(y) ? 1 : 0;
-				return index * (parameters.sort.startsWith("+") ? -1 : 1);
-			});
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.each([
-		{
-			label: "「見つけやすくする」がOFFのユーザーが含まれる",
-			user: (): User => userNotExplorable,
-		},
-		{ label: "ミュートユーザーが含まれる", user: (): User => userMutedByAlice },
-		{
-			label: "ブロックされているユーザーが含まれる",
-			user: (): User => userBlockedByAlice,
-		},
-		{
-			label: "ブロックしてきているユーザーが含まれる",
-			user: (): User => userBlockingAlice,
-		},
-		{ label: "承認制ユーザーが含まれる", user: (): User => userLocking },
-		{ label: "サイレンスユーザーが含まれる", user: (): User => userSilenced },
-		{
-			label: "サスペンドユーザーが含まれない",
-			user: (): User => userSuspended,
-			excluded: true,
-		},
-		{ label: "削除済ユーザーが含まれる", user: (): User => userDeletedBySelf },
-		{
-			label: "削除済(byAdmin)ユーザーが含まれる",
-			user: (): User => userDeletedByAdmin,
-		},
-	] as const)(
-		"をハッシュタグ指定で取得することができ、結果に$label",
-		async ({ user, excluded }) => {
-			const hashtag = `user_test${user().username}`;
-			if (user() !== userSuspended) {
-				// サスペンドユーザーはupdateできない。
-				await successfulApiCall({
-					endpoint: "i/update",
-					parameters: { description: `#${hashtag}` },
-					user: user(),
-				});
-			}
-			const parameters = {
-				tag: hashtag,
-				limit: 100,
-				sort: "-follower",
-			} as const;
-			const response = await successfulApiCall({
-				endpoint: "hashtags/users",
-				parameters,
-				user: alice,
-			});
-			const expected = excluded ?? false ? [] : [await show(user().id, alice)];
-			assert.deepStrictEqual(response, expected);
-		},
-	);
-	test.todo("をハッシュタグ指定で取得することができる(リモート)");
-
-	//#endregion
-	//#region オススメユーザー(users/recommendation)
-
-	// BUG users/recommendationは壊れている? > QueryFailedError: missing FROM-clause entry for table "note"
-	test.skip("のオススメを取得することができる", async () => {
-		const parameters = {};
-		const response = await successfulApiCall({
-			endpoint: "users/recommendation",
-			parameters,
-			user: alice,
-		});
-		const expected = await Promise.all(response.map((u) => show(u.id)));
-		assert.deepStrictEqual(response, expected);
-	});
-
-	//#endregion
-	//#region ピン止めユーザー(pinned-users)
-
-	test("のピン止めユーザーを取得することができる", async () => {
-		await successfulApiCall({
-			endpoint: "admin/update-meta",
-			parameters: { pinnedUsers: [bob.username, `@${carol.username}`] },
-			user: root,
-		});
-		const parameters = {} as const;
-		const response = await successfulApiCall({
-			endpoint: "pinned-users",
-			parameters,
-			user: alice,
-		});
-		const expected = await Promise.all(
-			[bob, carol].map((u) => show(u.id, alice)),
-		);
-		assert.deepStrictEqual(response, expected);
-	});
-
-	//#endregion
-
-	test.todo("を管理人として確認することができる(admin/show-user)");
-	test.todo("を管理人として確認することができる(admin/show-users)");
-	test.todo("をサーバー向けに取得することができる(federation/users)");
-});
diff --git a/packages/backend/test/endpoints.ts b/packages/backend/test/endpoints.ts
deleted file mode 100644
index 2aedc25f2c..0000000000
--- a/packages/backend/test/endpoints.ts
+++ /dev/null
@@ -1,865 +0,0 @@
-/*
-process.env.NODE_ENV = 'test';
-
-import * as assert from 'assert';
-import * as childProcess from 'child_process';
-import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js';
-
-describe('API: Endpoints', () => {
-	let p: childProcess.ChildProcess;
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: 'alice' });
-		bob = await signup({ username: 'bob' });
-		carol = await signup({ username: 'carol' });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	describe('signup', () => {
-		it('不正なユーザー名でアカウントが作成できない', async(async () => {
-			const res = await request('/signup', {
-				username: 'test.',
-				password: 'test'
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパスワードでアカウントが作成できない', async(async () => {
-			const res = await request('/signup', {
-				username: 'test',
-				password: ''
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('正しくアカウントが作成できる', async(async () => {
-			const me = {
-				username: 'test1',
-				password: 'test1'
-			};
-
-			const res = await request('/signup', me);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.username, me.username);
-		}));
-
-		it('同じユーザー名のアカウントは作成できない', async(async () => {
-			await signup({
-				username: 'test2'
-			});
-
-			const res = await request('/signup', {
-				username: 'test2',
-				password: 'test2'
-			});
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('signin', () => {
-		it('間違ったパスワードでサインインできない', async(async () => {
-			await signup({
-				username: 'test3',
-				password: 'foo'
-			});
-
-			const res = await request('/signin', {
-				username: 'test3',
-				password: 'bar'
-			});
-
-			assert.strictEqual(res.status, 403);
-		}));
-
-		it('クエリをインジェクションできない', async(async () => {
-			await signup({
-				username: 'test4'
-			});
-
-			const res = await request('/signin', {
-				username: 'test4',
-				password: {
-					$gt: ''
-				}
-			});
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('正しい情報でサインインできる', async(async () => {
-			await signup({
-				username: 'test5',
-				password: 'foo'
-			});
-
-			const res = await request('/signin', {
-				username: 'test5',
-				password: 'foo'
-			});
-
-			assert.strictEqual(res.status, 200);
-		}));
-	});
-
-	describe('i/update', () => {
-		it('アカウント設定を更新できる', async(async () => {
-			const myName = '大室櫻子';
-			const myLocation = '七森中';
-			const myBirthday = '2000-09-07';
-
-			const res = await request('/i/update', {
-				name: myName,
-				location: myLocation,
-				birthday: myBirthday
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, myName);
-			assert.strictEqual(res.body.location, myLocation);
-			assert.strictEqual(res.body.birthday, myBirthday);
-		}));
-
-		it('名前を空白にできない', async(async () => {
-			const res = await request('/i/update', {
-				name: ' '
-			}, alice);
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('誕生日の設定を削除できる', async(async () => {
-			await request('/i/update', {
-				birthday: '2000-09-07'
-			}, alice);
-
-			const res = await request('/i/update', {
-				birthday: null
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.birthday, null);
-		}));
-
-		it('不正な誕生日の形式で怒られる', async(async () => {
-			const res = await request('/i/update', {
-				birthday: '2000/09/07'
-			}, alice);
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('users/show', () => {
-		it('ユーザーが取得できる', async(async () => {
-			const res = await request('/users/show', {
-				userId: alice.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.id, alice.id);
-		}));
-
-		it('ユーザーが存在しなかったら怒る', async(async () => {
-			const res = await request('/users/show', {
-				userId: '000000000000000000000000'
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/users/show', {
-				userId: 'kyoppie'
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('notes/show', () => {
-		it('投稿が取得できる', async(async () => {
-			const myPost = await post(alice, {
-				text: 'test'
-			});
-
-			const res = await request('/notes/show', {
-				noteId: myPost.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.id, myPost.id);
-			assert.strictEqual(res.body.text, myPost.text);
-		}));
-
-		it('投稿が存在しなかったら怒る', async(async () => {
-			const res = await request('/notes/show', {
-				noteId: '000000000000000000000000'
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/notes/show', {
-				noteId: 'kyoppie'
-			});
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('notes/reactions/create', () => {
-		it('リアクションできる', async(async () => {
-			const bobPost = await post(bob);
-
-			const alice = await signup({ username: 'alice' });
-			const res = await request('/notes/reactions/create', {
-				noteId: bobPost.id,
-				reaction: '🚀',
-			}, alice);
-
-			assert.strictEqual(res.status, 204);
-
-			const resNote = await request('/notes/show', {
-				noteId: bobPost.id,
-			}, alice);
-
-			assert.strictEqual(resNote.status, 200);
-			assert.strictEqual(resNote.body.reactions['🚀'], [alice.id]);
-		}));
-
-		it('自分の投稿にもリアクションできる', async(async () => {
-			const myPost = await post(alice);
-
-			const res = await request('/notes/reactions/create', {
-				noteId: myPost.id,
-				reaction: '🚀',
-			}, alice);
-
-			assert.strictEqual(res.status, 204);
-		}));
-
-		it('二重にリアクションできない', async(async () => {
-			const bobPost = await post(bob);
-
-			await react(alice, bobPost, 'like');
-
-			const res = await request('/notes/reactions/create', {
-				noteId: bobPost.id,
-				reaction: '🚀',
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しない投稿にはリアクションできない', async(async () => {
-			const res = await request('/notes/reactions/create', {
-				noteId: '000000000000000000000000',
-				reaction: '🚀',
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const res = await request('/notes/reactions/create', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/notes/reactions/create', {
-				noteId: 'kyoppie',
-				reaction: '🚀',
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('following/create', () => {
-		it('フォローできる', async(async () => {
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 200);
-		}));
-
-		it('既にフォローしている場合は怒る', async(async () => {
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーはフォローできない', async(async () => {
-			const res = await request('/following/create', {
-				userId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('自分自身はフォローできない', async(async () => {
-			const res = await request('/following/create', {
-				userId: alice.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const res = await request('/following/create', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/following/create', {
-				userId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('following/delete', () => {
-		it('フォロー解除できる', async(async () => {
-			await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 200);
-		}));
-
-		it('フォローしていない場合は怒る', async(async () => {
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, bob);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーはフォロー解除できない', async(async () => {
-			const res = await request('/following/delete', {
-				userId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('自分自身はフォロー解除できない', async(async () => {
-			const res = await request('/following/delete', {
-				userId: alice.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('空のパラメータで怒られる', async(async () => {
-			const res = await request('/following/delete', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/following/delete', {
-				userId: 'kyoppie'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('drive', () => {
-		it('ドライブ情報を取得できる', async(async () => {
-			await uploadFile({
-				userId: alice.id,
-				size: 256
-			});
-			await uploadFile({
-				userId: alice.id,
-				size: 512
-			});
-			await uploadFile({
-				userId: alice.id,
-				size: 1024
-			});
-			const res = await request('/drive', {}, alice);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			expect(res.body).have.property('usage').eql(1792);
-		}));
-	});
-
-	describe('drive/files/create', () => {
-		it('ファイルを作成できる', async(async () => {
-			const res = await uploadFile(alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'Lenna.png');
-		}));
-
-		it('ファイルに名前を付けられる', async(async () => {
-			const res = await assert.request(server)
-				.post('/drive/files/create')
-				.field('i', alice.token)
-				.field('name', 'Belmond.png')
-				.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
-
-			expect(res).have.status(200);
-			expect(res.body).be.a('object');
-			expect(res.body).have.property('name').eql('Belmond.png');
-		}));
-
-		it('ファイル無しで怒られる', async(async () => {
-			const res = await request('/drive/files/create', {}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('SVGファイルを作成できる', async(async () => {
-			const res = await uploadFile(alice, __dirname + '/resources/image.svg');
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'image.svg');
-			assert.strictEqual(res.body.type, 'image/svg+xml');
-		}));
-	});
-
-	describe('drive/files/update', () => {
-		it('名前を更新できる', async(async () => {
-			const file = await uploadFile(alice);
-			const newName = 'いちごパスタ.png';
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				name: newName
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, newName);
-		}));
-
-		it('他人のファイルは更新できない', async(async () => {
-			const file = await uploadFile(bob);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('親フォルダを更新できる', async(async () => {
-			const file = await uploadFile(alice);
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.folderId, folder.id);
-		}));
-
-		it('親フォルダを無しにできる', async(async () => {
-			const file = await uploadFile(alice);
-
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: null
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.folderId, null);
-		}));
-
-		it('他人のフォルダには入れられない', async(async () => {
-			const file = await uploadFile(alice);
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, bob)).body;
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: folder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないフォルダで怒られる', async(async () => {
-			const file = await uploadFile(alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なフォルダIDで怒られる', async(async () => {
-			const file = await uploadFile(alice);
-
-			const res = await request('/drive/files/update', {
-				fileId: file.id,
-				folderId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('ファイルが存在しなかったら怒る', async(async () => {
-			const res = await request('/drive/files/update', {
-				fileId: '000000000000000000000000',
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('間違ったIDで怒られる', async(async () => {
-			const res = await request('/drive/files/update', {
-				fileId: 'kyoppie',
-				name: 'いちごパスタ.png'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('drive/folders/create', () => {
-		it('フォルダを作成できる', async(async () => {
-			const res = await request('/drive/folders/create', {
-				name: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'test');
-		}));
-	});
-
-	describe('drive/folders/update', () => {
-		it('名前を更新できる', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				name: 'new name'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.name, 'new name');
-		}));
-
-		it('他人のフォルダを更新できない', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, bob)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				name: 'new name'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('親フォルダを更新できる', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.parentId, parentFolder.id);
-		}));
-
-		it('親フォルダを無しに更新できる', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: null
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.parentId, null);
-		}));
-
-		it('他人のフォルダを親フォルダに設定できない', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, bob)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const parentFolder = (await request('/drive/folders/create', {
-				name: 'parent'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: parentFolder.id,
-				parentId: folder.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: parentFolder.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない(再帰的)', async(async () => {
-			const folderA = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const folderB = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			const folderC = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-			await request('/drive/folders/update', {
-				folderId: folderB.id,
-				parentId: folderA.id
-			}, alice);
-			await request('/drive/folders/update', {
-				folderId: folderC.id,
-				parentId: folderB.id
-			}, alice);
-
-			const res = await request('/drive/folders/update', {
-				folderId: folderA.id,
-				parentId: folderC.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('フォルダが循環するような構造にできない(自身)', async(async () => {
-			const folderA = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folderA.id,
-				parentId: folderA.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しない親フォルダを設定できない', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正な親フォルダIDで怒られる', async(async () => {
-			const folder = (await request('/drive/folders/create', {
-				name: 'test'
-			}, alice)).body;
-
-			const res = await request('/drive/folders/update', {
-				folderId: folder.id,
-				parentId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないフォルダを更新できない', async(async () => {
-			const res = await request('/drive/folders/update', {
-				folderId: '000000000000000000000000'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なフォルダIDで怒られる', async(async () => {
-			const res = await request('/drive/folders/update', {
-				folderId: 'foo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('messaging/messages/create', () => {
-		it('メッセージを送信できる', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id,
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
-			assert.strictEqual(res.body.text, 'test');
-		}));
-
-		it('自分自身にはメッセージを送信できない', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: alice.id,
-				text: 'Yo'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('存在しないユーザーにはメッセージを送信できない', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: '000000000000000000000000',
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('不正なユーザーIDで怒られる', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: 'foo',
-				text: 'test'
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('テキストが無くて怒られる', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it('文字数オーバーで怒られる', async(async () => {
-			const res = await request('/messaging/messages/create', {
-				userId: bob.id,
-				text: '!'.repeat(1001)
-			}, alice);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe('notes/replies', () => {
-		it('自分に閲覧権限のない投稿は含まれない', async(async () => {
-			const alicePost = await post(alice, {
-				text: 'foo'
-			});
-
-			await post(bob, {
-				replyId: alicePost.id,
-				text: 'bar',
-				visibility: 'specified',
-				visibleUserIds: [alice.id]
-			});
-
-			const res = await request('/notes/replies', {
-				noteId: alicePost.id
-			}, carol);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(res.body.length, 0);
-		}));
-	});
-
-	describe('notes/timeline', () => {
-		it('フォロワー限定投稿が含まれる', async(async () => {
-			await request('/following/create', {
-				userId: alice.id
-			}, bob);
-
-			const alicePost = await post(alice, {
-				text: 'foo',
-				visibility: 'followers'
-			});
-
-			const res = await request('/notes/timeline', {}, bob);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(res.body.length, 1);
-			assert.strictEqual(res.body[0].id, alicePost.id);
-		}));
-	});
-});
-*/
diff --git a/packages/backend/test/extract-mentions.ts b/packages/backend/test/extract-mentions.ts
deleted file mode 100644
index cf25a15f6e..0000000000
--- a/packages/backend/test/extract-mentions.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import * as assert from "node:assert";
-
-import { parse } from "mfm-js";
-import { extractMentions } from "../src/misc/extract-mentions.js";
-
-describe("Extract mentions", () => {
-	it("simple", () => {
-		const ast = parse("@foo @bar @baz")!;
-		const mentions = extractMentions(ast);
-		assert.deepStrictEqual(mentions, [
-			{
-				username: "foo",
-				acct: "@foo",
-				host: null,
-			},
-			{
-				username: "bar",
-				acct: "@bar",
-				host: null,
-			},
-			{
-				username: "baz",
-				acct: "@baz",
-				host: null,
-			},
-		]);
-	});
-
-	it("nested", () => {
-		const ast = parse("@foo **@bar** @baz")!;
-		const mentions = extractMentions(ast);
-		assert.deepStrictEqual(mentions, [
-			{
-				username: "foo",
-				acct: "@foo",
-				host: null,
-			},
-			{
-				username: "bar",
-				acct: "@bar",
-				host: null,
-			},
-			{
-				username: "baz",
-				acct: "@baz",
-				host: null,
-			},
-		]);
-	});
-});
diff --git a/packages/backend/test/fetch-resource.ts b/packages/backend/test/fetch-resource.ts
deleted file mode 100644
index 688c3ffa81..0000000000
--- a/packages/backend/test/fetch-resource.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import * as openapi from "@redocly/openapi-core";
-import {
-	async,
-	port,
-	post,
-	request,
-	shutdownServer,
-	signup,
-	simpleGet,
-	startServer,
-} from "./utils.js";
-
-// Request Accept
-const ONLY_AP = "application/activity+json";
-const PREFER_AP = "application/activity+json, */*";
-const PREFER_HTML = "text/html, */*";
-const UNSPECIFIED = "*/*";
-
-// Response Contet-Type
-const AP = "application/activity+json; charset=utf-8";
-const JSON = "application/json; charset=utf-8";
-const HTML = "text/html; charset=utf-8";
-
-describe("Fetch resource", () => {
-	let p: childProcess.ChildProcess;
-
-	let alice: any;
-	let alicesPost: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		alicesPost = await post(alice, {
-			text: "test",
-		});
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	describe("Common", () => {
-		it("meta", async(async () => {
-			const res = await request("/meta", {});
-
-			assert.strictEqual(res.status, 200);
-		}));
-
-		it("GET root", async(async () => {
-			const res = await simpleGet("/");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-
-		it("GET docs", async(async () => {
-			const res = await simpleGet("/docs/ja-JP/about");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-
-		it("GET api-doc", async(async () => {
-			const res = await simpleGet("/api-doc");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-
-		it("GET api.json", async(async () => {
-			const res = await simpleGet("/api.json");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, JSON);
-		}));
-
-		it("Validate api.json", async(async () => {
-			const config = await openapi.loadConfig();
-			const result = await openapi.bundle({
-				config,
-				ref: `http://localhost:${port}/api.json`,
-			});
-
-			for (const problem of result.problems) {
-				console.log(`${problem.message} - ${problem.location[0]?.pointer}`);
-			}
-
-			assert.strictEqual(result.problems.length, 0);
-		}));
-
-		it("GET favicon.ico", async(async () => {
-			const res = await simpleGet("/favicon.ico");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "image/x-icon");
-		}));
-
-		it("GET apple-touch-icon.png", async(async () => {
-			const res = await simpleGet("/apple-touch-icon.png");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "image/png");
-		}));
-
-		it("GET twemoji svg", async(async () => {
-			const res = await simpleGet("/twemoji/2764.svg");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "image/svg+xml");
-		}));
-
-		it("GET twemoji svg with hyphen", async(async () => {
-			const res = await simpleGet("/twemoji/2764-fe0f-200d-1f525.svg");
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "image/svg+xml");
-		}));
-	});
-
-	describe("/@:username", () => {
-		it("Only AP => AP", async(async () => {
-			const res = await simpleGet(`/@${alice.username}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer AP => AP", async(async () => {
-			const res = await simpleGet(`/@${alice.username}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer HTML => HTML", async(async () => {
-			const res = await simpleGet(`/@${alice.username}`, PREFER_HTML);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-
-		it("Unspecified => HTML", async(async () => {
-			const res = await simpleGet(`/@${alice.username}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-	});
-
-	describe("/users/:id", () => {
-		it("Only AP => AP", async(async () => {
-			const res = await simpleGet(`/users/${alice.id}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer AP => AP", async(async () => {
-			const res = await simpleGet(`/users/${alice.id}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer HTML => Redirect to /@:username", async(async () => {
-			const res = await simpleGet(`/users/${alice.id}`, PREFER_HTML);
-			assert.strictEqual(res.status, 302);
-			assert.strictEqual(res.location, `/@${alice.username}`);
-		}));
-
-		it("Undecided => HTML", async(async () => {
-			const res = await simpleGet(`/users/${alice.id}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 302);
-			assert.strictEqual(res.location, `/@${alice.username}`);
-		}));
-	});
-
-	describe("/notes/:id", () => {
-		it("Only AP => AP", async(async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer AP => AP", async(async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		}));
-
-		it("Prefer HTML => HTML", async(async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_HTML);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-
-		it("Unspecified => HTML", async(async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		}));
-	});
-
-	describe("Feeds", () => {
-		it("RSS", async(async () => {
-			const res = await simpleGet(`/@${alice.username}.rss`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "application/rss+xml; charset=utf-8");
-		}));
-
-		it("ATOM", async(async () => {
-			const res = await simpleGet(`/@${alice.username}.atom`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "application/atom+xml; charset=utf-8");
-		}));
-
-		it("JSON", async(async () => {
-			const res = await simpleGet(`/@${alice.username}.json`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, "application/json; charset=utf-8");
-		}));
-	});
-});
diff --git a/packages/backend/test/ff-visibility.ts b/packages/backend/test/ff-visibility.ts
deleted file mode 100644
index 2cd4d3c60e..0000000000
--- a/packages/backend/test/ff-visibility.ts
+++ /dev/null
@@ -1,283 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	connectStream,
-	post,
-	react,
-	request,
-	shutdownServer,
-	signup,
-	simpleGet,
-	startServer,
-} from "./utils.js";
-
-describe("FF visibility", () => {
-	let p: childProcess.ChildProcess;
-
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-		carol = await signup({ username: "carol" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("ffVisibility が public なユーザーのフォロー/フォロワーを誰でも見れる", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "public",
-			},
-			alice,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-
-		assert.strictEqual(followingRes.status, 200);
-		assert.strictEqual(Array.isArray(followingRes.body), true);
-		assert.strictEqual(followersRes.status, 200);
-		assert.strictEqual(Array.isArray(followersRes.body), true);
-	}));
-
-	it("ffVisibility が followers なユーザーのフォロー/フォロワーを自分で見れる", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "followers",
-			},
-			alice,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			alice,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			alice,
-		);
-
-		assert.strictEqual(followingRes.status, 200);
-		assert.strictEqual(Array.isArray(followingRes.body), true);
-		assert.strictEqual(followersRes.status, 200);
-		assert.strictEqual(Array.isArray(followersRes.body), true);
-	}));
-
-	it("ffVisibility が followers なユーザーのフォロー/フォロワーを非フォロワーが見れない", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "followers",
-			},
-			alice,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-
-		assert.strictEqual(followingRes.status, 400);
-		assert.strictEqual(followersRes.status, 400);
-	}));
-
-	it("ffVisibility が followers なユーザーのフォロー/フォロワーをフォロワーが見れる", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "followers",
-			},
-			alice,
-		);
-
-		await request(
-			"/following/create",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-
-		assert.strictEqual(followingRes.status, 200);
-		assert.strictEqual(Array.isArray(followingRes.body), true);
-		assert.strictEqual(followersRes.status, 200);
-		assert.strictEqual(Array.isArray(followersRes.body), true);
-	}));
-
-	it("ffVisibility が private なユーザーのフォロー/フォロワーを自分で見れる", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "private",
-			},
-			alice,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			alice,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			alice,
-		);
-
-		assert.strictEqual(followingRes.status, 200);
-		assert.strictEqual(Array.isArray(followingRes.body), true);
-		assert.strictEqual(followersRes.status, 200);
-		assert.strictEqual(Array.isArray(followersRes.body), true);
-	}));
-
-	it("ffVisibility が private なユーザーのフォロー/フォロワーを他人が見れない", async(async () => {
-		await request(
-			"/i/update",
-			{
-				ffVisibility: "private",
-			},
-			alice,
-		);
-
-		const followingRes = await request(
-			"/users/following",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-		const followersRes = await request(
-			"/users/followers",
-			{
-				userId: alice.id,
-			},
-			bob,
-		);
-
-		assert.strictEqual(followingRes.status, 400);
-		assert.strictEqual(followersRes.status, 400);
-	}));
-
-	describe("AP", () => {
-		it("ffVisibility が public 以外ならばAPからは取得できない", async(async () => {
-			{
-				await request(
-					"/i/update",
-					{
-						ffVisibility: "public",
-					},
-					alice,
-				);
-
-				const followingRes = await simpleGet(
-					`/users/${alice.id}/following`,
-					"application/activity+json",
-				);
-				const followersRes = await simpleGet(
-					`/users/${alice.id}/followers`,
-					"application/activity+json",
-				);
-				assert.strictEqual(followingRes.status, 200);
-				assert.strictEqual(followersRes.status, 200);
-			}
-			{
-				await request(
-					"/i/update",
-					{
-						ffVisibility: "followers",
-					},
-					alice,
-				);
-
-				const followingRes = await simpleGet(
-					`/users/${alice.id}/following`,
-					"application/activity+json",
-				).catch((res) => ({ status: res.statusCode }));
-				const followersRes = await simpleGet(
-					`/users/${alice.id}/followers`,
-					"application/activity+json",
-				).catch((res) => ({ status: res.statusCode }));
-				assert.strictEqual(followingRes.status, 403);
-				assert.strictEqual(followersRes.status, 403);
-			}
-			{
-				await request(
-					"/i/update",
-					{
-						ffVisibility: "private",
-					},
-					alice,
-				);
-
-				const followingRes = await simpleGet(
-					`/users/${alice.id}/following`,
-					"application/activity+json",
-				).catch((res) => ({ status: res.statusCode }));
-				const followersRes = await simpleGet(
-					`/users/${alice.id}/followers`,
-					"application/activity+json",
-				).catch((res) => ({ status: res.statusCode }));
-				assert.strictEqual(followingRes.status, 403);
-				assert.strictEqual(followersRes.status, 403);
-			}
-		}));
-	});
-});
diff --git a/packages/backend/test/get-file-info.ts b/packages/backend/test/get-file-info.ts
deleted file mode 100644
index cfe1fd445e..0000000000
--- a/packages/backend/test/get-file-info.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import * as assert from "node:assert";
-import { dirname } from "node:path";
-import { fileURLToPath } from "node:url";
-import { getFileInfo } from "../src/misc/get-file-info.js";
-import { async } from "./utils.js";
-
-const _filename = fileURLToPath(import.meta.url);
-const _dirname = dirname(_filename);
-
-describe("Get file info", () => {
-	it("Empty file", async(async () => {
-		const path = `${_dirname}/resources/emptyfile`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 0,
-			md5: "d41d8cd98f00b204e9800998ecf8427e",
-			type: {
-				mime: "application/octet-stream",
-				ext: null,
-			},
-			width: undefined,
-			height: undefined,
-			orientation: undefined,
-		});
-	}));
-
-	it("Generic JPEG", async(async () => {
-		const path = `${_dirname}/resources/Lenna.jpg`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 25360,
-			md5: "091b3f259662aa31e2ffef4519951168",
-			type: {
-				mime: "image/jpeg",
-				ext: "jpg",
-			},
-			width: 512,
-			height: 512,
-			orientation: undefined,
-		});
-	}));
-
-	it("Generic APNG", async(async () => {
-		const path = `${_dirname}/resources/anime.png`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 1868,
-			md5: "08189c607bea3b952704676bb3c979e0",
-			type: {
-				mime: "image/apng",
-				ext: "apng",
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
-		});
-	}));
-
-	it("Generic AGIF", async(async () => {
-		const path = `${_dirname}/resources/anime.gif`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 2248,
-			md5: "32c47a11555675d9267aee1a86571e7e",
-			type: {
-				mime: "image/gif",
-				ext: "gif",
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
-		});
-	}));
-
-	it("PNG with alpha", async(async () => {
-		const path = `${_dirname}/resources/with-alpha.png`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 3772,
-			md5: "f73535c3e1e27508885b69b10cf6e991",
-			type: {
-				mime: "image/png",
-				ext: "png",
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
-		});
-	}));
-
-	it("Generic SVG", async(async () => {
-		const path = `${_dirname}/resources/image.svg`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 505,
-			md5: "b6f52b4b021e7b92cdd04509c7267965",
-			type: {
-				mime: "image/svg+xml",
-				ext: "svg",
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
-		});
-	}));
-
-	it("SVG with XML definition", async(async () => {
-		// https://github.com/misskey-dev/misskey/issues/4413
-		const path = `${_dirname}/resources/with-xml-def.svg`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 544,
-			md5: "4b7a346cde9ccbeb267e812567e33397",
-			type: {
-				mime: "image/svg+xml",
-				ext: "svg",
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
-		});
-	}));
-
-	it("Dimension limit", async(async () => {
-		const path = `${_dirname}/resources/25000x25000.png`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 75933,
-			md5: "268c5dde99e17cf8fe09f1ab3f97df56",
-			type: {
-				mime: "application/octet-stream", // do not treat as image
-				ext: null,
-			},
-			width: 25000,
-			height: 25000,
-			orientation: undefined,
-		});
-	}));
-
-	it("Rotate JPEG", async(async () => {
-		const path = `${_dirname}/resources/rotate.jpg`;
-		const info = (await getFileInfo(path, {
-			skipSensitiveDetection: true,
-		})) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 12624,
-			md5: "68d5b2d8d1d1acbbce99203e3ec3857e",
-			type: {
-				mime: "image/jpeg",
-				ext: "jpg",
-			},
-			width: 512,
-			height: 256,
-			orientation: 8,
-		});
-	}));
-});
diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js
deleted file mode 100644
index 7e1bf379dc..0000000000
--- a/packages/backend/test/loader.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * ts-node/esmローダーに投げる前にpath mappingを解決する
- * 参考
- * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115
- * - https://nodejs.org/api/esm.html#loaders
- * ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる
- */
-
-import { resolve as resolveTs, load } from "ts-node/esm";
-import { loadConfig, createMatchPath } from "tsconfig-paths";
-import { pathToFileURL } from "url";
-
-const tsconfig = loadConfig();
-const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
-
-export function resolve(specifier, ctx, defaultResolve) {
-	let resolvedSpecifier;
-	if (specifier.endsWith(".js")) {
-		// maybe transpiled
-		const specifierWithoutExtension = specifier.substring(
-			0,
-			specifier.length - ".js".length,
-		);
-		const matchedSpecifier = matchPath(specifierWithoutExtension);
-		if (matchedSpecifier) {
-			resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href;
-		}
-	} else {
-		const matchedSpecifier = matchPath(specifier);
-		if (matchedSpecifier) {
-			resolvedSpecifier = pathToFileURL(matchedSpecifier).href;
-		}
-	}
-	return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve);
-}
-
-export { load };
diff --git a/packages/backend/test/mfm.ts b/packages/backend/test/mfm.ts
deleted file mode 100644
index 215da00dea..0000000000
--- a/packages/backend/test/mfm.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-import * as assert from "node:assert";
-import * as mfm from "mfm-js";
-
-import { fromHtml } from "../src/mfm/from-html.js";
-import { toHtml } from "../src/mfm/to-html.js";
-
-describe("toHtml", () => {
-	it("br", () => {
-		const input = "foo\nbar\nbaz";
-		const output = "<p><span>foo<br>bar<br>baz</span></p>";
-		assert.equal(toHtml(mfm.parse(input)), output);
-	});
-
-	it("br alt", () => {
-		const input = "foo\r\nbar\rbaz";
-		const output = "<p><span>foo<br>bar<br>baz</span></p>";
-		assert.equal(toHtml(mfm.parse(input)), output);
-	});
-});
-
-describe("fromHtml", () => {
-	it("p", () => {
-		assert.deepStrictEqual(fromHtml("<p>a</p><p>b</p>"), "a\n\nb");
-	});
-
-	it("block element", () => {
-		assert.deepStrictEqual(fromHtml("<div>a</div><div>b</div>"), "a\nb");
-	});
-
-	it("inline element", () => {
-		assert.deepStrictEqual(fromHtml("<ul><li>a</li><li>b</li></ul>"), "a\nb");
-	});
-
-	it("block code", () => {
-		assert.deepStrictEqual(
-			fromHtml("<pre><code>a\nb</code></pre>"),
-			"```\na\nb\n```",
-		);
-	});
-
-	it("inline code", () => {
-		assert.deepStrictEqual(fromHtml("<code>a</code>"), "`a`");
-	});
-
-	it("quote", () => {
-		assert.deepStrictEqual(
-			fromHtml("<blockquote>a\nb</blockquote>"),
-			"> a\n> b",
-		);
-	});
-
-	it("br", () => {
-		assert.deepStrictEqual(fromHtml("<p>abc<br><br/>d</p>"), "abc\n\nd");
-	});
-
-	it("link with different text", () => {
-		assert.deepStrictEqual(
-			fromHtml('<p>a <a href="https://firefish.dev/firefish">c</a> d</p>'),
-			"a [c](https://firefish.dev/firefish) d",
-		);
-	});
-
-	it("link with different text, but not encoded", () => {
-		assert.deepStrictEqual(
-			fromHtml('<p>a <a href="https://firefish.dev/ä">c</a> d</p>'),
-			"a [c](<https://firefish.dev/ä>) d",
-		);
-	});
-
-	it("link with same text", () => {
-		assert.deepStrictEqual(
-			fromHtml(
-				'<p>a <a href="https://firefish.dev/firefish/firefish">https://firefish.dev/firefish/firefish</a> d</p>',
-			),
-			"a https://firefish.dev/firefish/firefish d",
-		);
-	});
-
-	it("link with same text, but not encoded", () => {
-		assert.deepStrictEqual(
-			fromHtml(
-				'<p>a <a href="https://firefish.dev/ä">https://firefish.dev/ä</a> d</p>',
-			),
-			"a <https://firefish.dev/ä> d",
-		);
-	});
-
-	it("link with no url", () => {
-		assert.deepStrictEqual(
-			fromHtml('<p>a <a href="b">c</a> d</p>'),
-			"a [c](b) d",
-		);
-	});
-
-	it("link without href", () => {
-		assert.deepStrictEqual(fromHtml("<p>a <a>c</a> d</p>"), "a c d");
-	});
-
-	it("link without text", () => {
-		assert.deepStrictEqual(
-			fromHtml('<p>a <a href="https://firefish.dev/b"></a> d</p>'),
-			"a https://firefish.dev/b d",
-		);
-	});
-
-	it("link without both", () => {
-		assert.deepStrictEqual(fromHtml("<p>a <a></a> d</p>"), "a  d");
-	});
-
-	it("mention", () => {
-		assert.deepStrictEqual(
-			fromHtml(
-				'<p>a <a href="https://info.firefish.dev/@firefish" class="u-url mention">@firefish</a> d</p>',
-			),
-			"a @firefish@info.firefish.dev d",
-		);
-	});
-
-	it("hashtag", () => {
-		assert.deepStrictEqual(
-			fromHtml('<p>a <a href="https://info.firefish.dev/tags/a">#a</a> d</p>', [
-				"#a",
-			]),
-			"a #a d",
-		);
-	});
-});
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
deleted file mode 100644
index 74c67e3d3f..0000000000
--- a/packages/backend/test/misc/mock-resolver.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import Resolver from "../../src/remote/activitypub/resolver.js";
-import { IObject } from "../../src/remote/activitypub/type.js";
-
-type MockResponse = {
-	type: string;
-	content: string;
-};
-
-export class MockResolver extends Resolver {
-	private _rs = new Map<string, MockResponse>();
-	public async _register(
-		uri: string,
-		content: string | Record<string, any>,
-		type = "application/activity+json",
-	) {
-		this._rs.set(uri, {
-			type,
-			content: typeof content === "string" ? content : JSON.stringify(content),
-		});
-	}
-
-	public async resolve(value: string | IObject): Promise<IObject> {
-		if (typeof value !== "string") return value;
-
-		const r = this._rs.get(value);
-
-		if (!r) {
-			throw {
-				name: "StatusError",
-				statusCode: 404,
-				message: "Not registed for mock",
-			};
-		}
-
-		const object = JSON.parse(r.content);
-
-		return object;
-	}
-}
diff --git a/packages/backend/test/mute.ts b/packages/backend/test/mute.ts
deleted file mode 100644
index 25556954ab..0000000000
--- a/packages/backend/test/mute.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	post,
-	react,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-	waitFire,
-} from "./utils.js";
-
-describe("Mute", () => {
-	let p: childProcess.ChildProcess;
-
-	// alice mutes carol
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-		carol = await signup({ username: "carol" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("ミュート作成", async(async () => {
-		const res = await request(
-			"/mute/create",
-			{
-				userId: carol.id,
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 204);
-	}));
-
-	it("「自分宛ての投稿」にミュートしているユーザーの投稿が含まれない", async(async () => {
-		const bobNote = await post(bob, { text: "@alice hi" });
-		const carolNote = await post(carol, { text: "@alice hi" });
-
-		const res = await request("/notes/mentions", {}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === bobNote.id),
-			true,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === carolNote.id),
-			false,
-		);
-	}));
-
-	it("ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない", async(async () => {
-		// 状態リセット
-		await request("/i/read-all-unread-notes", {}, alice);
-
-		await post(carol, { text: "@alice hi" });
-
-		const res = await request("/i", {}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(res.body.hasUnreadMentions, false);
-	}));
-
-	it("ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない", async () => {
-		// 状態リセット
-		await request("/i/read-all-unread-notes", {}, alice);
-
-		const fired = await waitFire(
-			alice,
-			"main",
-			() => post(carol, { text: "@alice hi" }),
-			(msg) => msg.type === "unreadMention",
-		);
-
-		assert.strictEqual(fired, false);
-	});
-
-	it("ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない", async () => {
-		// 状態リセット
-		await request("/i/read-all-unread-notes", {}, alice);
-		await request("/notifications/mark-all-as-read", {}, alice);
-
-		const fired = await waitFire(
-			alice,
-			"main",
-			() => post(carol, { text: "@alice hi" }),
-			(msg) => msg.type === "unreadNotification",
-		);
-
-		assert.strictEqual(fired, false);
-	});
-
-	describe("Timeline", () => {
-		it("タイムラインにミュートしているユーザーの投稿が含まれない", async(async () => {
-			const aliceNote = await post(alice);
-			const bobNote = await post(bob);
-			const carolNote = await post(carol);
-
-			const res = await request("/notes/local-timeline", {}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === aliceNote.id),
-				true,
-			);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === bobNote.id),
-				true,
-			);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === carolNote.id),
-				false,
-			);
-		}));
-
-		it("タイムラインにミュートしているユーザーの投稿のRenoteが含まれない", async(async () => {
-			const aliceNote = await post(alice);
-			const carolNote = await post(carol);
-			const bobNote = await post(bob, {
-				renoteId: carolNote.id,
-			});
-
-			const res = await request("/notes/local-timeline", {}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === aliceNote.id),
-				true,
-			);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === bobNote.id),
-				false,
-			);
-			assert.strictEqual(
-				res.body.some((note: any) => note.id === carolNote.id),
-				false,
-			);
-		}));
-	});
-
-	describe("Notification", () => {
-		it("通知にミュートしているユーザーの通知が含まれない(リアクション)", async(async () => {
-			const aliceNote = await post(alice);
-			await react(bob, aliceNote, "like");
-			await react(carol, aliceNote, "like");
-
-			const res = await request("/i/notifications", {}, alice);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(Array.isArray(res.body), true);
-			assert.strictEqual(
-				res.body.some((notification: any) => notification.userId === bob.id),
-				true,
-			);
-			assert.strictEqual(
-				res.body.some((notification: any) => notification.userId === carol.id),
-				false,
-			);
-		}));
-	});
-});
diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts
deleted file mode 100644
index a889b254b5..0000000000
--- a/packages/backend/test/note.ts
+++ /dev/null
@@ -1,517 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import { Note } from "../src/models/entities/note.js";
-import {
-	api,
-	async,
-	initTestDb,
-	post,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-	uploadUrl,
-} from "./utils.js";
-
-describe("Note", () => {
-	let p: childProcess.ChildProcess;
-	let Notes: any;
-
-	let alice: any;
-	let bob: any;
-
-	before(async () => {
-		p = await startServer();
-		const connection = await initTestDb(true);
-		Notes = connection.getRepository(Note);
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("投稿できる", async(async () => {
-		const post = {
-			text: "test",
-		};
-
-		const res = await request("/notes/create", post, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.text, post.text);
-	}));
-
-	it("ファイルを添付できる", async(async () => {
-		const file = await uploadUrl(
-			alice,
-			"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
-		);
-
-		const res = await request(
-			"/notes/create",
-			{
-				fileIds: [file.id],
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.deepStrictEqual(res.body.createdNote.fileIds, [file.id]);
-	}));
-
-	it("他人のファイルは無視", async(async () => {
-		const file = await uploadUrl(
-			bob,
-			"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
-		);
-
-		const res = await request(
-			"/notes/create",
-			{
-				text: "test",
-				fileIds: [file.id],
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.deepStrictEqual(res.body.createdNote.fileIds, []);
-	}));
-
-	it("存在しないファイルは無視", async(async () => {
-		const res = await request(
-			"/notes/create",
-			{
-				text: "test",
-				fileIds: ["000000000000000000000000"],
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.deepStrictEqual(res.body.createdNote.fileIds, []);
-	}));
-
-	it("不正なファイルIDは無視", async(async () => {
-		const res = await request(
-			"/notes/create",
-			{
-				fileIds: ["kyoppie"],
-			},
-			alice,
-		);
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.deepStrictEqual(res.body.createdNote.fileIds, []);
-	}));
-
-	it("返信できる", async(async () => {
-		const bobPost = await post(bob, {
-			text: "foo",
-		});
-
-		const alicePost = {
-			text: "bar",
-			replyId: bobPost.id,
-		};
-
-		const res = await request("/notes/create", alicePost, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.text, alicePost.text);
-		assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
-		assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
-	}));
-
-	it("renoteできる", async(async () => {
-		const bobPost = await post(bob, {
-			text: "test",
-		});
-
-		const alicePost = {
-			renoteId: bobPost.id,
-		};
-
-		const res = await request("/notes/create", alicePost, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
-		assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
-	}));
-
-	it("引用renoteできる", async(async () => {
-		const bobPost = await post(bob, {
-			text: "test",
-		});
-
-		const alicePost = {
-			text: "test",
-			renoteId: bobPost.id,
-		};
-
-		const res = await request("/notes/create", alicePost, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.text, alicePost.text);
-		assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
-		assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
-	}));
-
-	it("文字数ぎりぎりで怒られない", async(async () => {
-		const post = {
-			text: "!".repeat(3000),
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 200);
-	}));
-
-	it("文字数オーバーで怒られる", async(async () => {
-		const post = {
-			text: "!".repeat(3001),
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 400);
-	}));
-
-	it("存在しないリプライ先で怒られる", async(async () => {
-		const post = {
-			text: "test",
-			replyId: "000000000000000000000000",
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 400);
-	}));
-
-	it("存在しないrenote対象で怒られる", async(async () => {
-		const post = {
-			renoteId: "000000000000000000000000",
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 400);
-	}));
-
-	it("不正なリプライ先IDで怒られる", async(async () => {
-		const post = {
-			text: "test",
-			replyId: "foo",
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 400);
-	}));
-
-	it("不正なrenote対象IDで怒られる", async(async () => {
-		const post = {
-			renoteId: "foo",
-		};
-		const res = await request("/notes/create", post, alice);
-		assert.strictEqual(res.status, 400);
-	}));
-
-	it("存在しないユーザーにメンションできる", async(async () => {
-		const post = {
-			text: "@ghost yo",
-		};
-
-		const res = await request("/notes/create", post, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.text, post.text);
-	}));
-
-	it("同じユーザーに複数メンションしても内部的にまとめられる", async(async () => {
-		const post = {
-			text: "@bob @bob @bob yo",
-		};
-
-		const res = await request("/notes/create", post, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(
-			typeof res.body === "object" && !Array.isArray(res.body),
-			true,
-		);
-		assert.strictEqual(res.body.createdNote.text, post.text);
-
-		const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
-		assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
-	}));
-
-	describe("notes/create", () => {
-		it("投票を添付できる", async(async () => {
-			const res = await request(
-				"/notes/create",
-				{
-					text: "test",
-					poll: {
-						choices: ["foo", "bar"],
-					},
-				},
-				alice,
-			);
-
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(
-				typeof res.body === "object" && !Array.isArray(res.body),
-				true,
-			);
-			assert.strictEqual(res.body.createdNote.poll != null, true);
-		}));
-
-		it("投票の選択肢が無くて怒られる", async(async () => {
-			const res = await request(
-				"/notes/create",
-				{
-					poll: {},
-				},
-				alice,
-			);
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("投票の選択肢が無くて怒られる (空の配列)", async(async () => {
-			const res = await request(
-				"/notes/create",
-				{
-					poll: {
-						choices: [],
-					},
-				},
-				alice,
-			);
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("投票の選択肢が1つで怒られる", async(async () => {
-			const res = await request(
-				"/notes/create",
-				{
-					poll: {
-						choices: ["Strawberry Pasta"],
-					},
-				},
-				alice,
-			);
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("投票できる", async(async () => {
-			const { body } = await request(
-				"/notes/create",
-				{
-					text: "test",
-					poll: {
-						choices: ["sakura", "izumi", "ako"],
-					},
-				},
-				alice,
-			);
-
-			const res = await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 1,
-				},
-				alice,
-			);
-
-			assert.strictEqual(res.status, 204);
-		}));
-
-		it("複数投票できない", async(async () => {
-			const { body } = await request(
-				"/notes/create",
-				{
-					text: "test",
-					poll: {
-						choices: ["sakura", "izumi", "ako"],
-					},
-				},
-				alice,
-			);
-
-			await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 0,
-				},
-				alice,
-			);
-
-			const res = await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 2,
-				},
-				alice,
-			);
-
-			assert.strictEqual(res.status, 400);
-		}));
-
-		it("許可されている場合は複数投票できる", async(async () => {
-			const { body } = await request(
-				"/notes/create",
-				{
-					text: "test",
-					poll: {
-						choices: ["sakura", "izumi", "ako"],
-						multiple: true,
-					},
-				},
-				alice,
-			);
-
-			await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 0,
-				},
-				alice,
-			);
-
-			await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 1,
-				},
-				alice,
-			);
-
-			const res = await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 2,
-				},
-				alice,
-			);
-
-			assert.strictEqual(res.status, 204);
-		}));
-
-		it("締め切られている場合は投票できない", async(async () => {
-			const { body } = await request(
-				"/notes/create",
-				{
-					text: "test",
-					poll: {
-						choices: ["sakura", "izumi", "ako"],
-						expiredAfter: 1,
-					},
-				},
-				alice,
-			);
-
-			await new Promise((x) => setTimeout(x, 2));
-
-			const res = await request(
-				"/notes/polls/vote",
-				{
-					noteId: body.createdNote.id,
-					choice: 1,
-				},
-				alice,
-			);
-
-			assert.strictEqual(res.status, 400);
-		}));
-	});
-
-	describe("notes/delete", () => {
-		it("delete a reply", async(async () => {
-			const mainNoteRes = await api(
-				"notes/create",
-				{
-					text: "main post",
-				},
-				alice,
-			);
-			const replyOneRes = await api(
-				"notes/create",
-				{
-					text: "reply one",
-					replyId: mainNoteRes.body.createdNote.id,
-				},
-				alice,
-			);
-			const replyTwoRes = await api(
-				"notes/create",
-				{
-					text: "reply two",
-					replyId: mainNoteRes.body.createdNote.id,
-				},
-				alice,
-			);
-
-			const deleteOneRes = await api(
-				"notes/delete",
-				{
-					noteId: replyOneRes.body.createdNote.id,
-				},
-				alice,
-			);
-
-			assert.strictEqual(deleteOneRes.status, 204);
-			let mainNote = await Notes.findOneBy({
-				id: mainNoteRes.body.createdNote.id,
-			});
-			assert.strictEqual(mainNote.repliesCount, 1);
-
-			const deleteTwoRes = await api(
-				"notes/delete",
-				{
-					noteId: replyTwoRes.body.createdNote.id,
-				},
-				alice,
-			);
-
-			assert.strictEqual(deleteTwoRes.status, 204);
-			mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
-			assert.strictEqual(mainNote.repliesCount, 0);
-		}));
-	});
-});
diff --git a/packages/backend/test/prelude/maybe.ts b/packages/backend/test/prelude/maybe.ts
deleted file mode 100644
index b88431450a..0000000000
--- a/packages/backend/test/prelude/maybe.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import * as assert from "node:assert";
-import { just, nothing } from "../../src/prelude/maybe.js";
-
-describe("just", () => {
-	it("has a value", () => {
-		assert.deepStrictEqual(just(3).isJust(), true);
-	});
-
-	it("has the inverse called get", () => {
-		assert.deepStrictEqual(just(3).get(), 3);
-	});
-});
-
-describe("nothing", () => {
-	it("has no value", () => {
-		assert.deepStrictEqual(nothing().isJust(), false);
-	});
-});
diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.ts
deleted file mode 100644
index 684776f52a..0000000000
--- a/packages/backend/test/prelude/url.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as assert from "node:assert";
-import { query } from "../../src/prelude/url.js";
-
-describe("url", () => {
-	it("query", () => {
-		const s = query({
-			foo: "ふぅ",
-			bar: "b a r",
-			baz: undefined,
-		});
-		assert.deepStrictEqual(s, "foo=%E3%81%B5%E3%81%85&bar=b%20a%20r");
-	});
-});
diff --git a/packages/backend/test/resources/25000x25000.png b/packages/backend/test/resources/25000x25000.png
deleted file mode 100644
index 0ed4666925..0000000000
Binary files a/packages/backend/test/resources/25000x25000.png and /dev/null differ
diff --git a/packages/backend/test/resources/Lenna.jpg b/packages/backend/test/resources/Lenna.jpg
deleted file mode 100644
index 6b5b32281c..0000000000
Binary files a/packages/backend/test/resources/Lenna.jpg and /dev/null differ
diff --git a/packages/backend/test/resources/Lenna.png b/packages/backend/test/resources/Lenna.png
deleted file mode 100644
index 59ef68aabd..0000000000
Binary files a/packages/backend/test/resources/Lenna.png and /dev/null differ
diff --git a/packages/backend/test/resources/anime.gif b/packages/backend/test/resources/anime.gif
deleted file mode 100644
index 256ba495ce..0000000000
Binary files a/packages/backend/test/resources/anime.gif and /dev/null differ
diff --git a/packages/backend/test/resources/anime.png b/packages/backend/test/resources/anime.png
deleted file mode 100644
index f13600f7a4..0000000000
Binary files a/packages/backend/test/resources/anime.png and /dev/null differ
diff --git a/packages/backend/test/resources/emptyfile b/packages/backend/test/resources/emptyfile
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/packages/backend/test/resources/image.svg b/packages/backend/test/resources/image.svg
deleted file mode 100644
index 983f0ae648..0000000000
--- a/packages/backend/test/resources/image.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#FF40A4" d="M128 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C164 84 144 76 128 80"/><path fill="#FFBF40" d="M192 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C228 84 208 76 192 80"/><path fill="#408EFF" d="M64 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8C28 172 48 180 64 176s20-24 20-48v-16c0-8 8-16 20.3-8C100 84 80 76 64 80"/></svg>
\ No newline at end of file
diff --git a/packages/backend/test/resources/rotate.jpg b/packages/backend/test/resources/rotate.jpg
deleted file mode 100644
index 477c2baf5b..0000000000
Binary files a/packages/backend/test/resources/rotate.jpg and /dev/null differ
diff --git a/packages/backend/test/resources/with-alpha.png b/packages/backend/test/resources/with-alpha.png
deleted file mode 100644
index adc8d01805..0000000000
Binary files a/packages/backend/test/resources/with-alpha.png and /dev/null differ
diff --git a/packages/backend/test/resources/with-xml-def.svg b/packages/backend/test/resources/with-xml-def.svg
deleted file mode 100644
index 983f0ae648..0000000000
--- a/packages/backend/test/resources/with-xml-def.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#FF40A4" d="M128 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C164 84 144 76 128 80"/><path fill="#FFBF40" d="M192 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C228 84 208 76 192 80"/><path fill="#408EFF" d="M64 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8C28 172 48 180 64 176s20-24 20-48v-16c0-8 8-16 20.3-8C100 84 80 76 64 80"/></svg>
\ No newline at end of file
diff --git a/packages/backend/test/streaming.ts b/packages/backend/test/streaming.ts
deleted file mode 100644
index 6a026b6b72..0000000000
--- a/packages/backend/test/streaming.ts
+++ /dev/null
@@ -1,766 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import { Following } from "../src/models/entities/following.js";
-import {
-	api,
-	connectStream,
-	initTestDb,
-	post,
-	shutdownServer,
-	signup,
-	startServer,
-	waitFire,
-} from "./utils.js";
-
-describe("Streaming", () => {
-	let p: childProcess.ChildProcess;
-	let Followings: any;
-
-	const follow = async (follower: any, followee: any) => {
-		await Followings.save({
-			id: "a",
-			createdAt: new Date(),
-			followerId: follower.id,
-			followeeId: followee.id,
-			followerHost: follower.host,
-			followerInbox: null,
-			followerSharedInbox: null,
-			followeeHost: followee.host,
-			followeeInbox: null,
-			followeeSharedInbox: null,
-		});
-	};
-
-	describe("Streaming", () => {
-		// Local users
-		let ayano: any;
-		let kyoko: any;
-		let chitose: any;
-
-		// Remote users
-		let akari: any;
-		let chinatsu: any;
-
-		let kyokoNote: any;
-		let list: any;
-
-		before(async () => {
-			p = await startServer();
-			const connection = await initTestDb(true);
-			Followings = connection.getRepository(Following);
-
-			ayano = await signup({ username: "ayano" });
-			kyoko = await signup({ username: "kyoko" });
-			chitose = await signup({ username: "chitose" });
-
-			akari = await signup({ username: "akari", host: "example.com" });
-			chinatsu = await signup({ username: "chinatsu", host: "example.com" });
-
-			kyokoNote = await post(kyoko, { text: "foo" });
-
-			// Follow: ayano => kyoko
-			await api("following/create", { userId: kyoko.id }, ayano);
-
-			// Follow: ayano => akari
-			await follow(ayano, akari);
-
-			// List: chitose => ayano, kyoko
-			list = await api(
-				"users/lists/create",
-				{
-					name: "my list",
-				},
-				chitose,
-			).then((x) => x.body);
-
-			await api(
-				"users/lists/push",
-				{
-					listId: list.id,
-					userId: ayano.id,
-				},
-				chitose,
-			);
-
-			await api(
-				"users/lists/push",
-				{
-					listId: list.id,
-					userId: kyoko.id,
-				},
-				chitose,
-			);
-		});
-
-		after(async () => {
-			await shutdownServer(p);
-		});
-
-		describe("Events", () => {
-			it("mention event", async () => {
-				const fired = await waitFire(
-					kyoko,
-					"main", // kyoko:main
-					() => post(ayano, { text: "foo @kyoko bar" }), // ayano mention => kyoko
-					(msg) => msg.type === "mention" && msg.body.userId === ayano.id, // wait ayano
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("renote event", async () => {
-				const fired = await waitFire(
-					kyoko,
-					"main", // kyoko:main
-					() => post(ayano, { renoteId: kyokoNote.id }), // ayano renote
-					(msg) => msg.type === "renote" && msg.body.renoteId === kyokoNote.id, // wait renote
-				);
-
-				assert.strictEqual(fired, true);
-			});
-		});
-
-		describe("Home Timeline", () => {
-			it("自分の投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"homeTimeline", // ayano:Home
-					() => api("notes/create", { text: "foo" }, ayano), // ayano posts
-					(msg) => msg.type === "note" && msg.body.text === "foo",
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしているユーザーの投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"homeTimeline", // ayano:home
-					() => api("notes/create", { text: "foo" }, kyoko), // kyoko posts
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					kyoko,
-					"homeTimeline", // kyoko:home
-					() => api("notes/create", { text: "foo" }, ayano), // ayano posts
-					(msg) => msg.type === "note" && msg.body.userId === ayano.id, // wait ayano
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしているユーザーのダイレクト投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"homeTimeline", // ayano:home
-					() =>
-						api(
-							"notes/create",
-							{
-								text: "foo",
-								visibility: "specified",
-								visibleUserIds: [ayano.id],
-							},
-							kyoko,
-						), // kyoko dm => ayano
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしているユーザーでも自分が指定されていないダイレクト投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"homeTimeline", // ayano:home
-					() =>
-						api(
-							"notes/create",
-							{
-								text: "foo",
-								visibility: "specified",
-								visibleUserIds: [chitose.id],
-							},
-							kyoko,
-						), // kyoko dm => chitose
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-		}); // Home
-
-		describe("Local Timeline", () => {
-			it("自分の投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, ayano), // ayano posts
-					(msg) => msg.type === "note" && msg.body.text === "foo",
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないローカルユーザーの投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, chitose), // chitose posts
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("リモートユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
-					(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしてたとしてもリモートユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, akari), // akari posts
-					(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("ホーム指定の投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko home posts
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしているローカルユーザーのダイレクト投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() =>
-						api(
-							"notes/create",
-							{
-								text: "foo",
-								visibility: "specified",
-								visibleUserIds: [ayano.id],
-							},
-							kyoko,
-						), // kyoko DM => ayano
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"localTimeline", // ayano:Local
-					() =>
-						api(
-							"notes/create",
-							{ text: "foo", visibility: "followers" },
-							chitose,
-						),
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, false);
-			});
-		});
-
-		describe("Recommended Timeline", () => {
-			it("自分の投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, ayano), // ayano posts
-					(msg) => msg.type === "note" && msg.body.text === "foo",
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないローカルユーザーの投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, chitose), // chitose posts
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("リモートユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
-					(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしてたとしてもリモートユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo" }, akari), // akari posts
-					(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("ホーム指定の投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko home posts
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしているローカルユーザーのダイレクト投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() =>
-						api(
-							"notes/create",
-							{
-								text: "foo",
-								visibility: "specified",
-								visibleUserIds: [ayano.id],
-							},
-							kyoko,
-						), // kyoko DM => ayano
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"recommendedTimeline", // ayano:Local
-					() =>
-						api(
-							"notes/create",
-							{ text: "foo", visibility: "followers" },
-							chitose,
-						),
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, false);
-			});
-		});
-
-		describe("Hybrid Timeline", () => {
-			it("自分の投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() => api("notes/create", { text: "foo" }, ayano), // ayano posts
-					(msg) => msg.type === "note" && msg.body.text === "foo",
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないローカルユーザーの投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() => api("notes/create", { text: "foo" }, chitose), // chitose posts
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしているリモートユーザーの投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() => api("notes/create", { text: "foo" }, akari), // akari posts
-					(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないリモートユーザーの投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
-					(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしているユーザーのダイレクト投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() =>
-						api(
-							"notes/create",
-							{
-								text: "foo",
-								visibility: "specified",
-								visibleUserIds: [ayano.id],
-							},
-							kyoko,
-						),
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしているユーザーのホーム投稿が流れる", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() => api("notes/create", { text: "foo", visibility: "home" }, kyoko),
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないローカルユーザーのホーム投稿は流れない", async () => {
-				const fired = await waitFire(
-					ayano,
-					"hybridTimeline", // ayano:Hybrid
-					() =>
-						api("notes/create", { text: "foo", visibility: "home" }, chitose),
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id,
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", () =>
-				async () => {
-					const fired = await waitFire(
-						ayano,
-						"hybridTimeline", // ayano:Hybrid
-						() =>
-							api(
-								"notes/create",
-								{ text: "foo", visibility: "followers" },
-								chitose,
-							),
-						(msg) => msg.type === "note" && msg.body.userId === chitose.id,
-					);
-
-					assert.strictEqual(fired, false);
-				});
-		});
-
-		describe("Global Timeline", () => {
-			it("フォローしていないローカルユーザーの投稿が流れる", () => async () => {
-				const fired = await waitFire(
-					ayano,
-					"globalTimeline", // ayano:Global
-					() => api("notes/create", { text: "foo" }, chitose), // chitose posts
-					(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("フォローしていないリモートユーザーの投稿が流れる", () => async () => {
-				const fired = await waitFire(
-					ayano,
-					"globalTimeline", // ayano:Global
-					() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
-					(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("ホーム投稿は流れない", () => async () => {
-				const fired = await waitFire(
-					ayano,
-					"globalTimeline", // ayano:Global
-					() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko posts
-					(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
-				);
-
-				assert.strictEqual(fired, false);
-			});
-		});
-
-		describe("UserList Timeline", () => {
-			it("リストに入れているユーザーの投稿が流れる", () => async () => {
-				const fired = await waitFire(
-					chitose,
-					"userList",
-					() => api("notes/create", { text: "foo" }, ayano),
-					(msg) => msg.type === "note" && msg.body.userId === ayano.id,
-					{ listId: list.id },
-				);
-
-				assert.strictEqual(fired, true);
-			});
-
-			it("リストに入れていないユーザーの投稿は流れない", () => async () => {
-				const fired = await waitFire(
-					chitose,
-					"userList",
-					() => api("notes/create", { text: "foo" }, chinatsu),
-					(msg) => msg.type === "note" && msg.body.userId === chinatsu.id,
-					{ listId: list.id },
-				);
-
-				assert.strictEqual(fired, false);
-			});
-
-			// #4471
-			it("リストに入れているユーザーのダイレクト投稿が流れる", () =>
-				async () => {
-					const fired = await waitFire(
-						chitose,
-						"userList",
-						() =>
-							api(
-								"notes/create",
-								{
-									text: "foo",
-									visibility: "specified",
-									visibleUserIds: [chitose.id],
-								},
-								ayano,
-							),
-						(msg) => msg.type === "note" && msg.body.userId === ayano.id,
-						{ listId: list.id },
-					);
-
-					assert.strictEqual(fired, true);
-				});
-
-			// #4335
-			it("リストに入れているがフォローはしてないユーザーのフォロワー宛て投稿は流れない", () =>
-				async () => {
-					const fired = await waitFire(
-						chitose,
-						"userList",
-						() =>
-							api(
-								"notes/create",
-								{ text: "foo", visibility: "followers" },
-								kyoko,
-							),
-						(msg) => msg.type === "note" && msg.body.userId === kyoko.id,
-						{ listId: list.id },
-					);
-
-					assert.strictEqual(fired, false);
-				});
-		});
-
-		describe("Hashtag Timeline", () => {
-			it("指定したハッシュタグの投稿が流れる", () =>
-				new Promise<void>(async (done) => {
-					const ws = await connectStream(
-						chitose,
-						"hashtag",
-						({ type, body }) => {
-							if (type == "note") {
-								assert.deepStrictEqual(body.text, "#foo");
-								ws.close();
-								done();
-							}
-						},
-						{
-							q: [["foo"]],
-						},
-					);
-
-					post(chitose, {
-						text: "#foo",
-					});
-				}));
-
-			it("指定したハッシュタグの投稿が流れる (AND)", () =>
-				new Promise<void>(async (done) => {
-					let fooCount = 0;
-					let barCount = 0;
-					let fooBarCount = 0;
-
-					const ws = await connectStream(
-						chitose,
-						"hashtag",
-						({ type, body }) => {
-							if (type == "note") {
-								if (body.text === "#foo") fooCount++;
-								if (body.text === "#bar") barCount++;
-								if (body.text === "#foo #bar") fooBarCount++;
-							}
-						},
-						{
-							q: [["foo", "bar"]],
-						},
-					);
-
-					post(chitose, {
-						text: "#foo",
-					});
-
-					post(chitose, {
-						text: "#bar",
-					});
-
-					post(chitose, {
-						text: "#foo #bar",
-					});
-
-					setTimeout(() => {
-						assert.strictEqual(fooCount, 0);
-						assert.strictEqual(barCount, 0);
-						assert.strictEqual(fooBarCount, 1);
-						ws.close();
-						done();
-					}, 3000);
-				}));
-
-			it("指定したハッシュタグの投稿が流れる (OR)", () =>
-				new Promise<void>(async (done) => {
-					let fooCount = 0;
-					let barCount = 0;
-					let fooBarCount = 0;
-					let piyoCount = 0;
-
-					const ws = await connectStream(
-						chitose,
-						"hashtag",
-						({ type, body }) => {
-							if (type == "note") {
-								if (body.text === "#foo") fooCount++;
-								if (body.text === "#bar") barCount++;
-								if (body.text === "#foo #bar") fooBarCount++;
-								if (body.text === "#piyo") piyoCount++;
-							}
-						},
-						{
-							q: [["foo"], ["bar"]],
-						},
-					);
-
-					post(chitose, {
-						text: "#foo",
-					});
-
-					post(chitose, {
-						text: "#bar",
-					});
-
-					post(chitose, {
-						text: "#foo #bar",
-					});
-
-					post(chitose, {
-						text: "#piyo",
-					});
-
-					setTimeout(() => {
-						assert.strictEqual(fooCount, 1);
-						assert.strictEqual(barCount, 1);
-						assert.strictEqual(fooBarCount, 1);
-						assert.strictEqual(piyoCount, 0);
-						ws.close();
-						done();
-					}, 3000);
-				}));
-
-			it("指定したハッシュタグの投稿が流れる (AND + OR)", () =>
-				new Promise<void>(async (done) => {
-					let fooCount = 0;
-					let barCount = 0;
-					let fooBarCount = 0;
-					let piyoCount = 0;
-					let waaaCount = 0;
-
-					const ws = await connectStream(
-						chitose,
-						"hashtag",
-						({ type, body }) => {
-							if (type == "note") {
-								if (body.text === "#foo") fooCount++;
-								if (body.text === "#bar") barCount++;
-								if (body.text === "#foo #bar") fooBarCount++;
-								if (body.text === "#piyo") piyoCount++;
-								if (body.text === "#waaa") waaaCount++;
-							}
-						},
-						{
-							q: [["foo", "bar"], ["piyo"]],
-						},
-					);
-
-					post(chitose, {
-						text: "#foo",
-					});
-
-					post(chitose, {
-						text: "#bar",
-					});
-
-					post(chitose, {
-						text: "#foo #bar",
-					});
-
-					post(chitose, {
-						text: "#piyo",
-					});
-
-					post(chitose, {
-						text: "#waaa",
-					});
-
-					setTimeout(() => {
-						assert.strictEqual(fooCount, 0);
-						assert.strictEqual(barCount, 0);
-						assert.strictEqual(fooBarCount, 1);
-						assert.strictEqual(piyoCount, 1);
-						assert.strictEqual(waaaCount, 0);
-						ws.close();
-						done();
-					}, 3000);
-				}));
-		});
-	});
-});
diff --git a/packages/backend/test/thread-mute.ts b/packages/backend/test/thread-mute.ts
deleted file mode 100644
index 08a32eaf4a..0000000000
--- a/packages/backend/test/thread-mute.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	connectStream,
-	post,
-	react,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-} from "./utils.js";
-
-describe("Note thread mute", () => {
-	let p: childProcess.ChildProcess;
-
-	let alice: any;
-	let bob: any;
-	let carol: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		bob = await signup({ username: "bob" });
-		carol = await signup({ username: "carol" });
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("notes/mentions にミュートしているスレッドの投稿が含まれない", async(async () => {
-		const bobNote = await post(bob, { text: "@alice @carol root note" });
-		const aliceReply = await post(alice, {
-			replyId: bobNote.id,
-			text: "@bob @carol child note",
-		});
-
-		await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
-
-		const carolReply = await post(carol, {
-			replyId: bobNote.id,
-			text: "@bob @alice child note",
-		});
-		const carolReplyWithoutMention = await post(carol, {
-			replyId: aliceReply.id,
-			text: "child note",
-		});
-
-		const res = await request("/notes/mentions", {}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === bobNote.id),
-			false,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === carolReply.id),
-			false,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === carolReplyWithoutMention.id),
-			false,
-		);
-	}));
-
-	it("ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない", async(async () => {
-		// 状態リセット
-		await request("/i/read-all-unread-notes", {}, alice);
-
-		const bobNote = await post(bob, { text: "@alice @carol root note" });
-
-		await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
-
-		const carolReply = await post(carol, {
-			replyId: bobNote.id,
-			text: "@bob @alice child note",
-		});
-
-		const res = await request("/i", {}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(res.body.hasUnreadMentions, false);
-	}));
-
-	it("ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない", () =>
-		new Promise(async (done) => {
-			// 状態リセット
-			await request("/i/read-all-unread-notes", {}, alice);
-
-			const bobNote = await post(bob, { text: "@alice @carol root note" });
-
-			await request(
-				"/notes/thread-muting/create",
-				{ noteId: bobNote.id },
-				alice,
-			);
-
-			let fired = false;
-
-			const ws = await connectStream(alice, "main", async ({ type, body }) => {
-				if (type === "unreadMention") {
-					if (body === bobNote.id) return;
-					fired = true;
-				}
-			});
-
-			const carolReply = await post(carol, {
-				replyId: bobNote.id,
-				text: "@bob @alice child note",
-			});
-
-			setTimeout(() => {
-				assert.strictEqual(fired, false);
-				ws.close();
-				done();
-			}, 5000);
-		}));
-
-	it("i/notifications にミュートしているスレッドの通知が含まれない", async(async () => {
-		const bobNote = await post(bob, { text: "@alice @carol root note" });
-		const aliceReply = await post(alice, {
-			replyId: bobNote.id,
-			text: "@bob @carol child note",
-		});
-
-		await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
-
-		const carolReply = await post(carol, {
-			replyId: bobNote.id,
-			text: "@bob @alice child note",
-		});
-		const carolReplyWithoutMention = await post(carol, {
-			replyId: aliceReply.id,
-			text: "child note",
-		});
-
-		const res = await request("/i/notifications", {}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(
-			res.body.some(
-				(notification: any) => notification.note.id === carolReply.id,
-			),
-			false,
-		);
-		assert.strictEqual(
-			res.body.some(
-				(notification: any) =>
-					notification.note.id === carolReplyWithoutMention.id,
-			),
-			false,
-		);
-
-		// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
-	}));
-});
diff --git a/packages/backend/test/tsconfig.json b/packages/backend/test/tsconfig.json
deleted file mode 100644
index 5fafa1005f..0000000000
--- a/packages/backend/test/tsconfig.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-	"compilerOptions": {
-		"allowJs": true,
-		"noEmitOnError": false,
-		"noImplicitAny": true,
-		"noImplicitReturns": true,
-		"noUnusedParameters": false,
-		"noUnusedLocals": true,
-		"noFallthroughCasesInSwitch": true,
-		"declaration": false,
-		"sourceMap": true,
-		"target": "es2021",
-		"module": "es2020",
-		"moduleResolution": "node",
-		"allowSyntheticDefaultImports": true,
-		"removeComments": false,
-		"noLib": false,
-		"strict": true,
-		"strictNullChecks": true,
-		"strictPropertyInitialization": false,
-		"experimentalDecorators": true,
-		"emitDecoratorMetadata": true,
-		"resolveJsonModule": true,
-		"isolatedModules": true,
-		"baseUrl": "./",
-		"paths": {
-			"@/*": ["../src/*"]
-		},
-		"typeRoots": ["../node_modules/@types", "../src/@types"],
-		"lib": ["esnext"]
-	},
-	"compileOnSave": false,
-	"include": ["./**/*.ts"]
-}
diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.ts
deleted file mode 100644
index 9b68561b4f..0000000000
--- a/packages/backend/test/user-notes.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-process.env.NODE_ENV = "test";
-
-import * as assert from "node:assert";
-import type * as childProcess from "node:child_process";
-import {
-	async,
-	post,
-	request,
-	shutdownServer,
-	signup,
-	startServer,
-	uploadUrl,
-} from "./utils.js";
-
-describe("users/notes", () => {
-	let p: childProcess.ChildProcess;
-
-	let alice: any;
-	let jpgNote: any;
-	let pngNote: any;
-	let jpgPngNote: any;
-
-	before(async () => {
-		p = await startServer();
-		alice = await signup({ username: "alice" });
-		const jpg = await uploadUrl(
-			alice,
-			"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
-		);
-		const png = await uploadUrl(
-			alice,
-			"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png",
-		);
-		jpgNote = await post(alice, {
-			fileIds: [jpg.id],
-		});
-		pngNote = await post(alice, {
-			fileIds: [png.id],
-		});
-		jpgPngNote = await post(alice, {
-			fileIds: [jpg.id, png.id],
-		});
-	});
-
-	after(async () => {
-		await shutdownServer(p);
-	});
-
-	it("ファイルタイプ指定 (jpg)", async(async () => {
-		const res = await request(
-			"/users/notes",
-			{
-				userId: alice.id,
-				fileType: ["image/jpeg"],
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(res.body.length, 2);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === jpgNote.id),
-			true,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === jpgPngNote.id),
-			true,
-		);
-	}));
-
-	it("ファイルタイプ指定 (jpg or png)", async(async () => {
-		const res = await request(
-			"/users/notes",
-			{
-				userId: alice.id,
-				fileType: ["image/jpeg", "image/png"],
-			},
-			alice,
-		);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(res.body.length, 3);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === jpgNote.id),
-			true,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === pngNote.id),
-			true,
-		);
-		assert.strictEqual(
-			res.body.some((note: any) => note.id === jpgPngNote.id),
-			true,
-		);
-	}));
-});
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
deleted file mode 100644
index a755fc9b96..0000000000
--- a/packages/backend/test/utils.ts
+++ /dev/null
@@ -1,403 +0,0 @@
-import { SIGKILL } from "constants";
-import * as childProcess from "node:child_process";
-import * as fs from "node:fs";
-import * as http from "node:http";
-import * as path from "node:path";
-import { dirname } from "node:path";
-import { fileURLToPath } from "node:url";
-import type { Entities, endpoints } from "firefish-js";
-import FormData from "form-data";
-import got from "got";
-import fetch from "node-fetch";
-import { DataSource } from "typeorm";
-import WebSocket from "ws";
-import loadConfig from "../src/config/load.js";
-import { entities } from "../src/db/postgre.js";
-
-const _filename = fileURLToPath(import.meta.url);
-const _dirname = dirname(_filename);
-
-const config = loadConfig();
-export const port = config.port;
-
-export const async = (fn: Function) => (done: Function) => {
-	fn().then(
-		() => {
-			done();
-		},
-		(err: Error) => {
-			done(err);
-		},
-	);
-};
-
-export const api = async (endpoint: string, params: any, me?: any) => {
-	endpoint = endpoint.replace(/^\//, "");
-
-	const auth = me
-		? {
-				i: me.token,
-			}
-		: {};
-
-	const res = await got<string>(`http://localhost:${port}/api/${endpoint}`, {
-		method: "POST",
-		headers: {
-			"Content-Type": "application/json",
-		},
-		body: JSON.stringify(Object.assign(auth, params)),
-		retry: {
-			limit: 0,
-		},
-		hooks: {
-			beforeError: [
-				(error) => {
-					const { response } = error;
-					if (response && response.body) console.warn(response.body);
-					return error;
-				},
-			],
-		},
-	});
-
-	const status = res.statusCode;
-	const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
-
-	return {
-		status,
-		body,
-	};
-};
-
-export const request = async (
-	endpoint: string,
-	params: any,
-	me?: any,
-): Promise<{ body: any; status: number }> => {
-	const auth = me
-		? {
-				i: me.token,
-			}
-		: {};
-
-	const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
-		method: "POST",
-		headers: {
-			"Content-Type": "application/json",
-		},
-		body: JSON.stringify(Object.assign(auth, params)),
-	});
-
-	const status = res.status;
-	const body = res.status !== 204 ? await res.json().catch() : null;
-
-	return {
-		body,
-		status,
-	};
-};
-
-export const signup = async (params?: any): Promise<any> => {
-	const q = Object.assign(
-		{
-			username: "test",
-			password: "test",
-		},
-		params,
-	);
-
-	const res = await api("signup", q);
-
-	return res.body;
-};
-
-export const post = async (
-	user: any,
-	params?: Endpoints["notes/create"]["req"],
-): Promise<entities.Note> => {
-	const q = Object.assign(
-		{
-			text: "test",
-		},
-		params,
-	);
-
-	const res = await api("notes/create", q, user);
-
-	return res.body ? res.body.createdNote : null;
-};
-
-export const react = async (
-	user: any,
-	note: any,
-	reaction: string,
-): Promise<any> => {
-	await api(
-		"notes/reactions/create",
-		{
-			noteId: note.id,
-			reaction: reaction,
-		},
-		user,
-	);
-};
-
-/**
- * Upload file
- * @param user User
- * @param _path Optional, absolute path or relative from ./resources/
- */
-export const uploadFile = async (user: any, _path?: string): Promise<any> => {
-	const absPath =
-		_path == null
-			? `${_dirname}/resources/Lenna.jpg`
-			: path.isAbsolute(_path)
-				? _path
-				: `${_dirname}/resources/${_path}`;
-
-	const formData = new FormData() as any;
-	formData.append("i", user.token);
-	formData.append("file", fs.createReadStream(absPath));
-	formData.append("force", "true");
-
-	const res = await got<string>(
-		`http://localhost:${port}/api/drive/files/create`,
-		{
-			method: "POST",
-			body: formData,
-			retry: {
-				limit: 0,
-			},
-		},
-	);
-
-	const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
-
-	return body;
-};
-
-export const uploadUrl = async (user: any, url: string) => {
-	let file: any;
-
-	const ws = await connectStream(user, "main", (msg) => {
-		if (msg.type === "driveFileCreated") {
-			file = msg.body;
-		}
-	});
-
-	await api(
-		"drive/files/upload-from-url",
-		{
-			url,
-			force: true,
-		},
-		user,
-	);
-
-	await sleep(5000);
-	ws.close();
-
-	return file;
-};
-
-export function connectStream(
-	user: any,
-	channel: string,
-	listener: (message: Record<string, any>) => any,
-	params?: any,
-): Promise<WebSocket> {
-	return new Promise((res, rej) => {
-		const ws = new WebSocket(
-			`ws://localhost:${port}/streaming?i=${user.token}`,
-		);
-
-		ws.on("open", () => {
-			ws.on("message", (data) => {
-				const msg = JSON.parse(data.toString());
-				if (msg.type === "channel" && msg.body.id === "a") {
-					listener(msg.body);
-				} else if (msg.type === "connected" && msg.body.id === "a") {
-					res(ws);
-				}
-			});
-
-			ws.send(
-				JSON.stringify({
-					type: "connect",
-					body: {
-						channel: channel,
-						id: "a",
-						pong: true,
-						params: params,
-					},
-				}),
-			);
-		});
-	});
-}
-
-export const waitFire = async (
-	user: any,
-	channel: string,
-	trgr: () => any,
-	cond: (msg: Record<string, any>) => boolean,
-	params?: any,
-) => {
-	return new Promise<boolean>(async (res, rej) => {
-		let timer: NodeJS.Timeout;
-
-		let ws: WebSocket;
-		try {
-			ws = await connectStream(
-				user,
-				channel,
-				(msg) => {
-					if (cond(msg)) {
-						ws.close();
-						if (timer) clearTimeout(timer);
-						res(true);
-					}
-				},
-				params,
-			);
-		} catch (e) {
-			rej(e);
-		}
-
-		if (!ws!) return;
-
-		timer = setTimeout(() => {
-			ws.close();
-			res(false);
-		}, 3000);
-
-		try {
-			await trgr();
-		} catch (e) {
-			ws.close();
-			if (timer) clearTimeout(timer);
-			rej(e);
-		}
-	});
-};
-
-export const simpleGet = async (
-	path: string,
-	accept = "*/*",
-): Promise<{ status?: number; type?: string; location?: string }> => {
-	// node-fetchだと3xxを取れない
-	return await new Promise((resolve, reject) => {
-		const req = http.request(
-			`http://localhost:${port}${path}`,
-			{
-				headers: {
-					Accept: accept,
-				},
-			},
-			(res) => {
-				if (res.statusCode! >= 400) {
-					reject(res);
-				} else {
-					resolve({
-						status: res.statusCode,
-						type: res.headers["content-type"],
-						location: res.headers.location,
-					});
-				}
-			},
-		);
-
-		req.end();
-	});
-};
-
-export function launchServer(
-	callbackSpawnedProcess: (p: childProcess.ChildProcess) => void,
-	moreProcess: () => Promise<void> = async () => {},
-) {
-	return (done: (err?: Error) => any) => {
-		const p = childProcess.spawn("node", [_dirname + "/../index.js"], {
-			stdio: ["inherit", "inherit", "inherit", "ipc"],
-			env: { NODE_ENV: "test", PATH: process.env.PATH },
-		});
-		callbackSpawnedProcess(p);
-		p.on("message", (message) => {
-			if (message === "ok")
-				moreProcess()
-					.then(() => done())
-					.catch((e) => done(e));
-		});
-	};
-}
-
-export async function initTestDb(justBorrow = false, initEntities?: any[]) {
-	if (process.env.NODE_ENV !== "test") throw "NODE_ENV is not a test";
-
-	const db = new DataSource({
-		type: "postgres",
-		host: config.db.host,
-		port: config.db.port,
-		username: config.db.user,
-		password: config.db.pass,
-		database: config.db.db,
-		synchronize: true && !justBorrow,
-		dropSchema: true && !justBorrow,
-		entities: initEntities || entities,
-	});
-
-	await db.initialize();
-
-	return db;
-}
-
-export function startServer(
-	timeout = 60 * 1000,
-): Promise<childProcess.ChildProcess> {
-	return new Promise((res, rej) => {
-		const t = setTimeout(() => {
-			p.kill(SIGKILL);
-			rej("timeout to start");
-		}, timeout);
-
-		const p = childProcess.spawn("node", [_dirname + "/../built/index.js"], {
-			stdio: ["inherit", "inherit", "inherit", "ipc"],
-			env: { NODE_ENV: "test", PATH: process.env.PATH },
-		});
-
-		p.on("error", (e) => rej(e));
-
-		p.on("message", (message) => {
-			if (message === "ok") {
-				clearTimeout(t);
-				res(p);
-			}
-		});
-	});
-}
-
-export function shutdownServer(
-	p: childProcess.ChildProcess,
-	timeout = 20 * 1000,
-) {
-	return new Promise((res, rej) => {
-		const t = setTimeout(() => {
-			p.kill(SIGKILL);
-			res("force exit");
-		}, timeout);
-
-		p.once("exit", () => {
-			clearTimeout(t);
-			res("exited");
-		});
-
-		p.kill();
-	});
-}
-
-export function sleep(msec: number) {
-	return new Promise<void>((res) => {
-		setTimeout(() => {
-			res();
-		}, msec);
-	});
-}
diff --git a/packages/client/package.json b/packages/client/package.json
index 7df3232d4f..3827866669 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -18,7 +18,7 @@
 		"@rollup/plugin-alias": "5.1.0",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/pluginutils": "5.1.0",
-		"@syuilo/aiscript": "0.17.0",
+		"@syuilo/aiscript": "0.19.0",
 		"@misskey-dev/browser-image-resizer": "2024.1.0",
 		"@types/autosize": "4.0.3",
 		"@types/glob": "8.1.0",
@@ -33,8 +33,8 @@
 		"@types/throttle-debounce": "5.0.2",
 		"@types/tinycolor2": "1.4.6",
 		"@types/uuid": "10.0.0",
-		"@vitejs/plugin-vue": "5.0.5",
-		"@vue/runtime-core": "3.4.31",
+		"@vitejs/plugin-vue": "5.1.0",
+		"@vue/runtime-core": "3.4.33",
 		"autobind-decorator": "2.4.0",
 		"autosize": "6.0.1",
 		"broadcast-channel": "7.0.0",
@@ -45,7 +45,7 @@
 		"chartjs-plugin-zoom": "2.0.1",
 		"check-password-strength": "2.0.10",
 		"city-timezones": "1.2.1",
-		"compare-versions": "6.1.0",
+		"compare-versions": "6.1.1",
 		"cropperjs": "2.0.0-rc.1",
 		"date-fns": "3.6.0",
 		"emojilib": "3.0.12",
@@ -70,25 +70,25 @@
 		"punycode": "2.3.1",
 		"qrcode": "1.5.3",
 		"qrcode-vue3": "1.6.8",
-		"rollup": "4.17.2",
+		"rollup": "4.19.0",
 		"s-age": "1.1.2",
 		"sass": "1.77.8",
 		"seedrandom": "3.0.5",
 		"stringz": "2.1.0",
-		"swiper": "11.1.4",
+		"swiper": "11.1.7",
 		"textarea-caret": "3.1.0",
 		"throttle-debounce": "5.0.2",
 		"tinycolor2": "1.6.0",
 		"tinyld": "1.3.4",
-		"typescript": "5.5.3",
+		"typescript": "5.5.4",
 		"unicode-emoji-json": "0.6.0",
 		"uuid": "10.0.0",
-		"vite": "5.3.3",
+		"vite": "5.3.4",
 		"vite-plugin-compression": "0.5.1",
-		"vue": "3.4.31",
+		"vue": "3.4.33",
 		"vue-draggable-plus": "0.5.2",
 		"vue-plyr": "7.0.0",
 		"vue-prism-editor": "2.0.0-alpha.2",
-		"vue-tsc": "2.0.26"
+		"vue-tsc": "2.0.28"
 	}
 }
diff --git a/packages/client/src/components/MkDrive.file.vue b/packages/client/src/components/MkDrive.file.vue
index 6d348a33e7..1e83fc0257 100644
--- a/packages/client/src/components/MkDrive.file.vue
+++ b/packages/client/src/components/MkDrive.file.vue
@@ -48,6 +48,7 @@ import { i18n } from "@/i18n";
 import { me } from "@/me";
 import icon from "@/scripts/icon";
 import type { MenuItem } from "@/types/menu";
+import { useRouter } from "@/router";
 
 const props = withDefaults(
 	defineProps<{
@@ -67,6 +68,8 @@ const emit = defineEmits<{
 	(ev: "dragend"): void;
 }>();
 
+const router = useRouter();
+
 const isDragging = ref(false);
 
 const title = computed(
@@ -107,10 +110,11 @@ function getMenu(): MenuItem[] {
 			download: props.file.name,
 		},
 		{
-			type: "a",
-			href: `/my/drive/file/${props.file.id}/attached`,
 			text: i18n.ts.showAttachedNotes,
 			icon: `${icon("ph-paperclip")}`,
+			action: () => {
+				router.push(`/my/drive/file/${props.file.id}/attached`);
+			},
 		},
 		null,
 		{
diff --git a/packages/client/src/components/MkWidgets.vue b/packages/client/src/components/MkWidgets.vue
index fb8a449590..58e192e926 100644
--- a/packages/client/src/components/MkWidgets.vue
+++ b/packages/client/src/components/MkWidgets.vue
@@ -9,7 +9,7 @@
 				>
 					<template #label>{{ i18n.ts.selectWidget }}</template>
 					<option
-						v-for="widget in widgetDefs"
+						v-for="widget in sortedWidgets"
 						:key="widget"
 						:value="widget"
 					>
@@ -101,6 +101,12 @@ const emit = defineEmits<{
 	(ev: "exit"): void;
 }>();
 
+const sortedWidgets = computed(() =>
+	widgetDefs.sort((a, b) =>
+		i18n.t(`_widgets.${a}`).localeCompare(i18n.t(`_widgets.${b}`)),
+	),
+);
+
 const widgetRefs = {};
 const configWidget = (id: string) => {
 	widgetRefs[id].configure();
diff --git a/packages/client/src/components/global/MkPageHeader.vue b/packages/client/src/components/global/MkPageHeader.vue
index 7f6e13131a..40b1e8a1e0 100644
--- a/packages/client/src/components/global/MkPageHeader.vue
+++ b/packages/client/src/components/global/MkPageHeader.vue
@@ -264,7 +264,8 @@ onMounted(() => {
 	watch(
 		() => [props.tab, props.tabs],
 		() => {
-			nextTick(() => {
+			nextTick(async () => {
+				await document.fonts.ready;
 				if (props.tab == null) return;
 				if (!isTabs(props.tabs)) return;
 				const tabEl = tabRefs[props.tab];
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index 4dd64e0fe1..62d9d24946 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -24,9 +24,6 @@
 				<FormRadios v-model="importType" class="_formBlock">
 					<option value="firefish">Firefish/Misskey</option>
 					<option value="mastodon">Mastodon/Akkoma/Pleroma</option>
-					<option :disabled="true" value="twitter">
-						Twitter (soon)
-					</option>
 				</FormRadios>
 				<MkButton
 					primary
@@ -77,6 +74,31 @@
 				>
 			</FormFolder>
 		</FormSection>
+		<FormSection>
+			<template #label>{{
+				i18n.ts.followers
+			}}</template>
+			<FormFolder class="_formBlock">
+				<template #label>{{ i18n.ts.export }}</template>
+				<template #icon
+					><i :class="icon('ph-download-simple')"></i
+				></template>
+				<FormSwitch v-model="excludeMutingUsers" class="_formBlock">
+					{{ i18n.ts._exportOrImport.excludeMutingUsers }}
+				</FormSwitch>
+				<FormSwitch v-model="excludeInactiveUsers" class="_formBlock">
+					{{ i18n.ts._exportOrImport.excludeInactiveUsers }}
+				</FormSwitch>
+				<MkButton
+					primary
+					:class="$style.button"
+					inline
+					@click="exportFollowers()"
+					><i :class="icon('ph-download-simple')"></i>
+					{{ i18n.ts.export }}</MkButton
+				>
+			</FormFolder>
+		</FormSection>
 		<FormSection>
 			<template #label>{{ i18n.ts._exportOrImport.userLists }}</template>
 			<FormFolder class="_formBlock">
@@ -236,6 +258,15 @@ const exportFollowing = () => {
 		.catch(onError);
 };
 
+const exportFollowers = () => {
+	os.api("i/export-followers", {
+		excludeMuting: excludeMutingUsers.value,
+		excludeInactive: excludeInactiveUsers.value,
+	})
+		.then(onExportSuccess)
+		.catch(onError);
+};
+
 const exportBlocking = () => {
 	os.api("i/export-blocking", {}).then(onExportSuccess).catch(onError);
 };
diff --git a/packages/client/src/pages/settings/migration.vue b/packages/client/src/pages/settings/migration.vue
index e82ee8b1bc..7842ae0c70 100644
--- a/packages/client/src/pages/settings/migration.vue
+++ b/packages/client/src/pages/settings/migration.vue
@@ -52,7 +52,7 @@
 <script lang="ts" setup>
 import { ref } from "vue";
 
-import { acct } from "firefish-js";
+import { acct, type entities } from "firefish-js";
 import FormSection from "@/components/form/section.vue";
 import FormInput from "@/components/form/input.vue";
 import FormButton from "@/components/MkButton.vue";
@@ -61,6 +61,7 @@ import * as os from "@/os";
 import { i18n } from "@/i18n";
 import { definePageMetadata } from "@/scripts/page-metadata";
 import { me } from "@/me";
+import { refreshAccount } from "@/account";
 import icon from "@/scripts/icon";
 
 const moveToAccount = ref("");
@@ -69,12 +70,16 @@ const accountAlias = ref([""]);
 await init();
 
 async function init() {
+	await refreshAccount();
 	if (me?.alsoKnownAs && me.alsoKnownAs.length > 0) {
-		const aka = await os.api("users/show", { userIds: me.alsoKnownAs });
-		accountAlias.value =
-			aka && aka.length > 0
-				? aka.map((user) => `@${acct.toString(user)}`)
-				: [""];
+		const aka = me.alsoKnownAs
+			.map((uri) => os.api("ap/show", { uri }))
+			.map(
+				async (user) =>
+					`@${acct.toString((await user).object as entities.UserDetailed)}`,
+			);
+		const accounts = await Promise.all(aka);
+		accountAlias.value = accounts.length > 0 ? accounts : [""];
 	} else {
 		accountAlias.value = [""];
 	}
diff --git a/packages/client/src/scripts/aiscript/api.ts b/packages/client/src/scripts/aiscript/api.ts
index 1e6b7aa018..03d2a66442 100644
--- a/packages/client/src/scripts/aiscript/api.ts
+++ b/packages/client/src/scripts/aiscript/api.ts
@@ -14,6 +14,7 @@ export function createAiScriptEnv(opts) {
 				title: title.value,
 				text: text.value,
 			});
+			return values.NULL;
 		}),
 		"Mk:confirm": values.FN_NATIVE(async ([title, text, type]) => {
 			const confirm = await os.confirm({
diff --git a/packages/firefish-js/package.json b/packages/firefish-js/package.json
index 29ee2b6f6b..4d0a225eae 100644
--- a/packages/firefish-js/package.json
+++ b/packages/firefish-js/package.json
@@ -21,16 +21,16 @@
 	},
 	"devDependencies": {
 		"@types/jest": "29.5.12",
-		"@types/node": "20.14.10",
+		"@types/node": "20.14.12",
 		"jest": "29.7.0",
 		"jest-fetch-mock": "3.0.3",
 		"jest-websocket-mock": "2.5.0",
 		"mock-socket": "9.3.1",
-		"ts-jest": "29.2.2",
+		"ts-jest": "29.2.3",
 		"ts-node": "10.9.2",
 		"tsc-alias": "1.8.10",
 		"tsd": "0.31.1",
-		"typescript": "5.5.3"
+		"typescript": "5.5.4"
 	},
 	"files": [
 		"built", "src"
diff --git a/packages/firefish-js/src/api.types.ts b/packages/firefish-js/src/api.types.ts
index eccab27082..4e775562e1 100644
--- a/packages/firefish-js/src/api.types.ts
+++ b/packages/firefish-js/src/api.types.ts
@@ -499,6 +499,7 @@ export type Endpoints = {
 	"i/delete-account": { req: { password: string }; res: null };
 	"i/export-blocking": { req: TODO; res: TODO };
 	"i/export-following": { req: TODO; res: TODO };
+	"i/export-followers": { req: TODO; res: TODO };
 	"i/export-mute": { req: TODO; res: TODO };
 	"i/export-notes": { req: TODO; res: TODO };
 	"i/export-user-lists": { req: TODO; res: TODO };
diff --git a/packages/macro-rs/macros-impl/src/error.rs b/packages/macro-rs/macros-impl/src/error.rs
new file mode 100644
index 0000000000..e14ea70c94
--- /dev/null
+++ b/packages/macro-rs/macros-impl/src/error.rs
@@ -0,0 +1,41 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+
+pub fn error_variants(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    match error_variants_impl(item) {
+        Ok(tokens) => tokens,
+        Err(error) => error.to_compile_error(),
+    }
+}
+
+fn error_variants_impl(item: TokenStream) -> syn::Result<TokenStream> {
+    let mut item: syn::ItemEnum = syn::parse2(item)?;
+
+    item.variants = item
+        .variants
+        .into_iter()
+        .map(|mut variant| {
+            // check if doc attribute is alredy there
+            if variant.attrs.iter().any(|attr| attr.path().is_ident("doc")) {
+                return variant;
+            }
+
+            let msg = variant.attrs.iter().find_map(|attr| {
+                if !attr.path().is_ident("error") {
+                    return None;
+                }
+                let lit: syn::LitStr = attr.parse_args().ok()?;
+                Some(lit.value())
+            });
+
+            // add #[doc] attribute
+            if let Some(msg) = msg {
+                variant.attrs.push(syn::parse_quote! { #[doc = #msg] });
+            }
+
+            variant
+        })
+        .collect();
+
+    Ok(quote! { #item })
+}
diff --git a/packages/macro-rs/macros-impl/src/lib.rs b/packages/macro-rs/macros-impl/src/lib.rs
index 9e4a70240e..4d9cde075b 100644
--- a/packages/macro-rs/macros-impl/src/lib.rs
+++ b/packages/macro-rs/macros-impl/src/lib.rs
@@ -1,4 +1,5 @@
 #![allow(clippy::items_after_test_module)]
 
+pub mod error;
 pub mod napi;
 mod util;
diff --git a/packages/macro-rs/macros/src/lib.rs b/packages/macro-rs/macros/src/lib.rs
index 6bc2c0ddc1..3619839a74 100644
--- a/packages/macro-rs/macros/src/lib.rs
+++ b/packages/macro-rs/macros/src/lib.rs
@@ -71,6 +71,17 @@ define_wrapper_proc_macro_attributes! {
         #[cfg(feature = "napi")]
         #[macros::napi(#attr)]
         #item
+
+        #[cfg(any(test, doctest))]
+        #item
+    }
+
+    /// When applied to error variant enums, this macro generates a document
+    /// based on error messages unless there is a doc comment
+    errors(attr, item) {
+        #[derive(::thiserror::Error, ::std::fmt::Debug)]
+        #[macros::error_variants(#attr, #item)]
+        #item
     }
 }
 
@@ -78,4 +89,6 @@ reexport_proc_macro_attributes! {
     /// Creates an extra wrapper function for [napi_derive](https://docs.rs/napi-derive/latest/napi_derive/).
     /// See [macros_impl::napi::napi] for details.
     macros_impl::napi::napi as napi
+
+    macros_impl::error::error_variants as error_variants
 }
diff --git a/packages/sw/package.json b/packages/sw/package.json
index b110a8f919..daf8b43b43 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -12,7 +12,7 @@
 	"devDependencies": {
 		"firefish-js": "workspace:*",
 		"idb-keyval": "6.2.1",
-		"vite": "5.3.3",
+		"vite": "5.3.4",
 		"vite-plugin-compression": "0.5.1"
 	}
 }
diff --git a/patrons.json b/patrons.json
deleted file mode 100644
index cbbeacfdc9..0000000000
--- a/patrons.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "patrons": [],
-  "sponsors": []
-}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ffc5b2522..b382641a86 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -28,26 +28,26 @@ importers:
         specifier: 1.8.3
         version: 1.8.3
       '@types/node':
-        specifier: 20.14.10
-        version: 20.14.10
+        specifier: 20.14.12
+        version: 20.14.12
       execa:
         specifier: 9.3.0
         version: 9.3.0
       pnpm:
-        specifier: 9.5.0
-        version: 9.5.0
+        specifier: 9.6.0
+        version: 9.6.0
 
   packages/backend:
     dependencies:
       '@bull-board/api':
-        specifier: 5.21.0
-        version: 5.21.0(@bull-board/ui@5.21.0)
+        specifier: 5.21.1
+        version: 5.21.1(@bull-board/ui@5.21.1)
       '@bull-board/koa':
-        specifier: 5.21.0
-        version: 5.21.0(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)
+        specifier: 5.21.1
+        version: 5.21.1(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)
       '@bull-board/ui':
-        specifier: 5.21.0
-        version: 5.21.0
+        specifier: 5.21.1
+        version: 5.21.1
       '@discordapp/twemoji':
         specifier: 15.0.3
         version: 15.0.3
@@ -62,13 +62,13 @@ importers:
         version: 12.0.1
       '@ladjs/koa-views':
         specifier: 9.0.0
-        version: 9.0.0(@babel/core@7.24.7)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
+        version: 9.0.0(@babel/core@7.24.9)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
       '@redocly/openapi-core':
-        specifier: 1.18.0
-        version: 1.18.0
+        specifier: 1.18.1
+        version: 1.18.1
       '@sinonjs/fake-timers':
         specifier: 11.2.2
         version: 11.2.2
@@ -88,8 +88,8 @@ importers:
         specifier: 0.5.0
         version: 0.5.0
       aws-sdk:
-        specifier: 2.1659.0
-        version: 2.1659.0
+        specifier: 2.1662.0
+        version: 2.1662.0
       axios:
         specifier: 1.7.2
         version: 1.7.2
@@ -129,9 +129,6 @@ importers:
       deep-email-validator:
         specifier: 0.1.21
         version: 0.1.21
-      deepl-node:
-        specifier: 1.13.0
-        version: 1.13.0
       escape-regexp:
         specifier: 0.0.1
         version: 0.0.1
@@ -139,8 +136,8 @@ importers:
         specifier: 4.2.2
         version: 4.2.2
       file-type:
-        specifier: 19.1.1
-        version: 19.1.1
+        specifier: 19.3.0
+        version: 19.3.0
       firefish-js:
         specifier: workspace:*
         version: link:../firefish-js
@@ -151,8 +148,8 @@ importers:
         specifier: 4.0.0
         version: 4.0.0
       got:
-        specifier: 14.4.1
-        version: 14.4.1
+        specifier: 14.4.2
+        version: 14.4.2
       gunzip-maybe:
         specifier: 1.4.2
         version: 1.4.2
@@ -169,8 +166,8 @@ importers:
         specifier: 5.0.1
         version: 5.0.1
       jsdom:
-        specifier: 24.1.0
-        version: 24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
+        specifier: 24.1.1
+        version: 24.1.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
       json5:
         specifier: 2.2.3
         version: 2.2.3
@@ -217,8 +214,8 @@ importers:
         specifier: 2.1.35
         version: 2.1.35
       msgpackr:
-        specifier: 1.10.2
-        version: 1.10.2
+        specifier: 1.11.0
+        version: 1.11.0
       multer:
         specifier: 1.4.5-lts.1
         version: 1.4.5-lts.1
@@ -231,9 +228,6 @@ importers:
       nodemailer:
         specifier: 6.9.14
         version: 6.9.14
-      opencc-js:
-        specifier: 1.0.5
-        version: 1.0.5
       otpauth:
         specifier: 9.3.1
         version: 9.3.1
@@ -289,8 +283,8 @@ importers:
         specifier: 2.13.0
         version: 2.13.0
       semver:
-        specifier: 7.6.2
-        version: 7.6.2
+        specifier: 7.6.3
+        version: 7.6.3
       sharp:
         specifier: 0.33.4
         version: 0.33.4
@@ -317,7 +311,7 @@ importers:
         version: 0.2.3
       typeorm:
         specifier: 0.3.20
-        version: 0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+        version: 0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       ulid:
         specifier: 2.3.0
         version: 2.3.0
@@ -394,12 +388,9 @@ importers:
       '@types/koa__router':
         specifier: 12.0.4
         version: 12.0.4
-      '@types/mocha':
-        specifier: 10.0.7
-        version: 10.0.7
       '@types/node':
-        specifier: 20.14.10
-        version: 20.14.10
+        specifier: 20.14.12
+        version: 20.14.12
       '@types/node-fetch':
         specifier: 2.6.11
         version: 2.6.11
@@ -416,8 +407,8 @@ importers:
         specifier: 8.11.6
         version: 8.11.6
       '@types/probe-image-size':
-        specifier: 7.2.4
-        version: 7.2.4
+        specifier: 7.2.5
+        version: 7.2.5
       '@types/pug':
         specifier: 2.0.10
         version: 2.0.10
@@ -469,9 +460,6 @@ importers:
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
-      mocha:
-        specifier: 10.6.0
-        version: 10.6.0
       pug:
         specifier: 3.0.3
         version: 3.0.3
@@ -480,10 +468,10 @@ importers:
         version: 2.0.0
       ts-loader:
         specifier: 9.5.1
-        version: 9.5.1(typescript@5.5.3)(webpack@5.93.0)
+        version: 9.5.1(typescript@5.5.4)(webpack@5.93.0)
       ts-node:
         specifier: 10.9.2
-        version: 10.9.2(@types/node@20.14.10)(typescript@5.5.3)
+        version: 10.9.2(@types/node@20.14.12)(typescript@5.5.4)
       tsc-alias:
         specifier: 1.8.10
         version: 1.8.10
@@ -491,11 +479,11 @@ importers:
         specifier: 4.2.0
         version: 4.2.0
       type-fest:
-        specifier: 4.21.0
-        version: 4.21.0
+        specifier: 4.23.0
+        version: 4.23.0
       typescript:
-        specifier: 5.5.3
-        version: 5.5.3
+        specifier: 5.5.4
+        version: 5.5.4
       webpack:
         specifier: 5.93.0
         version: 5.93.0
@@ -506,8 +494,8 @@ importers:
   packages/backend-rs:
     devDependencies:
       '@napi-rs/cli':
-        specifier: 3.0.0-alpha.58
-        version: 3.0.0-alpha.58(@emnapi/runtime@1.2.0)
+        specifier: 3.0.0-alpha.62
+        version: 3.0.0-alpha.62(@emnapi/runtime@1.2.0)
 
   packages/client:
     devDependencies:
@@ -519,16 +507,16 @@ importers:
         version: 2.1.1
       '@rollup/plugin-alias':
         specifier: 5.1.0
-        version: 5.1.0(rollup@4.17.2)
+        version: 5.1.0(rollup@4.19.0)
       '@rollup/plugin-json':
         specifier: 6.1.0
-        version: 6.1.0(rollup@4.17.2)
+        version: 6.1.0(rollup@4.19.0)
       '@rollup/pluginutils':
         specifier: 5.1.0
-        version: 5.1.0(rollup@4.17.2)
+        version: 5.1.0(rollup@4.19.0)
       '@syuilo/aiscript':
-        specifier: 0.17.0
-        version: 0.17.0
+        specifier: 0.19.0
+        version: 0.19.0
       '@types/autosize':
         specifier: 4.0.3
         version: 4.0.3
@@ -569,11 +557,11 @@ importers:
         specifier: 10.0.0
         version: 10.0.0
       '@vitejs/plugin-vue':
-        specifier: 5.0.5
-        version: 5.0.5(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2))(vue@3.4.31(typescript@5.5.3))
+        specifier: 5.1.0
+        version: 5.1.0(vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.33(typescript@5.5.4))
       '@vue/runtime-core':
-        specifier: 3.4.31
-        version: 3.4.31
+        specifier: 3.4.33
+        version: 3.4.33
       autobind-decorator:
         specifier: 2.4.0
         version: 2.4.0
@@ -605,8 +593,8 @@ importers:
         specifier: 1.2.1
         version: 1.2.1
       compare-versions:
-        specifier: 6.1.0
-        version: 6.1.0
+        specifier: 6.1.1
+        version: 6.1.1
       cropperjs:
         specifier: 2.0.0-rc.1
         version: 2.0.0-rc.1
@@ -630,7 +618,7 @@ importers:
         version: 7.5.4
       focus-trap-vue:
         specifier: 4.0.3
-        version: 4.0.3(focus-trap@7.5.4)(vue@3.4.31(typescript@5.5.3))
+        version: 4.0.3(focus-trap@7.5.4)(vue@3.4.33(typescript@5.5.4))
       gsap:
         specifier: 3.12.5
         version: 3.12.5
@@ -680,8 +668,8 @@ importers:
         specifier: 1.6.8
         version: 1.6.8
       rollup:
-        specifier: 4.17.2
-        version: 4.17.2
+        specifier: 4.19.0
+        version: 4.19.0
       s-age:
         specifier: 1.1.2
         version: 1.1.2
@@ -695,8 +683,8 @@ importers:
         specifier: 2.1.0
         version: 2.1.0
       swiper:
-        specifier: 11.1.4
-        version: 11.1.4
+        specifier: 11.1.7
+        version: 11.1.7
       textarea-caret:
         specifier: 3.1.0
         version: 3.1.0
@@ -710,8 +698,8 @@ importers:
         specifier: 1.3.4
         version: 1.3.4
       typescript:
-        specifier: 5.5.3
-        version: 5.5.3
+        specifier: 5.5.4
+        version: 5.5.4
       unicode-emoji-json:
         specifier: 0.6.0
         version: 0.6.0
@@ -719,14 +707,14 @@ importers:
         specifier: 10.0.0
         version: 10.0.0
       vite:
-        specifier: 5.3.3
-        version: 5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2)
+        specifier: 5.3.4
+        version: 5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
       vite-plugin-compression:
         specifier: 0.5.1
-        version: 0.5.1(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2))
+        version: 0.5.1(vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
       vue:
-        specifier: 3.4.31
-        version: 3.4.31(typescript@5.5.3)
+        specifier: 3.4.33
+        version: 3.4.33(typescript@5.5.4)
       vue-draggable-plus:
         specifier: 0.5.2
         version: 0.5.2(@types/sortablejs@1.15.8)
@@ -735,10 +723,10 @@ importers:
         version: 7.0.0
       vue-prism-editor:
         specifier: 2.0.0-alpha.2
-        version: 2.0.0-alpha.2(vue@3.4.31(typescript@5.5.3))
+        version: 2.0.0-alpha.2(vue@3.4.33(typescript@5.5.4))
       vue-tsc:
-        specifier: 2.0.26
-        version: 2.0.26(typescript@5.5.3)
+        specifier: 2.0.28
+        version: 2.0.28(typescript@5.5.4)
 
   packages/firefish-js:
     dependencies:
@@ -753,11 +741,11 @@ importers:
         specifier: 29.5.12
         version: 29.5.12
       '@types/node':
-        specifier: 20.14.10
-        version: 20.14.10
+        specifier: 20.14.12
+        version: 20.14.12
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+        version: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       jest-fetch-mock:
         specifier: 3.0.3
         version: 3.0.3
@@ -768,11 +756,11 @@ importers:
         specifier: 9.3.1
         version: 9.3.1
       ts-jest:
-        specifier: 29.2.2
-        version: 29.2.2(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))(typescript@5.5.3)
+        specifier: 29.2.3
+        version: 29.2.3(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)))(typescript@5.5.4)
       ts-node:
         specifier: 10.9.2
-        version: 10.9.2(@types/node@20.14.10)(typescript@5.5.3)
+        version: 10.9.2(@types/node@20.14.12)(typescript@5.5.4)
       tsc-alias:
         specifier: 1.8.10
         version: 1.8.10
@@ -780,8 +768,8 @@ importers:
         specifier: 0.31.1
         version: 0.31.1
       typescript:
-        specifier: 5.5.3
-        version: 5.5.3
+        specifier: 5.5.4
+        version: 5.5.4
 
   packages/sw:
     devDependencies:
@@ -792,11 +780,11 @@ importers:
         specifier: 6.2.1
         version: 6.2.1
       vite:
-        specifier: 5.3.3
-        version: 5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2)
+        specifier: 5.3.4
+        version: 5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
       vite-plugin-compression:
         specifier: 0.5.1
-        version: 0.5.1(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2))
+        version: 0.5.1(vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
 
 packages:
 
@@ -808,20 +796,20 @@ packages:
     resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/compat-data@7.24.7':
-    resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==}
+  '@babel/compat-data@7.24.9':
+    resolution: {integrity: sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/core@7.24.7':
-    resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==}
+  '@babel/core@7.24.9':
+    resolution: {integrity: sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/generator@7.24.7':
-    resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==}
+  '@babel/generator@7.24.10':
+    resolution: {integrity: sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-compilation-targets@7.24.7':
-    resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==}
+  '@babel/helper-compilation-targets@7.24.8':
+    resolution: {integrity: sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==}
     engines: {node: '>=6.9.0'}
 
   '@babel/helper-environment-visitor@7.24.7':
@@ -840,14 +828,14 @@ packages:
     resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-module-transforms@7.24.7':
-    resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==}
+  '@babel/helper-module-transforms@7.24.9':
+    resolution: {integrity: sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
 
-  '@babel/helper-plugin-utils@7.24.7':
-    resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==}
+  '@babel/helper-plugin-utils@7.24.8':
+    resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==}
     engines: {node: '>=6.9.0'}
 
   '@babel/helper-simple-access@7.24.7':
@@ -858,28 +846,28 @@ packages:
     resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-string-parser@7.24.7':
-    resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
+  '@babel/helper-string-parser@7.24.8':
+    resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
     engines: {node: '>=6.9.0'}
 
   '@babel/helper-validator-identifier@7.24.7':
     resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-validator-option@7.24.7':
-    resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==}
+  '@babel/helper-validator-option@7.24.8':
+    resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helpers@7.24.7':
-    resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
+  '@babel/helpers@7.24.8':
+    resolution: {integrity: sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==}
     engines: {node: '>=6.9.0'}
 
   '@babel/highlight@7.24.7':
     resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/parser@7.24.7':
-    resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==}
+  '@babel/parser@7.24.8':
+    resolution: {integrity: sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==}
     engines: {node: '>=6.0.0'}
     hasBin: true
 
@@ -968,8 +956,8 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-modules-commonjs@7.24.7':
-    resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==}
+  '@babel/plugin-transform-modules-commonjs@7.24.8':
+    resolution: {integrity: sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
@@ -982,12 +970,12 @@ packages:
     resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/traverse@7.24.7':
-    resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==}
+  '@babel/traverse@7.24.8':
+    resolution: {integrity: sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/types@7.24.7':
-    resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
+  '@babel/types@7.24.9':
+    resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==}
     engines: {node: '>=6.9.0'}
 
   '@bcoe/v8-coverage@0.2.3':
@@ -1046,16 +1034,16 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@bull-board/api@5.21.0':
-    resolution: {integrity: sha512-27tjptwgRgP1G5jT+POjiZZOP3LgdIM4XdfEWfa6t5E0CYImL4EjmdiFo5lhbHhYKZ842VhIpHuNcPk8nY3K9A==}
+  '@bull-board/api@5.21.1':
+    resolution: {integrity: sha512-anzTfhOJ93eraT/GYeyxWpxRMarHwuevn6pPBfdOj0LC2eg98OPnkfdMSjcrpL3qrqsxON0RslS7kuPfCEnX6A==}
     peerDependencies:
-      '@bull-board/ui': 5.21.0
+      '@bull-board/ui': 5.21.1
 
-  '@bull-board/koa@5.21.0':
-    resolution: {integrity: sha512-hXZHZz+z93bf6aN295ZJ23eyT6ZwNNrke2Ec2SZO3xKnQ0AfAnDaLcqX/hLevD0XpIBX5OgjOnuOIChzk2n26w==}
+  '@bull-board/koa@5.21.1':
+    resolution: {integrity: sha512-ygLYekgJzMw5cTMbowGteW04Re9HPauTl2C6gPxElGmyjl0/FO4Pw8h6hAGP1pCQjbtK635qRAb5xQW1NzId9w==}
 
-  '@bull-board/ui@5.21.0':
-    resolution: {integrity: sha512-eH8QQwIHgCXxNEmlg9EZr3fSvno/bdbgBGfSQO5s9c9n9eDEaKX46ambKSPvgFPtwSdiV1AYQEa/3fGSebVIxg==}
+  '@bull-board/ui@5.21.1':
+    resolution: {integrity: sha512-JBDeCqG7j/c3WE0uGMN9snPkRJz9/D6MpTZzyVj7KOxIJwNKPOICNFZbCrCNi7bcJYHDJ2xGTN9OO1mw7i43BQ==}
 
   '@cbor-extract/cbor-extract-darwin-arm64@2.2.0':
     resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==}
@@ -1401,56 +1389,56 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@inquirer/checkbox@2.3.10':
-    resolution: {integrity: sha512-CTc864M2/523rKc9AglIzAcUCuPXDZENgc5S2KZFVRbnMzpXcYTsUWmbqSeL0XLvtlvEtNevkkVbfVhJpruOyQ==}
+  '@inquirer/checkbox@2.4.1':
+    resolution: {integrity: sha512-Mt6JH1XuTPgzSirE26w1xHxw32z9tjUZPYOGAcgNeV0olSeLDidLF1nylFLZdzJrEQcMylQ+8t0RdP74LKS0oQ==}
     engines: {node: '>=18'}
 
-  '@inquirer/confirm@3.1.14':
-    resolution: {integrity: sha512-nbLSX37b2dGPtKWL3rPuR/5hOuD30S+pqJ/MuFiUEgN6GiMs8UMxiurKAMDzKt6C95ltjupa8zH6+3csXNHWpA==}
+  '@inquirer/confirm@3.1.16':
+    resolution: {integrity: sha512-DXgLZim+YVTk05zRywvFRfJt2Jje7sZ4DO6Ss9RpGtgXEd/T0IiTqubHWst0IazCwdPI9g/06Rtm/nm4IBFJBA==}
     engines: {node: '>=18'}
 
-  '@inquirer/core@9.0.2':
-    resolution: {integrity: sha512-nguvH3TZar3ACwbytZrraRTzGqyxJfYJwv+ZwqZNatAosdWQMP1GV8zvmkNlBe2JeZSaw0WYBHZk52pDpWC9qA==}
+  '@inquirer/core@9.0.4':
+    resolution: {integrity: sha512-46LaWACIctSfVKTu71ziFlqO8SVLhWGSxvaHpf0frfDTphSSpIfeNo5ZH/kJPHYJw4VgPGf/9c3zJN/FnCdaIQ==}
     engines: {node: '>=18'}
 
-  '@inquirer/editor@2.1.14':
-    resolution: {integrity: sha512-6nWpoJyVAKwAcv67bkbBmmi3f32xua79fP7TRmNUoR4K+B1GiOBsHO1YdvET/jvC+nTlBZL7puKAKyM7G+Lkzw==}
+  '@inquirer/editor@2.1.16':
+    resolution: {integrity: sha512-SkrpBFUK1XqCS5a66v2dnsjMoXyuxC+2golkM0NoT7XYq47eY8RVFnt5oOjj257MmXjbuSLcc7iQb7bFasHTfA==}
     engines: {node: '>=18'}
 
-  '@inquirer/expand@2.1.14':
-    resolution: {integrity: sha512-JcxsLajwPykF2kq6biIUdoOzTQ3LXqb8XMVrWkCprG/pFeU1SsxcSSFbF1T5jJGvvlTVcsE+JdGjbQ8ZRZ82RA==}
+  '@inquirer/expand@2.1.16':
+    resolution: {integrity: sha512-i7qnbjg7bFRd/UXq7I+IHkai84NQCWhFbNvVDp0Gi/DCwfPAoInFnwtPMBpf4Ep/UaLdVl98NR2AzwYRZdLV/w==}
     engines: {node: '>=18'}
 
-  '@inquirer/figures@1.0.3':
-    resolution: {integrity: sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw==}
+  '@inquirer/figures@1.0.4':
+    resolution: {integrity: sha512-R7Gsg6elpuqdn55fBH2y9oYzrU/yKrSmIsDX4ROT51vohrECFzTf2zw9BfUbOW8xjfmM2QbVoVYdTwhrtEKWSQ==}
     engines: {node: '>=18'}
 
-  '@inquirer/input@2.2.1':
-    resolution: {integrity: sha512-Yl1G6h7qWydzrJwqN777geeJVaAFL5Ly83aZlw4xHf8Z/BoTMfKRheyuMaQwOG7LQ4e5nQP7PxXdEg4SzQ+OKw==}
+  '@inquirer/input@2.2.3':
+    resolution: {integrity: sha512-L3vH9istz91uk43Us4dqLb0UV6XxoSQ2MYRs3QSIPDXj1zUPGOk44Y1R69tPkO4VSHnlZjDp+FPEf/CTaee4dg==}
     engines: {node: '>=18'}
 
-  '@inquirer/number@1.0.2':
-    resolution: {integrity: sha512-GcoK+Phxcln0Qw9e73S5a8B2Ejg3HgSTvNfDegIcS5/BKwUm8t5rejja1l09WXjZM9vrVbRDf9RzWtSUiWVYRQ==}
+  '@inquirer/number@1.0.4':
+    resolution: {integrity: sha512-kDa06HLzkVUzWnp5APF6JeQw6PCUc5hSQEyFYl8MoIRoJP0Psbf3Ys47skEpASqsSXcCjBy+3dEiFyhL95cbBA==}
     engines: {node: '>=18'}
 
-  '@inquirer/password@2.1.14':
-    resolution: {integrity: sha512-sPzOkXLhWJQ96K6nPZFnF8XB8tsDrcCRobd1d3EDz81F+4hp8BbdmsnsQcqZ7oYDIOVM/mWJyIUtJ35TrssJxQ==}
+  '@inquirer/password@2.1.16':
+    resolution: {integrity: sha512-UXzm1nzb0rGaciJ95ZeEjkZ/2KpPRxC94bTzOEkl5Gy/jQ5X3frJHHTzBMRg1KPuyAQTQSQKYXtjTKoknpTcTg==}
     engines: {node: '>=18'}
 
-  '@inquirer/prompts@5.1.2':
-    resolution: {integrity: sha512-E+ndnfwtVQtcmPt888Hc/HAxJUHSaA6OIvyvLAQ5BLQv+t20GbYdFSjXeLgb47OpMU+aRsKA/ys+Zoylw3kTVg==}
+  '@inquirer/prompts@5.2.1':
+    resolution: {integrity: sha512-jS99zPSKAHdSn3q58QRV5H/wPUPMThYA4ZgDV5oSyELj2W7d6QrJi5cezBtAXtsqhKjPBx5lix/vGpIJ5FRFXQ==}
     engines: {node: '>=18'}
 
-  '@inquirer/rawlist@2.1.14':
-    resolution: {integrity: sha512-pLpEzhKNQ/ugFAFfgCNaXljB+dcCwmXwR1jOxAbVeFIdB3l02E5gjI+h1rb136tq0T8JO6P5KFR1oTeld/wdrA==}
+  '@inquirer/rawlist@2.1.16':
+    resolution: {integrity: sha512-RINF+Rw6u5fJQ2kBbAUkNN8bLXUmrl+rLwrlZf24SJt/fosX672U3WseUUHdR3yvIIoIuISrHrh+jbhwl170/Q==}
     engines: {node: '>=18'}
 
-  '@inquirer/select@2.3.10':
-    resolution: {integrity: sha512-rr7iR0Zj1YFfgM8IUGimPD9Yukd+n/U63CnYT9kdum6DbRXtMxR45rrreP+EA9ixCnShr+W4xj7suRxC1+8t9g==}
+  '@inquirer/select@2.4.1':
+    resolution: {integrity: sha512-m15ZwV2E2QDy0VbO/BRkVZ6TX6chYU+7K7//R47c3/Xai1d2AESHy4U88G7uq2mR1atl/p4HvMClKASNJvUDRg==}
     engines: {node: '>=18'}
 
-  '@inquirer/type@1.4.0':
-    resolution: {integrity: sha512-AjOqykVyjdJQvtfkNDGUyMYGF8xN50VUxftCQWsOyIo4DFRLr6VQhW0VItGI1JIyQGCGgIpKa7hMMwNhZb4OIw==}
+  '@inquirer/type@1.5.0':
+    resolution: {integrity: sha512-L/UdayX9Z1lLN+itoTKqJ/X4DX5DaWu2Sruwt4XgZzMNv32x4qllbzMX4MbJlz0yxAQtU19UvABGOjmdq1u3qA==}
     engines: {node: '>=18'}
 
   '@ioredis/commands@1.2.0':
@@ -1767,8 +1755,8 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@napi-rs/cli@3.0.0-alpha.58':
-    resolution: {integrity: sha512-BVLF6rS5cjLJNPDktqcYsYax/nYhwHyFXFMJSDEqC0MmqCdiucVmW9pQZkrBKM1N/qddHcNr+GFEjTN0h7ns6Q==}
+  '@napi-rs/cli@3.0.0-alpha.62':
+    resolution: {integrity: sha512-IDUwHAEJZ9ad/s5oyhznrz5ZDcU+SJ6GdP5nb++Wx5MkS4FD9MeS3HfNZdsxkf10pOUPnmvCVFuG4xnLBQYmjw==}
     engines: {node: '>= 16'}
     hasBin: true
     peerDependencies:
@@ -2102,8 +2090,8 @@ packages:
     peerDependencies:
       '@octokit/core': '>=6'
 
-  '@octokit/plugin-request-log@5.3.0':
-    resolution: {integrity: sha512-FiGcyjdtYPlr03ExBk/0ysIlEFIFGJQAVoPPMxL19B24bVSEiZQnVGBunNtaAF1YnvE/EFoDpXmITtRnyCiypQ==}
+  '@octokit/plugin-request-log@5.3.1':
+    resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==}
     engines: {node: '>= 18'}
     peerDependencies:
       '@octokit/core': '>=6'
@@ -2114,16 +2102,16 @@ packages:
     peerDependencies:
       '@octokit/core': '>=6'
 
-  '@octokit/request-error@6.1.2':
-    resolution: {integrity: sha512-sA0oF7aL5wXbNfl+7zgLYJiFZctei9GaIMJlTraJrlQyFaoIYr4MCqPSakzxxGCfm8fET4vn0cQdRFmD7avlDg==}
+  '@octokit/request-error@6.1.4':
+    resolution: {integrity: sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==}
     engines: {node: '>= 18'}
 
-  '@octokit/request@9.1.1':
-    resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==}
+  '@octokit/request@9.1.3':
+    resolution: {integrity: sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==}
     engines: {node: '>= 18'}
 
-  '@octokit/rest@21.0.0':
-    resolution: {integrity: sha512-XudXXOmiIjivdjNZ+fN71NLrnDM00sxSZlhqmPR3v0dVoJwyP628tSlc12xqn8nX3N0965583RBw5GPo6r8u4Q==}
+  '@octokit/rest@21.0.1':
+    resolution: {integrity: sha512-RWA6YU4CqK0h0J6tfYlUFnH3+YgBADlxaHXaKSG+BVr2y4PTfbU2tlKuaQoQZ83qaTbi4CUxLNAmbAqR93A6mQ==}
     engines: {node: '>= 18'}
 
   '@octokit/types@13.5.0':
@@ -2146,11 +2134,11 @@ packages:
   '@redocly/ajv@8.11.0':
     resolution: {integrity: sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==}
 
-  '@redocly/config@0.6.2':
-    resolution: {integrity: sha512-c3K5u64eMnr2ootPcpEI0ioIRLE8QP8ptvLxG9MwAmb2sU8HMRfVwXDU3AZiMVY2w4Ts0mDc+Xv4HTIk8DRqFw==}
+  '@redocly/config@0.7.0':
+    resolution: {integrity: sha512-6GKxTo/9df0654Mtivvr4lQnMOp+pRj9neVywmI5+BwfZLTtkJnj2qB3D6d8FHTr4apsNOf6zTa5FojX0Evh4g==}
 
-  '@redocly/openapi-core@1.18.0':
-    resolution: {integrity: sha512-kcbt7w23pcVYGLnJkh2LZpXF1OX5RDM4DLOtwPug2HvRE8ow/YfY8ZEM1YCFlA41D8rBPBVP918cYeIx4BVUbw==}
+  '@redocly/openapi-core@1.18.1':
+    resolution: {integrity: sha512-y2ZR3aaVF80XRVoFP0Dp2z5DeCOilPTuS7V4HnHIYZdBTfsqzjkO169h5JqAaifnaLsLBhe3YArdgLb7W7wW6Q==}
     engines: {node: '>=14.19.0', npm: '>=7.0.0'}
 
   '@rollup/plugin-alias@5.1.0':
@@ -2180,83 +2168,83 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.17.2':
-    resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==}
+  '@rollup/rollup-android-arm-eabi@4.19.0':
+    resolution: {integrity: sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.17.2':
-    resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==}
+  '@rollup/rollup-android-arm64@4.19.0':
+    resolution: {integrity: sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.17.2':
-    resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==}
+  '@rollup/rollup-darwin-arm64@4.19.0':
+    resolution: {integrity: sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.17.2':
-    resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==}
+  '@rollup/rollup-darwin-x64@4.19.0':
+    resolution: {integrity: sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
-    resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.19.0':
+    resolution: {integrity: sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.17.2':
-    resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==}
+  '@rollup/rollup-linux-arm-musleabihf@4.19.0':
+    resolution: {integrity: sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.17.2':
-    resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==}
+  '@rollup/rollup-linux-arm64-gnu@4.19.0':
+    resolution: {integrity: sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.17.2':
-    resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==}
+  '@rollup/rollup-linux-arm64-musl@4.19.0':
+    resolution: {integrity: sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
-    resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.19.0':
+    resolution: {integrity: sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.17.2':
-    resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==}
+  '@rollup/rollup-linux-riscv64-gnu@4.19.0':
+    resolution: {integrity: sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.17.2':
-    resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==}
+  '@rollup/rollup-linux-s390x-gnu@4.19.0':
+    resolution: {integrity: sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.17.2':
-    resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==}
+  '@rollup/rollup-linux-x64-gnu@4.19.0':
+    resolution: {integrity: sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.17.2':
-    resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==}
+  '@rollup/rollup-linux-x64-musl@4.19.0':
+    resolution: {integrity: sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.17.2':
-    resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==}
+  '@rollup/rollup-win32-arm64-msvc@4.19.0':
+    resolution: {integrity: sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.17.2':
-    resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==}
+  '@rollup/rollup-win32-ia32-msvc@4.19.0':
+    resolution: {integrity: sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.17.2':
-    resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==}
+  '@rollup/rollup-win32-x64-msvc@4.19.0':
+    resolution: {integrity: sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==}
     cpu: [x64]
     os: [win32]
 
@@ -2270,9 +2258,9 @@ packages:
     resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
     engines: {node: '>=10'}
 
-  '@sindresorhus/is@6.3.1':
-    resolution: {integrity: sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==}
-    engines: {node: '>=16'}
+  '@sindresorhus/is@7.0.0':
+    resolution: {integrity: sha512-WDTlVTyvFivSOuyvMeedzg2hdoBLZ3f1uNVuEida2Rl9BrfjrIRjWA/VZIrMRLvSwJYCAlCRA3usDt1THytxWQ==}
+    engines: {node: '>=18'}
 
   '@sindresorhus/merge-streams@4.0.0':
     resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
@@ -2290,8 +2278,8 @@ packages:
   '@sqltools/formatter@1.2.5':
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
 
-  '@syuilo/aiscript@0.17.0':
-    resolution: {integrity: sha512-3JtQ1rWJHMxQ3153zLCXMUOwrOgjPPYGBl0dPHhR0ohm4tn7okMQRugxMCT0t3YxByemb9FfiM6TUjd0tEGxdA==}
+  '@syuilo/aiscript@0.19.0':
+    resolution: {integrity: sha512-ZWG4s1m6RrFjE7NeIMaxFz769YO1jW5ReTrOROrEO4IHheOrjxxJ/Ffe2TUNqX9/XxDloMwfWplKhfSzx8LGMA==}
 
   '@szmarczak/http-timer@4.0.6':
     resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
@@ -2500,9 +2488,6 @@ packages:
   '@types/minimist@1.2.5':
     resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
 
-  '@types/mocha@10.0.7':
-    resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==}
-
   '@types/mute-stream@0.0.4':
     resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
 
@@ -2512,8 +2497,8 @@ packages:
   '@types/node-fetch@2.6.11':
     resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
 
-  '@types/node@20.14.10':
-    resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==}
+  '@types/node@20.14.12':
+    resolution: {integrity: sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==}
 
   '@types/nodemailer@6.4.15':
     resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
@@ -2533,8 +2518,8 @@ packages:
   '@types/prismjs@1.26.4':
     resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==}
 
-  '@types/probe-image-size@7.2.4':
-    resolution: {integrity: sha512-HVqYj3L+D+S/6qpQRv5qMxrD/5pglzZuhP7ZIqgVSZ+Ck4z1TCFkNIRG8WesFueQTqWFTSgkkAl6f8lwxFPQSw==}
+  '@types/probe-image-size@7.2.5':
+    resolution: {integrity: sha512-9Bg6d/GNnjmhMMxadDstwrSlquuuLf0jQuPszbU6n3QUfybif3V/ryD3J2i9iaiC5JB/FU/8E41n88SM/UB+Tg==}
 
   '@types/pug@2.0.10':
     resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
@@ -2623,61 +2608,61 @@ packages:
   '@types/yargs@17.0.32':
     resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==}
 
-  '@vitejs/plugin-vue@5.0.5':
-    resolution: {integrity: sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==}
+  '@vitejs/plugin-vue@5.1.0':
+    resolution: {integrity: sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
       vite: ^5.0.0
       vue: ^3.2.25
 
-  '@volar/language-core@2.4.0-alpha.15':
-    resolution: {integrity: sha512-mt8z4Fm2WxfQYoQHPcKVjLQV6PgPqyKLbkCVY2cr5RSaamqCHjhKEpsFX66aL4D/7oYguuaUw9Bx03Vt0TpIIA==}
+  '@volar/language-core@2.4.0-alpha.18':
+    resolution: {integrity: sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==}
 
-  '@volar/source-map@2.4.0-alpha.15':
-    resolution: {integrity: sha512-8Htngw5TmBY4L3ClDqBGyfLhsB8EmoEXUH1xydyEtEoK0O6NX5ur4Jw8jgvscTlwzizyl/wsN1vn0cQXVbbXYg==}
+  '@volar/source-map@2.4.0-alpha.18':
+    resolution: {integrity: sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==}
 
-  '@volar/typescript@2.4.0-alpha.15':
-    resolution: {integrity: sha512-U3StRBbDuxV6Woa4hvGS4kz3XcOzrWUKgFdEFN+ba1x3eaYg7+ytau8ul05xgA+UNGLXXsKur7fTUhDFyISk0w==}
+  '@volar/typescript@2.4.0-alpha.18':
+    resolution: {integrity: sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==}
 
-  '@vue/compiler-core@3.4.31':
-    resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==}
+  '@vue/compiler-core@3.4.33':
+    resolution: {integrity: sha512-MoIREbkdPQlnGfSKDMgzTqzqx5nmEjIc0ydLVYlTACGBsfvOJ4tHSbZXKVF536n6fB+0eZaGEOqsGThPpdvF5A==}
 
-  '@vue/compiler-dom@3.4.31':
-    resolution: {integrity: sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==}
+  '@vue/compiler-dom@3.4.33':
+    resolution: {integrity: sha512-GzB8fxEHKw0gGet5BKlpfXEqoBnzSVWwMnT+dc25wE7pFEfrU/QsvjZMP9rD4iVXHBBoemTct8mN0GJEI6ZX5A==}
 
   '@vue/compiler-sfc@2.7.16':
     resolution: {integrity: sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==}
 
-  '@vue/compiler-sfc@3.4.31':
-    resolution: {integrity: sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==}
+  '@vue/compiler-sfc@3.4.33':
+    resolution: {integrity: sha512-7rk7Vbkn21xMwIUpHQR4hCVejwE6nvhBOiDgoBcR03qvGqRKA7dCBSsHZhwhYUsmjlbJ7OtD5UFIyhP6BY+c8A==}
 
-  '@vue/compiler-ssr@3.4.31':
-    resolution: {integrity: sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==}
+  '@vue/compiler-ssr@3.4.33':
+    resolution: {integrity: sha512-0WveC9Ai+eT/1b6LCV5IfsufBZ0HP7pSSTdDjcuW302tTEgoBw8rHVHKPbGUtzGReUFCRXbv6zQDDgucnV2WzQ==}
 
-  '@vue/language-core@2.0.26':
-    resolution: {integrity: sha512-/lt6SfQ3O1yDAhPsnLv9iSUgXd1dMHqUm/t3RctfqjuwQf1LnftZ414X3UBn6aXT4MiwXWtbNJ4Z0NZWwDWgJQ==}
+  '@vue/language-core@2.0.28':
+    resolution: {integrity: sha512-0z4tyCCaqqPbdyz0T4yTFQeLpCo4TOM/ZHAC3geGLHeCiFAjVbROB9PiEtrXR1AoLObqUPFHSmKZeWtEMssSqw==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
 
-  '@vue/reactivity@3.4.31':
-    resolution: {integrity: sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==}
+  '@vue/reactivity@3.4.33':
+    resolution: {integrity: sha512-B24QIelahDbyHipBgbUItQblbd4w5HpG3KccL+YkGyo3maXyS253FzcTR3pSz739OTphmzlxP7JxEMWBpewilA==}
 
-  '@vue/runtime-core@3.4.31':
-    resolution: {integrity: sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==}
+  '@vue/runtime-core@3.4.33':
+    resolution: {integrity: sha512-6wavthExzT4iAxpe8q37/rDmf44nyOJGISJPxCi9YsQO+8w9v0gLCFLfH5TzD1V1AYrTAdiF4Y1cgUmP68jP6w==}
 
-  '@vue/runtime-dom@3.4.31':
-    resolution: {integrity: sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==}
+  '@vue/runtime-dom@3.4.33':
+    resolution: {integrity: sha512-iHsMCUSFJ+4z432Bn9kZzHX+zOXa6+iw36DaVRmKYZpPt9jW9riF32SxNwB124i61kp9+AZtheQ/mKoJLerAaQ==}
 
-  '@vue/server-renderer@3.4.31':
-    resolution: {integrity: sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==}
+  '@vue/server-renderer@3.4.33':
+    resolution: {integrity: sha512-jTH0d6gQcaYideFP/k0WdEu8PpRS9MF8d0b6SfZzNi+ap972pZ0TNIeTaESwdOtdY0XPVj54XEJ6K0wXxir4fw==}
     peerDependencies:
-      vue: 3.4.31
+      vue: 3.4.33
 
-  '@vue/shared@3.4.31':
-    resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==}
+  '@vue/shared@3.4.33':
+    resolution: {integrity: sha512-aoRY0jQk3A/cuvdkodTrM4NMfxco8n55eG4H7ML/CRy7OryHfiqvug4xrCBBMbbN+dvXAetDDwZW9DXWWjBntA==}
 
   '@webassemblyjs/ast@1.12.1':
     resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==}
@@ -2780,10 +2765,6 @@ packages:
   ajv@8.17.1:
     resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
 
-  ansi-colors@4.1.3:
-    resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
-    engines: {node: '>=6'}
-
   ansi-escapes@4.3.2:
     resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
     engines: {node: '>=8'}
@@ -2890,8 +2871,8 @@ packages:
     resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
     engines: {node: '>= 0.4'}
 
-  aws-sdk@2.1659.0:
-    resolution: {integrity: sha512-WOoy5DdWW4kpQuxjWiQdoSDR+dT/HeAUwjb6b+8taEMZzvUzp3fmdDwdryUTlLWGxrnb7ru2yu5pryjhPOzANg==}
+  aws-sdk@2.1662.0:
+    resolution: {integrity: sha512-ISKN3yxjQtjIMOBU2b3zO8Qrxd9UqdYlJlxSQUn5/4jfqCSad/s1mF66Cwzl5UByJtvFn0xoJORIZ7K6BTLuuw==}
     engines: {node: '>= 10.0.0'}
 
   axios@0.24.0:
@@ -2979,9 +2960,6 @@ packages:
   broadcast-channel@7.0.0:
     resolution: {integrity: sha512-a2tW0Ia1pajcPBOGUF2jXlDnvE9d5/dg6BG9h60OmRUcZVr/veUrU8vEQFwwQIhwG3KVzYwSk3v2nRRGFgQDXQ==}
 
-  browser-stdout@1.3.1:
-    resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
-
   browserify-zlib@0.1.4:
     resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==}
 
@@ -3086,8 +3064,8 @@ packages:
     resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
     engines: {node: '>=10'}
 
-  caniuse-lite@1.0.30001641:
-    resolution: {integrity: sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==}
+  caniuse-lite@1.0.30001642:
+    resolution: {integrity: sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==}
 
   canonicalize@1.0.8:
     resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==}
@@ -3265,8 +3243,8 @@ packages:
     resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
     engines: {node: ^12.20.0 || >=14}
 
-  compare-versions@6.1.0:
-    resolution: {integrity: sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==}
+  compare-versions@6.1.1:
+    resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
 
   compress-commons@6.0.2:
     resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
@@ -3560,8 +3538,8 @@ packages:
   date-fns@3.6.0:
     resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
 
-  dayjs@1.11.11:
-    resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==}
+  dayjs@1.11.12:
+    resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==}
 
   de-indent@1.0.2:
     resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
@@ -3608,10 +3586,6 @@ packages:
     resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
     engines: {node: '>=0.10.0'}
 
-  decamelize@4.0.0:
-    resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
-    engines: {node: '>=10'}
-
   decimal.js@10.4.3:
     resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
 
@@ -3653,10 +3627,6 @@ packages:
   deep-equal@1.0.1:
     resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
 
-  deepl-node@1.13.0:
-    resolution: {integrity: sha512-pm8Al5B+/fRHiIKoreoSmv2RlXidF18+CznhtLILiYcj3EbxZpIhxWO8cgXCCsCTrUDMAbScIl8CuH3AqLPpGg==}
-    engines: {node: '>=12.0'}
-
   deepmerge@4.3.1:
     resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
     engines: {node: '>=0.10.0'}
@@ -3711,10 +3681,6 @@ packages:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
 
-  diff@5.2.0:
-    resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
-    engines: {node: '>=0.3.1'}
-
   dijkstrajs@1.0.3:
     resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
 
@@ -3782,8 +3748,8 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true
 
-  electron-to-chromium@1.4.824:
-    resolution: {integrity: sha512-GTQnZOP1v0wCuoWzKOxL8rurg9T13QRYISkoICGaZzskBf9laC3V8g9BHTpJv+j9vBRcKOulbGXwMzuzNdVrAA==}
+  electron-to-chromium@1.4.832:
+    resolution: {integrity: sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==}
 
   emittery@0.13.1:
     resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@@ -4005,8 +3971,8 @@ packages:
     resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
     engines: {node: '>=18'}
 
-  file-type@19.1.1:
-    resolution: {integrity: sha512-FF4rVPjylL7HkybFBpnBfcnpdi4MGNSFuk4s4VvTdt1wm9tVMdGmtBhvXyz+nh8565FJ5qDUMIPXR+WHLrfHew==}
+  file-type@19.3.0:
+    resolution: {integrity: sha512-mROwiKLZf/Kwa/2Rol+OOZQn1eyTkPB3ZTwC0ExY6OLFCbgxHYZvBm7xI77NvfZFMKBsmuXfmLJnD4eEftEhrA==}
     engines: {node: '>=18'}
 
   file-type@3.9.0:
@@ -4032,17 +3998,9 @@ packages:
     resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
     engines: {node: '>=8'}
 
-  find-up@5.0.0:
-    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
-    engines: {node: '>=10'}
-
   fix-esm@1.0.1:
     resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==}
 
-  flat@5.0.2:
-    resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
-    hasBin: true
-
   fluent-ffmpeg@2.1.3:
     resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
     engines: {node: '>=18'}
@@ -4076,10 +4034,6 @@ packages:
     resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
     engines: {node: '>= 18'}
 
-  form-data@3.0.1:
-    resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
-    engines: {node: '>= 6'}
-
   form-data@4.0.0:
     resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
     engines: {node: '>= 6'}
@@ -4153,10 +4107,6 @@ packages:
     resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
     engines: {node: '>=10'}
 
-  get-stream@8.0.1:
-    resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
-    engines: {node: '>=16'}
-
   get-stream@9.0.1:
     resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
     engines: {node: '>=18'}
@@ -4179,11 +4129,6 @@ packages:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
     deprecated: Glob versions prior to v9 are no longer supported
 
-  glob@8.1.0:
-    resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
-    engines: {node: '>=12'}
-    deprecated: Glob versions prior to v9 are no longer supported
-
   globals@11.12.0:
     resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
     engines: {node: '>=4'}
@@ -4199,8 +4144,8 @@ packages:
     resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
     engines: {node: '>=10.19.0'}
 
-  got@14.4.1:
-    resolution: {integrity: sha512-IvDJbJBUeexX74xNQuMIVgCRRuNOm5wuK+OC3Dc2pnSoh1AOmgc7JVj7WC+cJ4u0aPcO9KZ2frTXcqK4W/5qTQ==}
+  got@14.4.2:
+    resolution: {integrity: sha512-+Te/qEZ6hr7i+f0FNgXx/6WQteSM/QqueGvxeYQQFm0GDfoxLVJ/oiwUKYMTeioColWUTdewZ06hmrBjw6F7tw==}
     engines: {node: '>=20'}
 
   graceful-fs@4.2.11:
@@ -4386,8 +4331,8 @@ packages:
   ini@1.3.8:
     resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
 
-  inquirer@10.0.1:
-    resolution: {integrity: sha512-XgthhRIn0Ci9JdGJpUo2EtpPfaczbooZbGTN+FTzSCyUb7YHJcPPnuSXfeG5903bJMy3OyEoVTQMnvO4Ly5tFg==}
+  inquirer@10.0.4:
+    resolution: {integrity: sha512-tBci3o6smIDw5GmUWrPGgeom3XKGJaxFdN1X5zYt9CtLdIw4hkOS6a+d5x80BIKeaz6+2gxMCStzZsD06v+q4g==}
     engines: {node: '>=18'}
 
   insert-text-at-cursor@0.3.0:
@@ -4442,8 +4387,8 @@ packages:
     resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
     engines: {node: '>= 0.4'}
 
-  is-core-module@2.14.0:
-    resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==}
+  is-core-module@2.15.0:
+    resolution: {integrity: sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==}
     engines: {node: '>= 0.4'}
 
   is-deflate@1.0.0:
@@ -4498,10 +4443,6 @@ packages:
     resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
     engines: {node: '>=0.10.0'}
 
-  is-plain-obj@2.1.0:
-    resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
-    engines: {node: '>=8'}
-
   is-plain-obj@4.1.0:
     resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
     engines: {node: '>=12'}
@@ -4591,8 +4532,8 @@ packages:
   jackspeak@3.4.3:
     resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
 
-  jake@10.9.1:
-    resolution: {integrity: sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==}
+  jake@10.9.2:
+    resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -4779,8 +4720,8 @@ packages:
     resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==}
     engines: {node: '>=0.1.90'}
 
-  jsdom@24.1.0:
-    resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==}
+  jsdom@24.1.1:
+    resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==}
     engines: {node: '>=18'}
     peerDependencies:
       canvas: ^2.11.2
@@ -4964,10 +4905,6 @@ packages:
     resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
     engines: {node: '>=8'}
 
-  locate-path@6.0.0:
-    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
-    engines: {node: '>=10'}
-
   lodash-es@4.17.21:
     resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
 
@@ -5023,10 +4960,6 @@ packages:
     resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
     engines: {node: '>=10'}
 
-  loglevel@1.9.1:
-    resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==}
-    engines: {node: '>= 0.6.0'}
-
   long@5.2.3:
     resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
 
@@ -5172,11 +5105,6 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  mocha@10.6.0:
-    resolution: {integrity: sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==}
-    engines: {node: '>= 14.0.0'}
-    hasBin: true
-
   mock-socket@9.3.1:
     resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
     engines: {node: '>= 8'}
@@ -5197,8 +5125,8 @@ packages:
     resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
     hasBin: true
 
-  msgpackr@1.10.2:
-    resolution: {integrity: sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==}
+  msgpackr@1.11.0:
+    resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==}
 
   muggle-string@0.4.1:
     resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
@@ -5280,8 +5208,8 @@ packages:
   node-int64@0.4.0:
     resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
 
-  node-releases@2.0.14:
-    resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+  node-releases@2.0.17:
+    resolution: {integrity: sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==}
 
   nodemailer@6.9.14:
     resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==}
@@ -5354,9 +5282,6 @@ packages:
   only@0.0.2:
     resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
 
-  opencc-js@1.0.5:
-    resolution: {integrity: sha512-LD+1SoNnZdlRwtYTjnQdFrSVCAaYpuDqL5CkmOaHOkKoKh7mFxUicLTRVNLU5C+Jmi1vXQ3QL4jWdgSaa4sKjg==}
-
   opencollective-postinstall@2.0.3:
     resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
     hasBin: true
@@ -5396,10 +5321,6 @@ packages:
     resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
     engines: {node: '>=8'}
 
-  p-locate@5.0.0:
-    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
-    engines: {node: '>=10'}
-
   p-queue@6.6.2:
     resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
     engines: {node: '>=8'}
@@ -5481,8 +5402,8 @@ packages:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
 
-  peek-readable@5.1.1:
-    resolution: {integrity: sha512-4hEOSH7KeEaZpMDF/xfm1W9fS5rT7Ett3BkXWHqAEzRLLwLaHkwOL+GvvpIEh9UrvX9BDhzfkvteslgraoH69w==}
+  peek-readable@5.1.3:
+    resolution: {integrity: sha512-kCsc9HwH5RgVA3H3VqkWFyGQwsxUxLdiSX1d5nqAm7hnMFjNFX1VhBLmJoUY0hZNc8gmDNgBkLjfhiWPsziXWA==}
     engines: {node: '>=14.16'}
 
   peek-stream@1.1.3:
@@ -5596,8 +5517,8 @@ packages:
     resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==}
     engines: {node: '>=14.19.0'}
 
-  pnpm@9.5.0:
-    resolution: {integrity: sha512-FAA2gwEkYY1iSiGHtQ0EKJ1aCH8ybJ7fwMzXM9dsT1LDoxPU/BSHlKKp2BVTAWAE5nQujPhQZwJopzh/wiDJAw==}
+  pnpm@9.6.0:
+    resolution: {integrity: sha512-ONxvuo26NbOTQLlwARLC/h4S8QsXE0cVpKqYzPe7A152/Zgc8Ls4TfqY+NavVIHCvvL0Jmokv6IMNOtxR84LXg==}
     engines: {node: '>=18.12'}
     hasBin: true
 
@@ -5951,8 +5872,8 @@ packages:
   rndstr@1.0.0:
     resolution: {integrity: sha512-3KN+BHTiHcsyW1qjRw3Xhms8TQfTIN4fUVgqqJpj6FnmuCnto5/lLyppSmGfdTmOiKDWeuXU4XPp58I9fsoWFQ==}
 
-  rollup@4.17.2:
-    resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==}
+  rollup@4.19.0:
+    resolution: {integrity: sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -6027,8 +5948,8 @@ packages:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
 
-  semver@7.6.2:
-    resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
+  semver@7.6.3:
+    resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -6227,9 +6148,9 @@ packages:
   strnum@1.0.5:
     resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
 
-  strtok3@7.1.0:
-    resolution: {integrity: sha512-19dQEwG6Jd+VabjPRyBhymIF069vZiqWSZa2jJBoKJTsqGKnTxowGoQaLnz+yLARfDI041IUQekyPUMWElOgsQ==}
-    engines: {node: '>=14.16'}
+  strtok3@8.0.0:
+    resolution: {integrity: sha512-YzsSP+kli3q1tTA04HsfY1GqIapi3vEMN38jJ+aLpFyoev0onI/RuZWBGkQgc7ORynb3LW4cSOP3XtsKV21X6Q==}
+    engines: {node: '>=16'}
 
   summaly@2.7.0:
     resolution: {integrity: sha512-pEz9LL8Gp0oPIQfn6TrnBCcv/HkFE14hxhH3W6LPGdopXlPXjRcMlDMJaO+VupUNMOGaMjCsjq7+0rWnu8sp7w==}
@@ -6254,8 +6175,8 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  swiper@11.1.4:
-    resolution: {integrity: sha512-1n7kbYJB2dFEpUHRFszq7gys/ofIBrMNibwTiMvPHwneKND/t9kImnHt6CfGPScMHgI+dWMbGTycCKGMoOO1KA==}
+  swiper@11.1.7:
+    resolution: {integrity: sha512-2EpQvhgKb+DNbi8/i9uRXhddivcMZQxca341t2NZYV1xroCR2p4YtYd3azuqRQ4OEBGcG4nv3aN24O80bMipow==}
     engines: {node: '>= 4.7.0'}
 
   symbol-tree@3.2.4:
@@ -6295,8 +6216,8 @@ packages:
       uglify-js:
         optional: true
 
-  terser@5.31.2:
-    resolution: {integrity: sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw==}
+  terser@5.31.3:
+    resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -6395,8 +6316,8 @@ packages:
     resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
     engines: {node: '>=8'}
 
-  ts-jest@29.2.2:
-    resolution: {integrity: sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==}
+  ts-jest@29.2.3:
+    resolution: {integrity: sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -6486,8 +6407,8 @@ packages:
     resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
     engines: {node: '>=8'}
 
-  type-fest@4.21.0:
-    resolution: {integrity: sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==}
+  type-fest@4.23.0:
+    resolution: {integrity: sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==}
     engines: {node: '>=16'}
 
   type-is@1.6.18:
@@ -6561,13 +6482,13 @@ packages:
       typeorm-aurora-data-api-driver:
         optional: true
 
-  typescript@5.5.3:
-    resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
+  typescript@5.5.4:
+    resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
     engines: {node: '>=14.17'}
     hasBin: true
 
-  uint8array-extras@1.3.0:
-    resolution: {integrity: sha512-npBAT0ZIX6mAIG7SF6G4LF1BIoRx3h+HVajSplHx0XmOD0Ug4qio5Yhcajn72i5OEj/qkk1OFaYh2PhqHBV33w==}
+  uint8array-extras@1.4.0:
+    resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==}
     engines: {node: '>=18'}
 
   ulid@2.3.0:
@@ -6680,8 +6601,8 @@ packages:
     peerDependencies:
       vite: '>=2.0.0'
 
-  vite@5.3.3:
-    resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==}
+  vite@5.3.4:
+    resolution: {integrity: sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -6736,8 +6657,8 @@ packages:
   vue-template-compiler@2.7.16:
     resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
 
-  vue-tsc@2.0.26:
-    resolution: {integrity: sha512-tOhuwy2bIXbMhz82ef37qeiaQHMXKQkD6mOF6CCPl3/uYtST3l6fdNyfMxipudrQTxTfXVPlgJdMENBFfC1CfQ==}
+  vue-tsc@2.0.28:
+    resolution: {integrity: sha512-PQ/OFDM3NtQVMThaVlQf8plyL0j7UGdak4lb1KkUOSL0uyx/F9Liu6aOclgHiMMBKNGIjJWoiFh3HjIdV6DS/Q==}
     hasBin: true
     peerDependencies:
       typescript: '>=5.0.0'
@@ -6746,8 +6667,8 @@ packages:
     resolution: {integrity: sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==}
     deprecated: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.
 
-  vue@3.4.31:
-    resolution: {integrity: sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==}
+  vue@3.4.33:
+    resolution: {integrity: sha512-VdMCWQOummbhctl4QFMcW6eNtXHsFyDlX60O/tsSQuCcuDOnJ1qPOhhVla65Niece7xq/P2zyZReIO5mP+LGTQ==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
@@ -6835,9 +6756,6 @@ packages:
     resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
     engines: {node: '>= 10.0.0'}
 
-  workerpool@6.5.1:
-    resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
-
   wrap-ansi@6.2.0:
     resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
     engines: {node: '>=8'}
@@ -6931,10 +6849,6 @@ packages:
     resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
     engines: {node: '>=12'}
 
-  yargs-unparser@2.0.0:
-    resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
-    engines: {node: '>=10'}
-
   yargs@15.4.1:
     resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
     engines: {node: '>=8'}
@@ -6992,66 +6906,66 @@ snapshots:
       '@babel/highlight': 7.24.7
       picocolors: 1.0.1
 
-  '@babel/compat-data@7.24.7': {}
+  '@babel/compat-data@7.24.9': {}
 
-  '@babel/core@7.24.7':
+  '@babel/core@7.24.9':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@babel/code-frame': 7.24.7
-      '@babel/generator': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helpers': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/generator': 7.24.10
+      '@babel/helper-compilation-targets': 7.24.8
+      '@babel/helper-module-transforms': 7.24.9(@babel/core@7.24.9)
+      '@babel/helpers': 7.24.8
+      '@babel/parser': 7.24.8
       '@babel/template': 7.24.7
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/traverse': 7.24.8
+      '@babel/types': 7.24.9
       convert-source-map: 2.0.0
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/generator@7.24.7':
+  '@babel/generator@7.24.10':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
       jsesc: 2.5.2
 
-  '@babel/helper-compilation-targets@7.24.7':
+  '@babel/helper-compilation-targets@7.24.8':
     dependencies:
-      '@babel/compat-data': 7.24.7
-      '@babel/helper-validator-option': 7.24.7
+      '@babel/compat-data': 7.24.9
+      '@babel/helper-validator-option': 7.24.8
       browserslist: 4.23.2
       lru-cache: 5.1.1
       semver: 6.3.1
 
   '@babel/helper-environment-visitor@7.24.7':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@babel/helper-function-name@7.24.7':
     dependencies:
       '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@babel/helper-hoist-variables@7.24.7':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@babel/helper-module-imports@7.24.7':
     dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/traverse': 7.24.8
+      '@babel/types': 7.24.9
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)':
+  '@babel/helper-module-transforms@7.24.9(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-module-imports': 7.24.7
       '@babel/helper-simple-access': 7.24.7
@@ -7060,29 +6974,29 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-plugin-utils@7.24.7': {}
+  '@babel/helper-plugin-utils@7.24.8': {}
 
   '@babel/helper-simple-access@7.24.7':
     dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/traverse': 7.24.8
+      '@babel/types': 7.24.9
     transitivePeerDependencies:
       - supports-color
 
   '@babel/helper-split-export-declaration@7.24.7':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
-  '@babel/helper-string-parser@7.24.7': {}
+  '@babel/helper-string-parser@7.24.8': {}
 
   '@babel/helper-validator-identifier@7.24.7': {}
 
-  '@babel/helper-validator-option@7.24.7': {}
+  '@babel/helper-validator-option@7.24.8': {}
 
-  '@babel/helpers@7.24.7':
+  '@babel/helpers@7.24.8':
     dependencies:
       '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@babel/highlight@7.24.7':
     dependencies:
@@ -7091,96 +7005,96 @@ snapshots:
       js-tokens: 4.0.0
       picocolors: 1.0.1
 
-  '@babel/parser@7.24.7':
+  '@babel/parser@7.24.8':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
-  '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.24.7)':
+  '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7)
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.9)
 
-  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-plugin-utils': 7.24.8
 
-  '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-transform-modules-commonjs@7.24.8(@babel/core@7.24.9)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/helper-module-transforms': 7.24.9(@babel/core@7.24.9)
+      '@babel/helper-plugin-utils': 7.24.8
       '@babel/helper-simple-access': 7.24.7
     transitivePeerDependencies:
       - supports-color
@@ -7192,27 +7106,27 @@ snapshots:
   '@babel/template@7.24.7':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
 
-  '@babel/traverse@7.24.7':
+  '@babel/traverse@7.24.8':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/generator': 7.24.7
+      '@babel/generator': 7.24.10
       '@babel/helper-environment-visitor': 7.24.7
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@8.1.1)
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
+      debug: 4.3.5
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/types@7.24.7':
+  '@babel/types@7.24.9':
     dependencies:
-      '@babel/helper-string-parser': 7.24.7
+      '@babel/helper-string-parser': 7.24.8
       '@babel/helper-validator-identifier': 7.24.7
       to-fast-properties: 2.0.0
 
@@ -7249,15 +7163,15 @@ snapshots:
   '@biomejs/cli-win32-x64@1.8.3':
     optional: true
 
-  '@bull-board/api@5.21.0(@bull-board/ui@5.21.0)':
+  '@bull-board/api@5.21.1(@bull-board/ui@5.21.1)':
     dependencies:
-      '@bull-board/ui': 5.21.0
+      '@bull-board/ui': 5.21.1
       redis-info: 3.1.0
 
-  '@bull-board/koa@5.21.0(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)':
+  '@bull-board/koa@5.21.1(@types/koa@2.15.0)(lodash@4.17.21)(pug@3.0.3)':
     dependencies:
-      '@bull-board/api': 5.21.0(@bull-board/ui@5.21.0)
-      '@bull-board/ui': 5.21.0
+      '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1)
+      '@bull-board/ui': 5.21.1
       ejs: 3.1.10
       koa: 2.15.3
       koa-mount: 4.0.0
@@ -7320,9 +7234,9 @@ snapshots:
       - walrus
       - whiskers
 
-  '@bull-board/ui@5.21.0':
+  '@bull-board/ui@5.21.1':
     dependencies:
-      '@bull-board/api': 5.21.0(@bull-board/ui@5.21.0)
+      '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1)
 
   '@cbor-extract/cbor-extract-darwin-arm64@2.2.0':
     optional: true
@@ -7593,25 +7507,25 @@ snapshots:
   '@img/sharp-win32-x64@0.33.4':
     optional: true
 
-  '@inquirer/checkbox@2.3.10':
+  '@inquirer/checkbox@2.4.1':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/figures': 1.0.3
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/figures': 1.0.4
+      '@inquirer/type': 1.5.0
       ansi-escapes: 4.3.2
       yoctocolors-cjs: 2.1.2
 
-  '@inquirer/confirm@3.1.14':
+  '@inquirer/confirm@3.1.16':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
 
-  '@inquirer/core@9.0.2':
+  '@inquirer/core@9.0.4':
     dependencies:
-      '@inquirer/figures': 1.0.3
-      '@inquirer/type': 1.4.0
+      '@inquirer/figures': 1.0.4
+      '@inquirer/type': 1.5.0
       '@types/mute-stream': 0.0.4
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/wrap-ansi': 3.0.0
       ansi-escapes: 4.3.2
       cli-spinners: 2.9.2
@@ -7622,63 +7536,63 @@ snapshots:
       wrap-ansi: 6.2.0
       yoctocolors-cjs: 2.1.2
 
-  '@inquirer/editor@2.1.14':
+  '@inquirer/editor@2.1.16':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
       external-editor: 3.1.0
 
-  '@inquirer/expand@2.1.14':
+  '@inquirer/expand@2.1.16':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
       yoctocolors-cjs: 2.1.2
 
-  '@inquirer/figures@1.0.3': {}
+  '@inquirer/figures@1.0.4': {}
 
-  '@inquirer/input@2.2.1':
+  '@inquirer/input@2.2.3':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
 
-  '@inquirer/number@1.0.2':
+  '@inquirer/number@1.0.4':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
 
-  '@inquirer/password@2.1.14':
+  '@inquirer/password@2.1.16':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
       ansi-escapes: 4.3.2
 
-  '@inquirer/prompts@5.1.2':
+  '@inquirer/prompts@5.2.1':
     dependencies:
-      '@inquirer/checkbox': 2.3.10
-      '@inquirer/confirm': 3.1.14
-      '@inquirer/editor': 2.1.14
-      '@inquirer/expand': 2.1.14
-      '@inquirer/input': 2.2.1
-      '@inquirer/number': 1.0.2
-      '@inquirer/password': 2.1.14
-      '@inquirer/rawlist': 2.1.14
-      '@inquirer/select': 2.3.10
+      '@inquirer/checkbox': 2.4.1
+      '@inquirer/confirm': 3.1.16
+      '@inquirer/editor': 2.1.16
+      '@inquirer/expand': 2.1.16
+      '@inquirer/input': 2.2.3
+      '@inquirer/number': 1.0.4
+      '@inquirer/password': 2.1.16
+      '@inquirer/rawlist': 2.1.16
+      '@inquirer/select': 2.4.1
 
-  '@inquirer/rawlist@2.1.14':
+  '@inquirer/rawlist@2.1.16':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/type': 1.5.0
       yoctocolors-cjs: 2.1.2
 
-  '@inquirer/select@2.3.10':
+  '@inquirer/select@2.4.1':
     dependencies:
-      '@inquirer/core': 9.0.2
-      '@inquirer/figures': 1.0.3
-      '@inquirer/type': 1.4.0
+      '@inquirer/core': 9.0.4
+      '@inquirer/figures': 1.0.4
+      '@inquirer/type': 1.5.0
       ansi-escapes: 4.3.2
       yoctocolors-cjs: 2.1.2
 
-  '@inquirer/type@1.4.0':
+  '@inquirer/type@1.5.0':
     dependencies:
       mute-stream: 1.0.0
 
@@ -7706,27 +7620,27 @@ snapshots:
   '@jest/console@29.7.0':
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
       slash: 3.0.0
 
-  '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))':
+  '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))':
     dependencies:
       '@jest/console': 29.7.0
       '@jest/reporters': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.9.0
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      jest-config: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -7751,7 +7665,7 @@ snapshots:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       jest-mock: 29.7.0
 
   '@jest/expect-utils@29.7.0':
@@ -7769,7 +7683,7 @@ snapshots:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -7791,7 +7705,7 @@ snapshots:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.25
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       collect-v8-coverage: 1.0.2
       exit: 0.1.2
@@ -7838,7 +7752,7 @@ snapshots:
 
   '@jest/transform@29.7.0':
     dependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.25
       babel-plugin-istanbul: 6.1.1
@@ -7861,7 +7775,7 @@ snapshots:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/yargs': 17.0.32
       chalk: 4.1.2
 
@@ -7905,7 +7819,7 @@ snapshots:
 
   '@koa/router@12.0.1':
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       http-errors: 2.0.0
       koa-compose: 4.1.0
       methods: 1.1.2
@@ -7915,17 +7829,17 @@ snapshots:
 
   '@kurkle/color@0.3.2': {}
 
-  '@ladjs/consolidate@1.0.4(@babel/core@7.24.7)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
+  '@ladjs/consolidate@1.0.4(@babel/core@7.24.9)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
     optionalDependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       ejs: 3.1.10
       lodash: 4.17.21
       pug: 3.0.3
 
-  '@ladjs/koa-views@9.0.0(@babel/core@7.24.7)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
+  '@ladjs/koa-views@9.0.0(@babel/core@7.24.9)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
     dependencies:
-      '@ladjs/consolidate': 1.0.4(@babel/core@7.24.7)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
-      debug: 4.3.5(supports-color@8.1.1)
+      '@ladjs/consolidate': 1.0.4(@babel/core@7.24.9)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
+      debug: 4.3.5
       get-paths: 0.0.7
       koa-send: 5.0.1
       mz: 2.7.0
@@ -8004,18 +7918,18 @@ snapshots:
   '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
     optional: true
 
-  '@napi-rs/cli@3.0.0-alpha.58(@emnapi/runtime@1.2.0)':
+  '@napi-rs/cli@3.0.0-alpha.62(@emnapi/runtime@1.2.0)':
     dependencies:
       '@napi-rs/cross-toolchain': 0.0.16
       '@napi-rs/wasm-tools': 0.0.2
-      '@octokit/rest': 21.0.0
+      '@octokit/rest': 21.0.1
       clipanion: 3.2.1(typanion@3.14.0)
       colorette: 2.0.20
-      debug: 4.3.5(supports-color@8.1.1)
-      inquirer: 10.0.1
+      debug: 4.3.5
+      inquirer: 10.0.4
       js-yaml: 4.1.0
       lodash-es: 4.17.21
-      semver: 7.6.2
+      semver: 7.6.3
       toml: 3.0.0
       typanion: 3.14.0
       wasm-sjlj: 1.0.5
@@ -8034,7 +7948,7 @@ snapshots:
     dependencies:
       '@napi-rs/lzma': 1.3.1
       '@napi-rs/tar': 0.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
     transitivePeerDependencies:
       - supports-color
 
@@ -8244,8 +8158,8 @@ snapshots:
     dependencies:
       '@octokit/auth-token': 5.1.1
       '@octokit/graphql': 8.1.1
-      '@octokit/request': 9.1.1
-      '@octokit/request-error': 6.1.2
+      '@octokit/request': 9.1.3
+      '@octokit/request-error': 6.1.4
       '@octokit/types': 13.5.0
       before-after-hook: 3.0.2
       universal-user-agent: 7.0.2
@@ -8257,7 +8171,7 @@ snapshots:
 
   '@octokit/graphql@8.1.1':
     dependencies:
-      '@octokit/request': 9.1.1
+      '@octokit/request': 9.1.3
       '@octokit/types': 13.5.0
       universal-user-agent: 7.0.2
 
@@ -8268,7 +8182,7 @@ snapshots:
       '@octokit/core': 6.1.2
       '@octokit/types': 13.5.0
 
-  '@octokit/plugin-request-log@5.3.0(@octokit/core@6.1.2)':
+  '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.2)':
     dependencies:
       '@octokit/core': 6.1.2
 
@@ -8277,22 +8191,22 @@ snapshots:
       '@octokit/core': 6.1.2
       '@octokit/types': 13.5.0
 
-  '@octokit/request-error@6.1.2':
+  '@octokit/request-error@6.1.4':
     dependencies:
       '@octokit/types': 13.5.0
 
-  '@octokit/request@9.1.1':
+  '@octokit/request@9.1.3':
     dependencies:
       '@octokit/endpoint': 10.1.1
-      '@octokit/request-error': 6.1.2
+      '@octokit/request-error': 6.1.4
       '@octokit/types': 13.5.0
       universal-user-agent: 7.0.2
 
-  '@octokit/rest@21.0.0':
+  '@octokit/rest@21.0.1':
     dependencies:
       '@octokit/core': 6.1.2
       '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2)
-      '@octokit/plugin-request-log': 5.3.0(@octokit/core@6.1.2)
+      '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.2)
       '@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2)
 
   '@octokit/types@13.5.0':
@@ -8319,12 +8233,12 @@ snapshots:
       require-from-string: 2.0.2
       uri-js: 4.4.1
 
-  '@redocly/config@0.6.2': {}
+  '@redocly/config@0.7.0': {}
 
-  '@redocly/openapi-core@1.18.0':
+  '@redocly/openapi-core@1.18.1':
     dependencies:
       '@redocly/ajv': 8.11.0
-      '@redocly/config': 0.6.2
+      '@redocly/config': 0.7.0
       colorette: 1.4.0
       https-proxy-agent: 7.0.5
       js-levenshtein: 1.1.6
@@ -8338,72 +8252,72 @@ snapshots:
       - encoding
       - supports-color
 
-  '@rollup/plugin-alias@5.1.0(rollup@4.17.2)':
+  '@rollup/plugin-alias@5.1.0(rollup@4.19.0)':
     dependencies:
       slash: 4.0.0
     optionalDependencies:
-      rollup: 4.17.2
+      rollup: 4.19.0
 
-  '@rollup/plugin-json@6.1.0(rollup@4.17.2)':
+  '@rollup/plugin-json@6.1.0(rollup@4.19.0)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.17.2)
+      '@rollup/pluginutils': 5.1.0(rollup@4.19.0)
     optionalDependencies:
-      rollup: 4.17.2
+      rollup: 4.19.0
 
-  '@rollup/pluginutils@5.1.0(rollup@4.17.2)':
+  '@rollup/pluginutils@5.1.0(rollup@4.19.0)':
     dependencies:
       '@types/estree': 1.0.5
       estree-walker: 2.0.2
       picomatch: 2.3.1
     optionalDependencies:
-      rollup: 4.17.2
+      rollup: 4.19.0
 
-  '@rollup/rollup-android-arm-eabi@4.17.2':
+  '@rollup/rollup-android-arm-eabi@4.19.0':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.17.2':
+  '@rollup/rollup-android-arm64@4.19.0':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.17.2':
+  '@rollup/rollup-darwin-arm64@4.19.0':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.17.2':
+  '@rollup/rollup-darwin-x64@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
+  '@rollup/rollup-linux-arm-gnueabihf@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.17.2':
+  '@rollup/rollup-linux-arm-musleabihf@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.17.2':
+  '@rollup/rollup-linux-arm64-gnu@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.17.2':
+  '@rollup/rollup-linux-arm64-musl@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.17.2':
+  '@rollup/rollup-linux-riscv64-gnu@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.17.2':
+  '@rollup/rollup-linux-s390x-gnu@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.17.2':
+  '@rollup/rollup-linux-x64-gnu@4.19.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.17.2':
+  '@rollup/rollup-linux-x64-musl@4.19.0':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.17.2':
+  '@rollup/rollup-win32-arm64-msvc@4.19.0':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.17.2':
+  '@rollup/rollup-win32-ia32-msvc@4.19.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.17.2':
+  '@rollup/rollup-win32-x64-msvc@4.19.0':
     optional: true
 
   '@sec-ant/readable-stream@0.4.1': {}
@@ -8412,7 +8326,7 @@ snapshots:
 
   '@sindresorhus/is@4.6.0': {}
 
-  '@sindresorhus/is@6.3.1': {}
+  '@sindresorhus/is@7.0.0': {}
 
   '@sindresorhus/merge-streams@4.0.0': {}
 
@@ -8430,7 +8344,7 @@ snapshots:
 
   '@sqltools/formatter@1.2.5': {}
 
-  '@syuilo/aiscript@0.17.0':
+  '@syuilo/aiscript@0.19.0':
     dependencies:
       seedrandom: 3.0.5
       stringz: 2.1.0
@@ -8465,11 +8379,11 @@ snapshots:
 
   '@types/accepts@1.3.7':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/adm-zip@0.5.5':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/async-lock@1.4.2': {}
 
@@ -8477,40 +8391,40 @@ snapshots:
 
   '@types/babel__core@7.20.5':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
       '@types/babel__generator': 7.6.8
       '@types/babel__template': 7.4.4
       '@types/babel__traverse': 7.20.6
 
   '@types/babel__generator@7.6.8':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@types/babel__template@7.4.4':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
 
   '@types/babel__traverse@7.20.6':
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/cacheable-request@6.0.3':
     dependencies:
       '@types/http-cache-semantics': 4.0.4
       '@types/keyv': 3.1.4
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/responselike': 1.0.3
 
   '@types/co-body@6.1.3':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/qs': 6.9.15
 
   '@types/color-convert@2.0.3':
@@ -8521,7 +8435,7 @@ snapshots:
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/content-disposition@0.5.8': {}
 
@@ -8530,7 +8444,7 @@ snapshots:
       '@types/connect': 3.4.38
       '@types/express': 4.17.21
       '@types/keygrip': 1.0.6
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/disposable-email-domains@1.0.6': {}
 
@@ -8555,7 +8469,7 @@ snapshots:
 
   '@types/express-serve-static-core@4.19.5':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/qs': 6.9.15
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -8569,20 +8483,20 @@ snapshots:
 
   '@types/fluent-ffmpeg@2.1.24':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/formidable@2.0.6':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/glob@8.1.0':
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/graceful-fs@4.1.9':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/http-assert@1.5.5': {}
 
@@ -8609,7 +8523,7 @@ snapshots:
 
   '@types/jsdom@21.1.7':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/tough-cookie': 4.0.5
       parse5: 7.1.2
 
@@ -8625,7 +8539,7 @@ snapshots:
 
   '@types/keyv@3.1.4':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/koa-bodyparser@4.3.12':
     dependencies:
@@ -8664,7 +8578,7 @@ snapshots:
       '@types/http-errors': 2.0.4
       '@types/keygrip': 1.0.6
       '@types/koa-compose': 3.2.8
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/koa__cors@5.0.0':
     dependencies:
@@ -8686,49 +8600,47 @@ snapshots:
 
   '@types/minimist@1.2.5': {}
 
-  '@types/mocha@10.0.7': {}
-
   '@types/mute-stream@0.0.4':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/needle@3.3.0':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/node-fetch@2.6.11':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       form-data: 4.0.0
 
-  '@types/node@20.14.10':
+  '@types/node@20.14.12':
     dependencies:
       undici-types: 5.26.5
 
   '@types/nodemailer@6.4.15':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/normalize-package-data@2.4.4': {}
 
   '@types/oauth@0.9.5':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/opencc-js@1.0.3': {}
 
   '@types/pg@8.11.6':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       pg-protocol: 1.6.1
       pg-types: 4.0.2
 
   '@types/prismjs@1.26.4': {}
 
-  '@types/probe-image-size@7.2.4':
+  '@types/probe-image-size@7.2.5':
     dependencies:
       '@types/needle': 3.3.0
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/pug@2.0.10': {}
 
@@ -8736,7 +8648,7 @@ snapshots:
 
   '@types/qrcode@1.5.5':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/qs@6.9.15': {}
 
@@ -8750,7 +8662,7 @@ snapshots:
 
   '@types/responselike@1.0.3':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/sanitize-html@2.11.0':
     dependencies:
@@ -8763,12 +8675,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       '@types/send': 0.17.4
 
   '@types/sinonjs__fake-timers@8.1.5': {}
@@ -8793,13 +8705,13 @@ snapshots:
 
   '@types/websocket@1.0.10':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/wrap-ansi@3.0.0': {}
 
   '@types/ws@8.5.11':
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
 
   '@types/yargs-parser@21.0.3': {}
 
@@ -8807,97 +8719,97 @@ snapshots:
     dependencies:
       '@types/yargs-parser': 21.0.3
 
-  '@vitejs/plugin-vue@5.0.5(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2))(vue@3.4.31(typescript@5.5.3))':
+  '@vitejs/plugin-vue@5.1.0(vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.33(typescript@5.5.4))':
     dependencies:
-      vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2)
-      vue: 3.4.31(typescript@5.5.3)
+      vite: 5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      vue: 3.4.33(typescript@5.5.4)
 
-  '@volar/language-core@2.4.0-alpha.15':
+  '@volar/language-core@2.4.0-alpha.18':
     dependencies:
-      '@volar/source-map': 2.4.0-alpha.15
+      '@volar/source-map': 2.4.0-alpha.18
 
-  '@volar/source-map@2.4.0-alpha.15': {}
+  '@volar/source-map@2.4.0-alpha.18': {}
 
-  '@volar/typescript@2.4.0-alpha.15':
+  '@volar/typescript@2.4.0-alpha.18':
     dependencies:
-      '@volar/language-core': 2.4.0-alpha.15
+      '@volar/language-core': 2.4.0-alpha.18
       path-browserify: 1.0.1
       vscode-uri: 3.0.8
 
-  '@vue/compiler-core@3.4.31':
+  '@vue/compiler-core@3.4.33':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/shared': 3.4.31
+      '@babel/parser': 7.24.8
+      '@vue/shared': 3.4.33
       entities: 4.5.0
       estree-walker: 2.0.2
       source-map-js: 1.2.0
 
-  '@vue/compiler-dom@3.4.31':
+  '@vue/compiler-dom@3.4.33':
     dependencies:
-      '@vue/compiler-core': 3.4.31
-      '@vue/shared': 3.4.31
+      '@vue/compiler-core': 3.4.33
+      '@vue/shared': 3.4.33
 
   '@vue/compiler-sfc@2.7.16':
     dependencies:
-      '@babel/parser': 7.24.7
+      '@babel/parser': 7.24.8
       postcss: 8.4.39
       source-map: 0.6.1
     optionalDependencies:
       prettier: 2.8.8
 
-  '@vue/compiler-sfc@3.4.31':
+  '@vue/compiler-sfc@3.4.33':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/compiler-core': 3.4.31
-      '@vue/compiler-dom': 3.4.31
-      '@vue/compiler-ssr': 3.4.31
-      '@vue/shared': 3.4.31
+      '@babel/parser': 7.24.8
+      '@vue/compiler-core': 3.4.33
+      '@vue/compiler-dom': 3.4.33
+      '@vue/compiler-ssr': 3.4.33
+      '@vue/shared': 3.4.33
       estree-walker: 2.0.2
       magic-string: 0.30.10
       postcss: 8.4.39
       source-map-js: 1.2.0
 
-  '@vue/compiler-ssr@3.4.31':
+  '@vue/compiler-ssr@3.4.33':
     dependencies:
-      '@vue/compiler-dom': 3.4.31
-      '@vue/shared': 3.4.31
+      '@vue/compiler-dom': 3.4.33
+      '@vue/shared': 3.4.33
 
-  '@vue/language-core@2.0.26(typescript@5.5.3)':
+  '@vue/language-core@2.0.28(typescript@5.5.4)':
     dependencies:
-      '@volar/language-core': 2.4.0-alpha.15
-      '@vue/compiler-dom': 3.4.31
-      '@vue/shared': 3.4.31
+      '@volar/language-core': 2.4.0-alpha.18
+      '@vue/compiler-dom': 3.4.33
+      '@vue/shared': 3.4.33
       computeds: 0.0.1
       minimatch: 9.0.5
       muggle-string: 0.4.1
       path-browserify: 1.0.1
       vue-template-compiler: 2.7.16
     optionalDependencies:
-      typescript: 5.5.3
+      typescript: 5.5.4
 
-  '@vue/reactivity@3.4.31':
+  '@vue/reactivity@3.4.33':
     dependencies:
-      '@vue/shared': 3.4.31
+      '@vue/shared': 3.4.33
 
-  '@vue/runtime-core@3.4.31':
+  '@vue/runtime-core@3.4.33':
     dependencies:
-      '@vue/reactivity': 3.4.31
-      '@vue/shared': 3.4.31
+      '@vue/reactivity': 3.4.33
+      '@vue/shared': 3.4.33
 
-  '@vue/runtime-dom@3.4.31':
+  '@vue/runtime-dom@3.4.33':
     dependencies:
-      '@vue/reactivity': 3.4.31
-      '@vue/runtime-core': 3.4.31
-      '@vue/shared': 3.4.31
+      '@vue/reactivity': 3.4.33
+      '@vue/runtime-core': 3.4.33
+      '@vue/shared': 3.4.33
       csstype: 3.1.3
 
-  '@vue/server-renderer@3.4.31(vue@3.4.31(typescript@5.5.3))':
+  '@vue/server-renderer@3.4.33(vue@3.4.33(typescript@5.5.4))':
     dependencies:
-      '@vue/compiler-ssr': 3.4.31
-      '@vue/shared': 3.4.31
-      vue: 3.4.31(typescript@5.5.3)
+      '@vue/compiler-ssr': 3.4.33
+      '@vue/shared': 3.4.33
+      vue: 3.4.33(typescript@5.5.4)
 
-  '@vue/shared@3.4.31': {}
+  '@vue/shared@3.4.33': {}
 
   '@webassemblyjs/ast@1.12.1':
     dependencies:
@@ -9006,7 +8918,7 @@ snapshots:
 
   agent-base@7.1.1:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
     transitivePeerDependencies:
       - supports-color
 
@@ -9028,8 +8940,6 @@ snapshots:
       json-schema-traverse: 1.0.0
       require-from-string: 2.0.2
 
-  ansi-colors@4.1.3: {}
-
   ansi-escapes@4.3.2:
     dependencies:
       type-fest: 0.21.3
@@ -9123,7 +9033,7 @@ snapshots:
     dependencies:
       possible-typed-array-names: 1.0.0
 
-  aws-sdk@2.1659.0:
+  aws-sdk@2.1662.0:
     dependencies:
       buffer: 4.9.2
       events: 1.1.1
@@ -9152,13 +9062,13 @@ snapshots:
 
   b4a@1.6.6: {}
 
-  babel-jest@29.7.0(@babel/core@7.24.7):
+  babel-jest@29.7.0(@babel/core@7.24.9):
     dependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       '@jest/transform': 29.7.0
       '@types/babel__core': 7.20.5
       babel-plugin-istanbul: 6.1.1
-      babel-preset-jest: 29.6.3(@babel/core@7.24.7)
+      babel-preset-jest: 29.6.3(@babel/core@7.24.9)
       chalk: 4.1.2
       graceful-fs: 4.2.11
       slash: 3.0.0
@@ -9167,7 +9077,7 @@ snapshots:
 
   babel-plugin-istanbul@6.1.1:
     dependencies:
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/helper-plugin-utils': 7.24.8
       '@istanbuljs/load-nyc-config': 1.1.0
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-instrument: 5.2.1
@@ -9178,35 +9088,35 @@ snapshots:
   babel-plugin-jest-hoist@29.6.3:
     dependencies:
       '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.20.6
 
-  babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7):
+  babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.9):
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7)
-      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7)
+      '@babel/core': 7.24.9
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.9)
+      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.9)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.9)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.9)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.9)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.9)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.9)
 
-  babel-preset-jest@29.6.3(@babel/core@7.24.7):
+  babel-preset-jest@29.6.3(@babel/core@7.24.9):
     dependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       babel-plugin-jest-hoist: 29.6.3
-      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7)
+      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.9)
 
   babel-walk@3.0.0-canary-5:
     dependencies:
-      '@babel/types': 7.24.7
+      '@babel/types': 7.24.9
 
   balanced-match@1.0.2: {}
 
@@ -9256,17 +9166,15 @@ snapshots:
       p-queue: 6.6.2
       unload: 2.4.1
 
-  browser-stdout@1.3.1: {}
-
   browserify-zlib@0.1.4:
     dependencies:
       pako: 0.2.9
 
   browserslist@4.23.2:
     dependencies:
-      caniuse-lite: 1.0.30001641
-      electron-to-chromium: 1.4.824
-      node-releases: 2.0.14
+      caniuse-lite: 1.0.30001642
+      electron-to-chromium: 1.4.832
+      node-releases: 2.0.17
       update-browserslist-db: 1.1.0(browserslist@4.23.2)
 
   bs-logger@0.2.6:
@@ -9318,8 +9226,8 @@ snapshots:
       get-port: 5.1.1
       ioredis: 5.4.1
       lodash: 4.17.21
-      msgpackr: 1.10.2
-      semver: 7.6.2
+      msgpackr: 1.11.0
+      semver: 7.6.3
       uuid: 8.3.2
     transitivePeerDependencies:
       - supports-color
@@ -9381,7 +9289,7 @@ snapshots:
 
   camelcase@6.3.0: {}
 
-  caniuse-lite@1.0.30001641: {}
+  caniuse-lite@1.0.30001642: {}
 
   canonicalize@1.0.8: {}
 
@@ -9585,7 +9493,7 @@ snapshots:
 
   commander@9.5.0: {}
 
-  compare-versions@6.1.0: {}
+  compare-versions@6.1.1: {}
 
   compress-commons@6.0.2:
     dependencies:
@@ -9627,8 +9535,8 @@ snapshots:
 
   constantinople@4.0.1:
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
 
   content-disposition@0.5.4:
     dependencies:
@@ -9663,13 +9571,13 @@ snapshots:
       crc-32: 1.2.2
       readable-stream: 4.5.2
 
-  create-jest@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
+  create-jest@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)):
     dependencies:
       '@jest/types': 29.6.3
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      jest-config: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -9740,7 +9648,7 @@ snapshots:
 
   date-fns@3.6.0: {}
 
-  dayjs@1.11.11: {}
+  dayjs@1.11.12: {}
 
   de-indent@1.0.2: {}
 
@@ -9756,11 +9664,9 @@ snapshots:
     dependencies:
       ms: 2.1.2
 
-  debug@4.3.5(supports-color@8.1.1):
+  debug@4.3.5:
     dependencies:
       ms: 2.1.2
-    optionalDependencies:
-      supports-color: 8.1.1
 
   decamelize-keys@1.1.1:
     dependencies:
@@ -9769,8 +9675,6 @@ snapshots:
 
   decamelize@1.2.0: {}
 
-  decamelize@4.0.0: {}
-
   decimal.js@10.4.3: {}
 
   decompress-response@6.0.0:
@@ -9828,15 +9732,6 @@ snapshots:
 
   deep-equal@1.0.1: {}
 
-  deepl-node@1.13.0:
-    dependencies:
-      '@types/node': 20.14.10
-      axios: 1.7.2
-      form-data: 3.0.1
-      loglevel: 1.9.1
-    transitivePeerDependencies:
-      - debug
-
   deepmerge@4.3.1: {}
 
   defer-to-connect@2.0.1: {}
@@ -9872,8 +9767,6 @@ snapshots:
 
   diff@4.0.2: {}
 
-  diff@5.2.0: {}
-
   dijkstrajs@1.0.3: {}
 
   dir-glob@3.0.1:
@@ -9944,15 +9837,15 @@ snapshots:
       '@one-ini/wasm': 0.1.1
       commander: 10.0.1
       minimatch: 9.0.1
-      semver: 7.6.2
+      semver: 7.6.3
 
   ee-first@1.1.1: {}
 
   ejs@3.1.10:
     dependencies:
-      jake: 10.9.1
+      jake: 10.9.2
 
-  electron-to-chromium@1.4.824: {}
+  electron-to-chromium@1.4.832: {}
 
   emittery@0.13.1: {}
 
@@ -10201,11 +10094,11 @@ snapshots:
     dependencies:
       is-unicode-supported: 2.0.0
 
-  file-type@19.1.1:
+  file-type@19.3.0:
     dependencies:
-      strtok3: 7.1.0
+      strtok3: 8.0.0
       token-types: 6.0.0
-      uint8array-extras: 1.3.0
+      uint8array-extras: 1.4.0
 
   file-type@3.9.0: {}
 
@@ -10226,30 +10119,23 @@ snapshots:
       locate-path: 5.0.0
       path-exists: 4.0.0
 
-  find-up@5.0.0:
-    dependencies:
-      locate-path: 6.0.0
-      path-exists: 4.0.0
-
   fix-esm@1.0.1:
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7)
+      '@babel/core': 7.24.9
+      '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.24.9)
+      '@babel/plugin-transform-modules-commonjs': 7.24.8(@babel/core@7.24.9)
     transitivePeerDependencies:
       - supports-color
 
-  flat@5.0.2: {}
-
   fluent-ffmpeg@2.1.3:
     dependencies:
       async: 0.2.10
       which: 1.3.1
 
-  focus-trap-vue@4.0.3(focus-trap@7.5.4)(vue@3.4.31(typescript@5.5.3)):
+  focus-trap-vue@4.0.3(focus-trap@7.5.4)(vue@3.4.33(typescript@5.5.4)):
     dependencies:
       focus-trap: 7.5.4
-      vue: 3.4.31(typescript@5.5.3)
+      vue: 3.4.33(typescript@5.5.4)
 
   focus-trap@7.5.4:
     dependencies:
@@ -10268,12 +10154,6 @@ snapshots:
 
   form-data-encoder@4.0.2: {}
 
-  form-data@3.0.1:
-    dependencies:
-      asynckit: 0.4.0
-      combined-stream: 1.0.8
-      mime-types: 2.1.35
-
   form-data@4.0.0:
     dependencies:
       asynckit: 0.4.0
@@ -10345,8 +10225,6 @@ snapshots:
 
   get-stream@6.0.1: {}
 
-  get-stream@8.0.1: {}
-
   get-stream@9.0.1:
     dependencies:
       '@sec-ant/readable-stream': 0.4.1
@@ -10380,14 +10258,6 @@ snapshots:
       once: 1.4.0
       path-is-absolute: 1.0.1
 
-  glob@8.1.0:
-    dependencies:
-      fs.realpath: 1.0.0
-      inflight: 1.0.6
-      inherits: 2.0.4
-      minimatch: 5.1.6
-      once: 1.4.0
-
   globals@11.12.0: {}
 
   globby@11.1.0:
@@ -10417,20 +10287,19 @@ snapshots:
       p-cancelable: 2.1.1
       responselike: 2.0.1
 
-  got@14.4.1:
+  got@14.4.2:
     dependencies:
-      '@sindresorhus/is': 6.3.1
+      '@sindresorhus/is': 7.0.0
       '@szmarczak/http-timer': 5.0.1
       cacheable-lookup: 7.0.0
       cacheable-request: 12.0.1
       decompress-response: 6.0.0
       form-data-encoder: 4.0.2
-      get-stream: 8.0.1
       http2-wrapper: 2.2.1
       lowercase-keys: 3.0.0
       p-cancelable: 4.0.1
       responselike: 3.0.0
-      type-fest: 4.21.0
+      type-fest: 4.23.0
 
   graceful-fs@4.2.11: {}
 
@@ -10540,7 +10409,7 @@ snapshots:
   http-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
     transitivePeerDependencies:
       - supports-color
 
@@ -10557,7 +10426,7 @@ snapshots:
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
     transitivePeerDependencies:
       - supports-color
 
@@ -10607,10 +10476,10 @@ snapshots:
 
   ini@1.3.8: {}
 
-  inquirer@10.0.1:
+  inquirer@10.0.4:
     dependencies:
-      '@inquirer/prompts': 5.1.2
-      '@inquirer/type': 1.4.0
+      '@inquirer/prompts': 5.2.1
+      '@inquirer/type': 1.5.0
       '@types/mute-stream': 0.0.4
       ansi-escapes: 4.3.2
       mute-stream: 1.0.0
@@ -10623,7 +10492,7 @@ snapshots:
     dependencies:
       '@ioredis/commands': 1.2.0
       cluster-key-slot: 1.1.2
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       denque: 2.1.0
       lodash.defaults: 4.2.0
       lodash.isarguments: 3.1.0
@@ -10667,7 +10536,7 @@ snapshots:
 
   is-callable@1.2.7: {}
 
-  is-core-module@2.14.0:
+  is-core-module@2.15.0:
     dependencies:
       hasown: 2.0.2
 
@@ -10708,8 +10577,6 @@ snapshots:
 
   is-plain-obj@1.1.0: {}
 
-  is-plain-obj@2.1.0: {}
-
   is-plain-obj@4.1.0: {}
 
   is-plain-object@5.0.0: {}
@@ -10755,8 +10622,8 @@ snapshots:
 
   istanbul-lib-instrument@5.2.1:
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/parser': 7.24.8
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
       semver: 6.3.1
@@ -10765,11 +10632,11 @@ snapshots:
 
   istanbul-lib-instrument@6.0.3:
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/parser': 7.24.8
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
-      semver: 7.6.2
+      semver: 7.6.3
     transitivePeerDependencies:
       - supports-color
 
@@ -10781,7 +10648,7 @@ snapshots:
 
   istanbul-lib-source-maps@4.0.1:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -10798,7 +10665,7 @@ snapshots:
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0
 
-  jake@10.9.1:
+  jake@10.9.2:
     dependencies:
       async: 3.2.5
       chalk: 4.1.2
@@ -10817,7 +10684,7 @@ snapshots:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.5.3
@@ -10837,16 +10704,16 @@ snapshots:
       - babel-plugin-macros
       - supports-color
 
-  jest-cli@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
+  jest-cli@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)):
     dependencies:
-      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      create-jest: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       exit: 0.1.2
       import-local: 3.1.0
-      jest-config: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      jest-config: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.7.2
@@ -10856,12 +10723,12 @@ snapshots:
       - supports-color
       - ts-node
 
-  jest-config@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
+  jest-config@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)):
     dependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       '@jest/test-sequencer': 29.7.0
       '@jest/types': 29.6.3
-      babel-jest: 29.7.0(@babel/core@7.24.7)
+      babel-jest: 29.7.0(@babel/core@7.24.9)
       chalk: 4.1.2
       ci-info: 3.9.0
       deepmerge: 4.3.1
@@ -10881,8 +10748,8 @@ snapshots:
       slash: 3.0.0
       strip-json-comments: 3.1.1
     optionalDependencies:
-      '@types/node': 20.14.10
-      ts-node: 10.9.2(@types/node@20.14.10)(typescript@5.5.3)
+      '@types/node': 20.14.12
+      ts-node: 10.9.2(@types/node@20.14.12)(typescript@5.5.4)
     transitivePeerDependencies:
       - babel-plugin-macros
       - supports-color
@@ -10911,7 +10778,7 @@ snapshots:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       jest-mock: 29.7.0
       jest-util: 29.7.0
 
@@ -10928,7 +10795,7 @@ snapshots:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.9
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -10967,7 +10834,7 @@ snapshots:
   jest-mock@29.7.0:
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       jest-util: 29.7.0
 
   jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -11002,7 +10869,7 @@ snapshots:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -11030,7 +10897,7 @@ snapshots:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       cjs-module-lexer: 1.3.1
       collect-v8-coverage: 1.0.2
@@ -11050,15 +10917,15 @@ snapshots:
 
   jest-snapshot@29.7.0:
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/generator': 7.24.7
-      '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7)
-      '@babel/types': 7.24.7
+      '@babel/core': 7.24.9
+      '@babel/generator': 7.24.10
+      '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.9)
+      '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.9)
+      '@babel/types': 7.24.9
       '@jest/expect-utils': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7)
+      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.9)
       chalk: 4.1.2
       expect: 29.7.0
       graceful-fs: 4.2.11
@@ -11069,14 +10936,14 @@ snapshots:
       jest-util: 29.7.0
       natural-compare: 1.4.0
       pretty-format: 29.7.0
-      semver: 7.6.2
+      semver: 7.6.3
     transitivePeerDependencies:
       - supports-color
 
   jest-util@29.7.0:
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       chalk: 4.1.2
       ci-info: 3.9.0
       graceful-fs: 4.2.11
@@ -11095,7 +10962,7 @@ snapshots:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -11109,23 +10976,23 @@ snapshots:
 
   jest-worker@27.5.1:
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       merge-stream: 2.0.0
       supports-color: 8.1.1
 
   jest-worker@29.7.0:
     dependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
 
-  jest@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
+  jest@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)):
     dependencies:
-      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       '@jest/types': 29.6.3
       import-local: 3.1.0
-      jest-cli: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      jest-cli: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -11167,7 +11034,7 @@ snapshots:
 
   jschardet@3.0.0: {}
 
-  jsdom@24.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10):
+  jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
     dependencies:
       cssstyle: 4.0.1
       data-urls: 5.0.0
@@ -11309,7 +11176,7 @@ snapshots:
 
   koa-mount@4.0.0:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       koa-compose: 4.1.0
     transitivePeerDependencies:
       - supports-color
@@ -11318,7 +11185,7 @@ snapshots:
 
   koa-router@10.1.1:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       http-errors: 1.8.1
       koa-compose: 4.1.0
       methods: 1.1.2
@@ -11328,7 +11195,7 @@ snapshots:
 
   koa-send@5.0.1:
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       http-errors: 1.8.1
       resolve-path: 1.4.0
     transitivePeerDependencies:
@@ -11344,7 +11211,7 @@ snapshots:
   koa-views@7.0.2(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3):
     dependencies:
       consolidate: 0.16.0(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       get-paths: 0.0.7
       koa-send: 5.0.1
       mz: 2.7.0
@@ -11415,7 +11282,7 @@ snapshots:
       content-disposition: 0.5.4
       content-type: 1.0.5
       cookies: 0.8.0
-      debug: 4.3.3
+      debug: 4.3.5
       delegates: 1.0.0
       depd: 2.0.0
       destroy: 1.2.0
@@ -11443,7 +11310,7 @@ snapshots:
       content-disposition: 0.5.4
       content-type: 1.0.5
       cookies: 0.9.1
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       delegates: 1.0.0
       depd: 2.0.0
       destroy: 1.2.0
@@ -11492,10 +11359,6 @@ snapshots:
     dependencies:
       p-locate: 4.1.0
 
-  locate-path@6.0.0:
-    dependencies:
-      p-locate: 5.0.0
-
   lodash-es@4.17.21: {}
 
   lodash.assignin@4.2.0: {}
@@ -11535,8 +11398,6 @@ snapshots:
       chalk: 4.1.2
       is-unicode-supported: 0.1.0
 
-  loglevel@1.9.1: {}
-
   long@5.2.3: {}
 
   lowercase-keys@2.0.0: {}
@@ -11567,7 +11428,7 @@ snapshots:
 
   make-dir@4.0.0:
     dependencies:
-      semver: 7.6.2
+      semver: 7.6.3
 
   make-error@1.3.6: {}
 
@@ -11661,29 +11522,6 @@ snapshots:
 
   mkdirp@2.1.6: {}
 
-  mocha@10.6.0:
-    dependencies:
-      ansi-colors: 4.1.3
-      browser-stdout: 1.3.1
-      chokidar: 3.6.0
-      debug: 4.3.5(supports-color@8.1.1)
-      diff: 5.2.0
-      escape-string-regexp: 4.0.0
-      find-up: 5.0.0
-      glob: 8.1.0
-      he: 1.2.0
-      js-yaml: 4.1.0
-      log-symbols: 4.1.0
-      minimatch: 5.1.6
-      ms: 2.1.3
-      serialize-javascript: 6.0.2
-      strip-json-comments: 3.1.1
-      supports-color: 8.1.1
-      workerpool: 6.5.1
-      yargs: 16.2.0
-      yargs-parser: 20.2.9
-      yargs-unparser: 2.0.0
-
   mock-socket@9.3.1: {}
 
   moment@2.30.1: {}
@@ -11706,7 +11544,7 @@ snapshots:
       '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
     optional: true
 
-  msgpackr@1.10.2:
+  msgpackr@1.11.0:
     optionalDependencies:
       msgpackr-extract: 3.0.3
 
@@ -11780,7 +11618,7 @@ snapshots:
 
   node-int64@0.4.0: {}
 
-  node-releases@2.0.14: {}
+  node-releases@2.0.17: {}
 
   nodemailer@6.9.14: {}
 
@@ -11798,8 +11636,8 @@ snapshots:
   normalize-package-data@3.0.3:
     dependencies:
       hosted-git-info: 4.1.0
-      is-core-module: 2.14.0
-      semver: 7.6.2
+      is-core-module: 2.15.0
+      semver: 7.6.3
       validate-npm-package-license: 3.0.4
 
   normalize-path@3.0.0: {}
@@ -11844,8 +11682,6 @@ snapshots:
 
   only@0.0.2: {}
 
-  opencc-js@1.0.5: {}
-
   opencollective-postinstall@2.0.3: {}
 
   opentype.js@0.4.11: {}
@@ -11874,10 +11710,6 @@ snapshots:
     dependencies:
       p-limit: 2.3.0
 
-  p-locate@5.0.0:
-    dependencies:
-      p-limit: 3.1.0
-
   p-queue@6.6.2:
     dependencies:
       eventemitter3: 4.0.7
@@ -11941,7 +11773,7 @@ snapshots:
 
   path-type@4.0.0: {}
 
-  peek-readable@5.1.1: {}
+  peek-readable@5.1.3: {}
 
   peek-stream@1.1.3:
     dependencies:
@@ -12044,7 +11876,7 @@ snapshots:
 
   pngjs@7.0.0: {}
 
-  pnpm@9.5.0: {}
+  pnpm@9.6.0: {}
 
   possible-typed-array-names@1.0.0: {}
 
@@ -12359,7 +12191,7 @@ snapshots:
 
   redis-semaphore@5.6.0(ioredis@5.4.1):
     dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
     optionalDependencies:
       ioredis: 5.4.1
     transitivePeerDependencies:
@@ -12404,7 +12236,7 @@ snapshots:
 
   resolve@1.22.8:
     dependencies:
-      is-core-module: 2.14.0
+      is-core-module: 2.15.0
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
 
@@ -12423,26 +12255,26 @@ snapshots:
       rangestr: 0.0.1
       seedrandom: 2.4.2
 
-  rollup@4.17.2:
+  rollup@4.19.0:
     dependencies:
       '@types/estree': 1.0.5
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.17.2
-      '@rollup/rollup-android-arm64': 4.17.2
-      '@rollup/rollup-darwin-arm64': 4.17.2
-      '@rollup/rollup-darwin-x64': 4.17.2
-      '@rollup/rollup-linux-arm-gnueabihf': 4.17.2
-      '@rollup/rollup-linux-arm-musleabihf': 4.17.2
-      '@rollup/rollup-linux-arm64-gnu': 4.17.2
-      '@rollup/rollup-linux-arm64-musl': 4.17.2
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2
-      '@rollup/rollup-linux-riscv64-gnu': 4.17.2
-      '@rollup/rollup-linux-s390x-gnu': 4.17.2
-      '@rollup/rollup-linux-x64-gnu': 4.17.2
-      '@rollup/rollup-linux-x64-musl': 4.17.2
-      '@rollup/rollup-win32-arm64-msvc': 4.17.2
-      '@rollup/rollup-win32-ia32-msvc': 4.17.2
-      '@rollup/rollup-win32-x64-msvc': 4.17.2
+      '@rollup/rollup-android-arm-eabi': 4.19.0
+      '@rollup/rollup-android-arm64': 4.19.0
+      '@rollup/rollup-darwin-arm64': 4.19.0
+      '@rollup/rollup-darwin-x64': 4.19.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.19.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.19.0
+      '@rollup/rollup-linux-arm64-gnu': 4.19.0
+      '@rollup/rollup-linux-arm64-musl': 4.19.0
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.19.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.19.0
+      '@rollup/rollup-linux-s390x-gnu': 4.19.0
+      '@rollup/rollup-linux-x64-gnu': 4.19.0
+      '@rollup/rollup-linux-x64-musl': 4.19.0
+      '@rollup/rollup-win32-arm64-msvc': 4.19.0
+      '@rollup/rollup-win32-ia32-msvc': 4.19.0
+      '@rollup/rollup-win32-x64-msvc': 4.19.0
       fsevents: 2.3.3
 
   rrweb-cssom@0.6.0: {}
@@ -12513,7 +12345,7 @@ snapshots:
 
   semver@6.3.1: {}
 
-  semver@7.6.2: {}
+  semver@7.6.3: {}
 
   serialize-javascript@6.0.2:
     dependencies:
@@ -12545,7 +12377,7 @@ snapshots:
     dependencies:
       color: 4.2.3
       detect-libc: 2.0.3
-      semver: 7.6.2
+      semver: 7.6.3
     optionalDependencies:
       '@img/sharp-darwin-arm64': 0.33.4
       '@img/sharp-darwin-x64': 0.33.4
@@ -12729,10 +12561,10 @@ snapshots:
 
   strnum@1.0.5: {}
 
-  strtok3@7.1.0:
+  strtok3@8.0.0:
     dependencies:
       '@tokenizer/token': 0.3.0
-      peek-readable: 5.1.1
+      peek-readable: 5.1.3
 
   summaly@2.7.0:
     dependencies:
@@ -12769,7 +12601,7 @@ snapshots:
 
   supports-preserve-symlinks-flag@1.0.0: {}
 
-  swiper@11.1.4: {}
+  swiper@11.1.7: {}
 
   symbol-tree@3.2.4: {}
 
@@ -12803,10 +12635,10 @@ snapshots:
       jest-worker: 27.5.1
       schema-utils: 3.3.0
       serialize-javascript: 6.0.2
-      terser: 5.31.2
+      terser: 5.31.3
       webpack: 5.93.0
 
-  terser@5.31.2:
+  terser@5.31.3:
     dependencies:
       '@jridgewell/source-map': 0.3.6
       acorn: 8.12.1
@@ -12907,50 +12739,50 @@ snapshots:
 
   trim-newlines@3.0.1: {}
 
-  ts-jest@29.2.2(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))(typescript@5.5.3):
+  ts-jest@29.2.3(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)))(typescript@5.5.4):
     dependencies:
       bs-logger: 0.2.6
       ejs: 3.1.10
       fast-json-stable-stringify: 2.1.0
-      jest: 29.7.0(@types/node@20.14.10)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))
+      jest: 29.7.0(@types/node@20.14.12)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4))
       jest-util: 29.7.0
       json5: 2.2.3
       lodash.memoize: 4.1.2
       make-error: 1.3.6
-      semver: 7.6.2
-      typescript: 5.5.3
+      semver: 7.6.3
+      typescript: 5.5.4
       yargs-parser: 21.1.1
     optionalDependencies:
-      '@babel/core': 7.24.7
+      '@babel/core': 7.24.9
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      babel-jest: 29.7.0(@babel/core@7.24.7)
+      babel-jest: 29.7.0(@babel/core@7.24.9)
 
-  ts-loader@9.5.1(typescript@5.5.3)(webpack@5.93.0):
+  ts-loader@9.5.1(typescript@5.5.4)(webpack@5.93.0):
     dependencies:
       chalk: 4.1.2
       enhanced-resolve: 5.17.0
       micromatch: 4.0.7
-      semver: 7.6.2
+      semver: 7.6.3
       source-map: 0.7.4
-      typescript: 5.5.3
+      typescript: 5.5.4
       webpack: 5.93.0
 
-  ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3):
+  ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       acorn: 8.12.1
       acorn-walk: 8.3.3
       arg: 4.1.3
       create-require: 1.1.1
       diff: 4.0.2
       make-error: 1.3.6
-      typescript: 5.5.3
+      typescript: 5.5.4
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
@@ -12997,7 +12829,7 @@ snapshots:
 
   type-fest@0.8.1: {}
 
-  type-fest@4.21.0: {}
+  type-fest@4.23.0: {}
 
   type-is@1.6.18:
     dependencies:
@@ -13012,15 +12844,15 @@ snapshots:
 
   typedarray@0.0.6: {}
 
-  typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)):
+  typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0)(ts-node@10.9.2(@types/node@20.14.12)(typescript@5.5.4)):
     dependencies:
       '@sqltools/formatter': 1.2.5
       app-root-path: 3.1.0
       buffer: 6.0.3
       chalk: 4.1.2
       cli-highlight: 2.1.11
-      dayjs: 1.11.11
-      debug: 4.3.5(supports-color@8.1.1)
+      dayjs: 1.11.12
+      debug: 4.3.5
       dotenv: 16.4.5
       glob: 10.4.5
       mkdirp: 2.1.6
@@ -13032,13 +12864,13 @@ snapshots:
     optionalDependencies:
       ioredis: 5.4.1
       pg: 8.12.0
-      ts-node: 10.9.2(@types/node@20.14.10)(typescript@5.5.3)
+      ts-node: 10.9.2(@types/node@20.14.12)(typescript@5.5.4)
     transitivePeerDependencies:
       - supports-color
 
-  typescript@5.5.3: {}
+  typescript@5.5.4: {}
 
-  uint8array-extras@1.3.0: {}
+  uint8array-extras@1.4.0: {}
 
   ulid@2.3.0: {}
 
@@ -13143,25 +12975,25 @@ snapshots:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  vite-plugin-compression@0.5.1(vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2)):
+  vite-plugin-compression@0.5.1(vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)):
     dependencies:
       chalk: 4.1.2
-      debug: 4.3.5(supports-color@8.1.1)
+      debug: 4.3.5
       fs-extra: 10.1.0
-      vite: 5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2)
+      vite: 5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.3.3(@types/node@20.14.10)(sass@1.77.8)(terser@5.31.2):
+  vite@5.3.4(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.39
-      rollup: 4.17.2
+      rollup: 4.19.0
     optionalDependencies:
-      '@types/node': 20.14.10
+      '@types/node': 20.14.12
       fsevents: 2.3.3
       sass: 1.77.8
-      terser: 5.31.2
+      terser: 5.31.3
 
   void-elements@3.1.0: {}
 
@@ -13176,36 +13008,36 @@ snapshots:
       plyr: https://codeload.github.com/sampotts/plyr/tar.gz/d434c9af16e641400aaee93188594208d88f2658
       vue: 2.7.16
 
-  vue-prism-editor@2.0.0-alpha.2(vue@3.4.31(typescript@5.5.3)):
+  vue-prism-editor@2.0.0-alpha.2(vue@3.4.33(typescript@5.5.4)):
     dependencies:
-      vue: 3.4.31(typescript@5.5.3)
+      vue: 3.4.33(typescript@5.5.4)
 
   vue-template-compiler@2.7.16:
     dependencies:
       de-indent: 1.0.2
       he: 1.2.0
 
-  vue-tsc@2.0.26(typescript@5.5.3):
+  vue-tsc@2.0.28(typescript@5.5.4):
     dependencies:
-      '@volar/typescript': 2.4.0-alpha.15
-      '@vue/language-core': 2.0.26(typescript@5.5.3)
-      semver: 7.6.2
-      typescript: 5.5.3
+      '@volar/typescript': 2.4.0-alpha.18
+      '@vue/language-core': 2.0.28(typescript@5.5.4)
+      semver: 7.6.3
+      typescript: 5.5.4
 
   vue@2.7.16:
     dependencies:
       '@vue/compiler-sfc': 2.7.16
       csstype: 3.1.3
 
-  vue@3.4.31(typescript@5.5.3):
+  vue@3.4.33(typescript@5.5.4):
     dependencies:
-      '@vue/compiler-dom': 3.4.31
-      '@vue/compiler-sfc': 3.4.31
-      '@vue/runtime-dom': 3.4.31
-      '@vue/server-renderer': 3.4.31(vue@3.4.31(typescript@5.5.3))
-      '@vue/shared': 3.4.31
+      '@vue/compiler-dom': 3.4.33
+      '@vue/compiler-sfc': 3.4.33
+      '@vue/runtime-dom': 3.4.33
+      '@vue/server-renderer': 3.4.33(vue@3.4.33(typescript@5.5.4))
+      '@vue/shared': 3.4.33
     optionalDependencies:
-      typescript: 5.5.3
+      typescript: 5.5.4
 
   w3c-xmlserializer@5.0.0:
     dependencies:
@@ -13310,13 +13142,11 @@ snapshots:
 
   with@7.0.2:
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/parser': 7.24.8
+      '@babel/types': 7.24.9
       assert-never: 1.3.0
       babel-walk: 3.0.0-canary-5
 
-  workerpool@6.5.1: {}
-
   wrap-ansi@6.2.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -13392,13 +13222,6 @@ snapshots:
 
   yargs-parser@21.1.1: {}
 
-  yargs-unparser@2.0.0:
-    dependencies:
-      camelcase: 6.3.0
-      decamelize: 4.0.0
-      flat: 5.0.2
-      is-plain-obj: 2.1.0
-
   yargs@15.4.1:
     dependencies:
       cliui: 6.0.0