Merge branch 'develop' into 'main'

release: v20240725

Co-authored-by: GitLab CI <project_7_bot_1bfaee5701aed20091a86249a967a6c1@noreply.firefish.dev>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: jolupa <jolupameister@gmail.com>

See merge request firefish/firefish!11196
This commit is contained in:
naskya 2024-07-24 16:05:07 +00:00
commit eeed97cd96
149 changed files with 1847 additions and 7838 deletions

1
.gitignore vendored
View file

@ -34,6 +34,7 @@ coverage
!/.config/helm_values_example.yml
!/.config/LICENSE
/docker-compose.yml
/compose.yml
/custom/*
!/custom/LICENSE

336
Cargo.lock generated
View file

@ -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]]

View file

@ -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

View file

@ -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 Iceshrimps.

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"
}
}

View file

@ -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"] }

View file

@ -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'|

View file

@ -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

View file

@ -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/",

View file

@ -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",
];

View file

@ -3,6 +3,5 @@
pub use meta::local_server_info;
pub use server::CONFIG;
pub mod constant;
pub mod meta;
pub mod server;

View file

@ -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),

View file

@ -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),

View file

@ -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"]

View file

@ -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),

View file

@ -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)]

View file

@ -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?);

View file

@ -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)]

View file

@ -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()) {

View file

@ -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),

View file

@ -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;

View file

@ -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),

View file

@ -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)]

View file

@ -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)]

View file

@ -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,
})
}
}

View file

@ -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)]

View file

@ -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)]

View file

@ -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)]

View file

@ -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),

View file

@ -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),

View file

@ -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),

View file

@ -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
}

View file

@ -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;
});

View file

@ -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"
}

View file

@ -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
*/

View file

@ -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,
},

View file

@ -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,

View file

@ -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";

View file

@ -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),
};
}

View file

@ -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";

View file

@ -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",

View file

@ -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();
}

View file

@ -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,

View file

@ -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)`);
}
});

View file

@ -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],

View file

@ -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"],

View file

@ -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";

View file

@ -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"],

View file

@ -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"],

View file

@ -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 = {

View file

@ -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";

View file

@ -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"],

View file

@ -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,

View file

@ -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"],

View file

@ -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"],

View file

@ -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"],

View file

@ -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";

View file

@ -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"],

View file

@ -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";

View file

@ -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,

View file

@ -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);
});

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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";

View file

@ -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";

View file

@ -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,

View file

@ -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"],

View file

@ -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";

View file

@ -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"],

View file

@ -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";

View file

@ -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 = {

View file

@ -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"],

View file

@ -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,
);
});

View file

@ -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"],

View file

@ -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";

View file

@ -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"],

View file

@ -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 = {

View file

@ -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"],

View file

@ -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,

View file

@ -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 {

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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";

View file

@ -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,

View file

@ -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 {

Some files were not shown because too many files have changed in this diff Show more