diff --git a/.config/devenv.yml b/.config/devenv.yml index 77d4ce66b6..3c980914a9 100644 --- a/.config/devenv.yml +++ b/.config/devenv.yml @@ -13,16 +13,8 @@ redis: host: firefish_redis port: 6379 -id: 'aid' - #allowedPrivateNetworks: [ # '10.69.1.0/24' #] -logLevel: [ - 'error', - 'success', - 'warning', - 'debug', - 'info' -] +maxLogLevel: 'debug' diff --git a/.config/example.yml b/.config/example.yml index 17149f6c3a..75ee713526 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -145,16 +145,11 @@ reservedUsernames: [ # IP address family used for outgoing request (ipv4, ipv6 or dual) #outgoingAddressFamily: ipv4 -# Log Option -# Production env: ['error', 'success', 'warning', 'info'] -# Debug/Test env or Troubleshooting: ['error', 'success', 'warning', 'debug' ,'info'] -# Production env which storage space or IO is tight: ['error', 'warning'] -logLevel: [ - 'error', - 'success', - 'warning', - 'info' -] +# Log level (error, warning, info, debug, trace) +# Production env: info +# Production env whose storage space or IO is tight: warning +# Debug/Test env or Troubleshooting: debug (or trace) +maxLogLevel: info # Syslog option #syslog: diff --git a/Cargo.lock b/Cargo.lock index 6cb32a21d0..ed3f639cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,6 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", - "serde", "version_check", "zerocopy", ] @@ -57,6 +56,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.18" @@ -78,60 +83,29 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" - -[[package]] -name = "anstyle-parse" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" -dependencies = [ - "anstyle", - "windows-sys 0.52.0", -] - [[package]] name = "anyhow" version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "argon2" version = "0.5.3" @@ -198,31 +172,54 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "backend-rs" version = "0.0.0" dependencies = [ "argon2", - "async-trait", "basen", "bcrypt", - "cfg-if", "chrono", "cuid2", "emojis", "idna", - "jsonschema", + "image", "macro_rs", "napi", "napi-build", "napi-derive", + "nom-exif", "once_cell", - "parse-display", + "openssl", "pretty_assertions", "rand", "redis", "regex", - "schemars", + "reqwest", + "rmp-serde", "sea-orm", "serde", "serde_json", @@ -230,6 +227,8 @@ dependencies = [ "strum 0.26.2", "thiserror", "tokio", + "tracing", + "tracing-subscriber", "url", "urlencoding", ] @@ -298,19 +297,10 @@ dependencies = [ ] [[package]] -name = "bit-set" -version = "0.5.3" +name = "bit_field" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" @@ -327,6 +317,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitstream-io" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" + [[package]] name = "bitvec" version = "1.0.1" @@ -391,6 +387,12 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "built" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" + [[package]] name = "bumpalo" version = "3.16.0" @@ -420,10 +422,10 @@ dependencies = [ ] [[package]] -name = "bytecount" -version = "0.6.7" +name = "bytemuck" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -431,6 +433,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -442,6 +450,20 @@ name = "cc" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] [[package]] name = "cfg-if" @@ -481,50 +503,10 @@ dependencies = [ ] [[package]] -name = "clap" -version = "4.5.4" +name = "color_quant" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.58", -] - -[[package]] -name = "clap_lex" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "combine" @@ -591,6 +573,34 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -606,6 +616,12 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -700,12 +716,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "either" version = "1.11.0" @@ -767,13 +777,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] -name = "fancy-regex" -version = "0.11.0" +name = "exr" +version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ - "bit-set", - "regex", + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", ] [[package]] @@ -782,12 +798,31 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "finl_unicode" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.0" @@ -805,6 +840,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -814,16 +864,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fraction" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678" -dependencies = [ - "lazy_static", - "num", -] - [[package]] name = "funty" version = "2.0.0" @@ -934,10 +974,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", ] [[package]] @@ -948,9 +996,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.26" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", @@ -965,6 +1013,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1049,9 +1107,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1060,12 +1118,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", "pin-project-lite", ] @@ -1075,34 +1145,60 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" -version = "0.14.28" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", "socket2", "tokio", + "tower", "tower-service", "tracing", - "want", ] [[package]] @@ -1138,6 +1234,45 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.6" @@ -1168,21 +1303,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "iso8601" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" -dependencies = [ - "nom", -] - [[package]] name = "itertools" version = "0.12.1" @@ -1198,6 +1335,21 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.69" @@ -1207,36 +1359,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonschema" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978" -dependencies = [ - "ahash 0.8.11", - "anyhow", - "base64 0.21.7", - "bytecount", - "clap", - "fancy-regex", - "fraction", - "getrandom", - "iso8601", - "itoa", - "memchr", - "num-cmp", - "once_cell", - "parking_lot", - "percent-encoding", - "regex", - "reqwest", - "serde", - "serde_json", - "time", - "url", - "uuid", -] - [[package]] name = "keccak" version = "0.1.5" @@ -1255,12 +1377,29 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.8.3" @@ -1310,6 +1449,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "macro_rs" version = "0.0.0" @@ -1322,6 +1470,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1357,6 +1515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1431,6 +1590,30 @@ dependencies = [ "libloading", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nom" version = "7.1.3" @@ -1441,6 +1624,34 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom-exif" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da85c96824dc1b583f55fd2c39e53b04482b4c199f25db27868db334bf06407" +dependencies = [ + "chrono", + "nom", + "regex", + "thiserror", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.4.2" @@ -1483,12 +1694,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-cmp" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" - [[package]] name = "num-complex" version = "0.4.5" @@ -1504,6 +1709,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1571,6 +1787,60 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.2.3+3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "3.9.2" @@ -1604,6 +1874,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1627,31 +1903,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "parse-display" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06af5f9333eb47bd9ba8462d612e37a8328a5cb80b13f0af4de4c3b89f52dee5" -dependencies = [ - "parse-display-derive", - "regex", - "regex-syntax", -] - -[[package]] -name = "parse-display-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9252f259500ee570c75adcc4e317fa6f57a1e47747d622e0bf838002a7b790" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "regex-syntax", - "structmeta", - "syn 2.0.58", -] - [[package]] name = "password-hash" version = "0.5.0" @@ -1702,6 +1953,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1741,6 +2012,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1769,7 +2053,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -1805,6 +2089,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.58", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -1825,6 +2128,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.36" @@ -1870,6 +2188,76 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redis" version = "0.25.3" @@ -1934,32 +2322,39 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", + "http-body-util", "hyper", + "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile 2.1.2", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", + "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -1968,6 +2363,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -2012,6 +2416,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938a142ab806f18b88a97b0dea523d39e0fd730a064b035726adcfc58a8a5188" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2087,6 +2513,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2110,28 +2552,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] -name = "schemars" -version = "0.8.16" +name = "schannel" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "chrono", - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", + "windows-sys 0.52.0", ] [[package]] @@ -2244,6 +2670,29 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.22" @@ -2270,17 +2719,6 @@ dependencies = [ "syn 2.0.58", ] -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_json" version = "1.0.115" @@ -2292,6 +2730,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2355,6 +2802,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2374,6 +2830,21 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -2491,7 +2962,7 @@ dependencies = [ "percent-encoding", "rust_decimal", "rustls", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "sha2", @@ -2680,35 +3151,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "structmeta" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn 2.0.58", -] - -[[package]] -name = "structmeta-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.58", -] - [[package]] name = "strum" version = "0.25.0" @@ -2804,12 +3246,31 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "tempfile" version = "3.10.1" @@ -2842,6 +3303,27 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.36" @@ -2918,6 +3400,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -2943,11 +3435,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.12", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2957,9 +3464,44 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] +[[package]] +name = "toml_edit" +version = "0.22.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.6", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -2996,6 +3538,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -3072,12 +3640,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - [[package]] name = "uuid" version = "1.8.0" @@ -3087,12 +3649,35 @@ dependencies = [ "serde", ] +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -3202,6 +3787,12 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" version = "1.5.1" @@ -3212,6 +3803,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -3370,10 +3983,19 @@ dependencies = [ ] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", @@ -3419,3 +4041,27 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 0bde324447..14e5d45c40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,25 +10,25 @@ napi-derive = "2.16.2" napi-build = "2.1.3" argon2 = "0.5.3" -async-trait = "0.1.80" basen = "0.1.0" bcrypt = "0.15.1" -cfg-if = "1.0.0" chrono = "0.4.37" convert_case = "0.6.0" cuid2 = "0.1.2" emojis = "0.6.1" idna = "0.5.0" -jsonschema = "0.17.1" +image = "0.25.1" +nom-exif = "1.2.0" once_cell = "1.19.0" -parse-display = "0.9.0" +openssl = "0.10.64" pretty_assertions = "1.4.0" proc-macro2 = "1.0.79" quote = "1.0.36" rand = "0.8.5" redis = "0.25.3" regex = "1.10.4" -schemars = "0.8.16" +reqwest = "0.12.4" +rmp-serde = "1.2.0" sea-orm = "0.12.15" serde = "1.0.197" serde_json = "1.0.115" @@ -37,6 +37,8 @@ strum = "0.26.2" syn = "2.0.58" thiserror = "1.0.58" tokio = "1.37.0" +tracing = "0.1.40" +tracing-subscriber = "0.3.1" url = "2.5.0" urlencoding = "2.1.3" diff --git a/biome.json b/biome.json index 21b711f457..487165266a 100644 --- a/biome.json +++ b/biome.json @@ -14,7 +14,7 @@ }, "overrides": [ { - "include": ["*.vue"], + "include": ["*.vue", "packages/client/*.ts"], "linter": { "rules": { "style": { diff --git a/docs/api-change.md b/docs/api-change.md index dcd4329a27..d9b7095c7c 100644 --- a/docs/api-change.md +++ b/docs/api-change.md @@ -2,9 +2,16 @@ Breaking changes are indicated by the :warning: icon. -## Unreleased +## v20240424 - Added `antennaLimit` field to the response of `meta` and `admin/meta`, and the request of `admin/update-meta` (optional). +- Added `filter` optional parameter to `notes/renotes` endpoint to filter the types of renotes. It can take the following values: + - `all` (default) + - `renote` + - `quote` +- :warning: Removed the following optional parameters in `notes/reactions`, as they were never taken into account due to a bug: + - `sinceId` + - `untilId` ## v20240413 diff --git a/docs/changelog.md b/docs/changelog.md index 70f8b34fbe..844c45b706 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,12 @@ Critical security updates are indicated by the :warning: icon. - Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well. - Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well. +## [v20240424](https://firefish.dev/firefish/firefish/-/merge_requests/10765/commits) + +- Improve the usability of the feature to prevent forgetting to write alt texts +- Add a server-wide setting for the maximum number of antennas each user can create +- Fix bugs + ## [v20240421](https://firefish.dev/firefish/firefish/-/merge_requests/10756/commits) - Fix bugs diff --git a/docs/downgrade.sql b/docs/downgrade.sql index 44222f818f..eed0079c3b 100644 --- a/docs/downgrade.sql +++ b/docs/downgrade.sql @@ -1,6 +1,7 @@ BEGIN; DELETE FROM "migrations" WHERE name IN ( + 'AlterAkaType1714099399879', 'AddDriveFileUsage1713451569342', 'ConvertCwVarcharToText1713225866247', 'FixChatFileConstraint1712855579316', @@ -24,6 +25,13 @@ DELETE FROM "migrations" WHERE name IN ( 'RemoveNativeUtilsMigration1705877093218' ); +-- alter-aka-type +ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld"; +ALTER TABLE "user" ADD COLUMN "alsoKnownAs" text; +UPDATE "user" SET "alsoKnownAs" = array_to_string("alsoKnownAsOld", ','); +COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'; +ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld"; + -- AddDriveFileUsage ALTER TABLE "drive_file" DROP COLUMN "usageHint"; DROP TYPE "drive_file_usage_hint_enum"; diff --git a/docs/notice-for-admins.md b/docs/notice-for-admins.md index 9bff40a65c..a27f4e6c2f 100644 --- a/docs/notice-for-admins.md +++ b/docs/notice-for-admins.md @@ -2,6 +2,12 @@ You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md). +## Unreleased + +### For all users + +You can control the verbosity of the server log by adding `maxLogLevel` in `.config/default.yml`. `logLevels` has been deprecated in favor of this setting. (see also: ) + ## v20240413 ### For all users diff --git a/locales/bg-BG.yml b/locales/bg-BG.yml index 1142a1eb57..75bb03773d 100644 --- a/locales/bg-BG.yml +++ b/locales/bg-BG.yml @@ -19,7 +19,7 @@ deleteAndEditConfirm: Сигурни ли сте, че искате да изт copyUsername: Копиране на потребителското име searchUser: Търсене на потребител reply: Отговор -showMore: Покажи още +showMore: Показване на повече loadMore: Зареди още followRequestAccepted: Заявката за последване е приета importAndExport: Импорт/експорт на данни @@ -69,7 +69,7 @@ renameFile: Преименуване на файла _widgets: activity: Дейност notifications: Известия - timeline: Инфопоток + timeline: Хронология clock: Часовник trends: Актуални photos: Снимки @@ -187,7 +187,7 @@ notesAndReplies: Публикации и отговори noSuchUser: Потребителят не е намерен pinnedPages: Закачени страници pinLimitExceeded: Не може да закачаш повече публикации -flagShowTimelineReplies: Показване на отговори в инфопотока +flagShowTimelineReplies: Показване на отговори в хронологията followersCount: Брой последователи receivedReactionsCount: Брой получени реакции federation: Федерация @@ -336,11 +336,15 @@ _pages: title: Заглавие my: Моите страници pageSetting: Настройки на страницата + url: Адрес на страницата + summary: Кратко обобщение + alignCenter: Центриране на елементите + variables: Променливи _deck: _columns: notifications: Известия mentions: Споменавания - tl: Инфопоток + tl: Хронология direct: Директни съобщения list: Списък antenna: Антена @@ -375,7 +379,7 @@ basicSettings: Основни настройки otherSettings: Други настройки openInWindow: Отваряне в прозорец profile: Профил -timeline: Инфопоток +timeline: Хронология noAccountDescription: Този потребител все още не е написал своята биография. login: Вход loggingIn: Вписване @@ -398,7 +402,7 @@ sendMessage: Изпращане на съобщение jumpToPrevious: Премини към предишно newer: по-ново older: по-старо -showLess: Покажи по-малко +showLess: Показване на по-малко youGotNewFollower: те последва receiveFollowRequest: Заявка за последване получена mention: Споменаване @@ -558,12 +562,12 @@ _visibility: specified: Директна localOnly: Само местни public: Общодостъпна - publicDescription: Публикацията ще бъде видима във всички публични инфопотоци + publicDescription: Публикацията ще бъде видима във всички публични хронологии home: Скрита localOnlyDescription: Не е видима за отдалечени потребители specifiedDescription: Видима само за определени потребители followersDescription: Видима само за последователите ти и споменатите потребители - homeDescription: Публикуване само в началния инфопоток + homeDescription: Публикуване само в началната хронология explore: Разглеждане theme: Теми wallpaper: Тапет @@ -594,21 +598,21 @@ _tutorial: да разберат дали искат да видят вашите публикации или да ви следват. title: Как се използва Firefish step1_1: Добре дошли! - step5_1: Инфопотоци, инфопотоци навсякъде! + step5_1: Хронологии, хронологии навсякъде! step3_1: Сега е време да последвате няколко хора! step1_2: Нека да ви настроим. Ще бъдете готови за нула време! - step5_3: Началният {icon} инфопоток е мястото, където можете да видите публикации + step5_3: Началната {icon} хронология е мястото, където можете да видите публикации от акаунтите, които следвате. step6_1: И така, какво е това място? - step5_7: Глобалният {icon} инфопоток е мястото, където можете да видите публикации + step5_7: Глобалната {icon} хронология е мястото, където можете да видите публикации от всеки друг свързан сървър. step4_2: За първата си публикация някои хора обичат да правят публикация {introduction} или просто „Здравей свят!“ - step5_2: Вашият сървър има активирани {timelines} различни инфопотоци. - step5_4: Местният {icon} инфопоток е мястото, където можете да видите публикации + step5_2: Вашият сървър има активирани {timelines} различни хронологии. + step5_4: Местната {icon} хронология е мястото, където можете да видите публикации от всички останали на този сървър. - step5_5: Социалният {icon} инфопоток е комбинация от Началния и Местния инфопоток. - step5_6: Препоръчаният {icon} инфопоток е мястото, където можете да видите публикации + step5_5: Социалната {icon} хронология е комбинация от Началната и Местната хронология. + step5_6: Препоръчаната {icon} хронология е мястото, където можете да видите публикации от сървъри, препоръчани от администраторите. step6_4: Сега отидете, изследвайте и се забавлявайте! step6_3: Всеки сървър работи по различни начини и не всички сървъри работят с Firefish. @@ -754,7 +758,7 @@ _feeds: general: Общи metadata: Метаданни disk: Диск -featured: Представени +featured: Препоръчано yearsOld: на {age} години reload: Опресняване invites: Покани @@ -778,8 +782,8 @@ uploadFromUrl: Качване от URL адрес instanceName: Име на сървъра instanceDescription: Описание на сървъра accept: Приемане -enableLocalTimeline: Включване на местния инфопоток -enableGlobalTimeline: Включване на глобалния инфопоток +enableLocalTimeline: Включване на местната хронология +enableGlobalTimeline: Включване на глобалната хронология removeMember: Премахване на член isAdmin: Администратор isModerator: Модератор @@ -862,8 +866,8 @@ apply: Прилагане selectAccount: Избор на акаунт muteThread: Заглушаване на нишката ffVisibility: Видимост на Последвани/Последователи -renoteMute: Заглушаване на подсилванията в инфопотоците -replyMute: Заглушаване на отговорите в инфопотоците +renoteMute: Заглуш. на подсилванията в хронолог. +replyMute: Заглуш. на отговорите в хронолог. blockConfirm: Сигурни ли сте, че искате да блокирате този акаунт? appearance: Облик fontSize: Размер на шрифта @@ -893,7 +897,7 @@ charts: Диаграми disablePagesScript: Изключване на AiScript в Страниците updatedAt: Обновено на privateDescription: Видима само за теб -enableTimelineStreaming: Автоматично обновяване на инфопотоците +enableTimelineStreaming: Автоматично обновяване на хронологиите toEdit: Редактиране showEmojisInReactionNotifications: Показване на емоджита в известията за реакции rememberNoteVisibility: Запомняне на настройките за видимост на публикациите @@ -932,3 +936,19 @@ clientSettings: Настройки за устройството behavior: Поведение detectPostLanguage: Автоматично откриване на езика и показване на бутон за превеждане за публикации на чужди езици +replyUnmute: Отмяна на заглушаването на отговорите +searchWords: Думи за търсене / ID или URL за поглеждане +reloadConfirm: Искате ли да опресните хронологията? +enableRecommendedTimeline: Включване на препоръчаната хронология +showGapBetweenNotesInTimeline: Показване на празнина между публикациите в хронологията +lookup: Поглеждане +media: Мултимедия +welcomeBackWithName: Добре дошли отново, {name} +reduceUiAnimation: Намаляване на UI анимациите +clickToFinishEmailVerification: Моля, натиснете [{ok}], за да завършите потвърждаването + на ел. поща. +_cw: + show: Показване на съдържанието +remoteFollow: Отдалечено последване +messagingUnencryptedInfo: Чатовете във Firefish не са шифровани от край до край. Не + споделяйте чувствителна информация през Firefish. diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 47328cb554..199397153d 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -2289,3 +2289,6 @@ autocorrectNoteLanguage: Mostra un avís si l'idioma de la publicació no coinci amb el resultat de l'idioma detectat automàticament noteEditHistory: Historial d'edicions media: Multimèdia +antennaLimit: El nombre màxim d'antenes que pot crear un usuari +showAddFileDescriptionAtFirstPost: Obra de forma automàtica un formulari per escriure + una descripció quant intentes publicar un fitxer que no en té diff --git a/locales/en-US.yml b/locales/en-US.yml index a6ab32f2e6..7b46795097 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -645,6 +645,7 @@ deletedNote: "Deleted post" invisibleNote: "Invisible post" enableInfiniteScroll: "Automatically load more" visibility: "Visiblility" +cannotEditVisibility: "You can't edit the visibility" poll: "Poll" useCw: "Hide content" enablePlayer: "Open video player" @@ -1010,6 +1011,8 @@ isSystemAccount: "This account is created and automatically operated by the syst Please do not moderate, edit, delete, or otherwise tamper with this account, or it may break your server." typeToConfirm: "Please enter {x} to confirm" +useThisAccountConfirm: "Do you want to continue with this account?" +inputAccountId: "Please input your account (e.g., @firefish@info.firefish.dev)" deleteAccount: "Delete account" document: "Documentation" numberOfPageCache: "Number of cached pages" @@ -1156,6 +1159,9 @@ addRe: "Add \"re:\" at the beginning of comment in reply to a post with a conten confirm: "Confirm" importZip: "Import ZIP" exportZip: "Export ZIP" +getQrCode: "Show QR code" +remoteFollow: "Remote follow" +copyRemoteFollowUrl: "Copy remote follow URL" emojiPackCreator: "Emoji pack creator" indexable: "Indexable" indexableDescription: "Allow built-in search to show your public posts" @@ -2146,6 +2152,7 @@ _notification: reacted: "reacted to your post" renoted: "boosted your post" voted: "voted on your poll" + andCountUsers: "and {count} more users {acted}" _types: all: "All" follow: "New followers" @@ -2233,3 +2240,4 @@ incorrectLanguageWarning: "It looks like your post is in {detected}, but you sel {current}.\nWould you like to set the language to {detected} instead?" noteEditHistory: "Post edit history" slashQuote: "Slash quote" +foldNotification: "Group similar notifications" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 6367dda9f4..5d7a1ecef8 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -928,6 +928,8 @@ colored: "Coloré" label: "Étiquette" localOnly: "Local seulement" account: "Comptes" +getQrCode: "Obtenir le code QR" + _emailUnavailable: used: "Adresse non disponible" format: "Le format de cette adresse de courriel est invalide" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index 4535492e7d..be780444a4 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1825,6 +1825,7 @@ _notification: reacted: mereaksi postinganmu renoted: memposting ulang postinganmu voted: memilih di angketmu + andCountUsers: dan {count} lebih banyak pengguna {acted} _deck: alwaysShowMainColumn: "Selalu tampilkan kolom utama" columnAlign: "Luruskan kolom" @@ -2267,3 +2268,13 @@ markLocalFilesNsfwByDefaultDescription: Terlepas dari pengaturan ini, pengguna d menghapus sendiri tanda NSFW. Berkas yang ada tidak berpengaruh. noteEditHistory: Riwayat penyuntingan kiriman media: Media +antennaLimit: Jumlah antena maksimum yang dapat dibuat oleh setiap pengguna +showAddFileDescriptionAtFirstPost: Buka formulir secara otomatis untuk menulis deskripsi + ketika mencoba mengirim berkas tanpa deskripsi +remoteFollow: Ikuti jarak jauh +foldNotification: Kelompokkan notifikasi yang sama +getQrCode: Tampilkan kode QR +cannotEditVisibility: Kamu tidak bisa menyunting keterlihatan +useThisAccountConfirm: Apakah kamu ingin melanjutkan dengan akun ini? +inputAccountId: Silakan memasukkan akunmu (misalnya, @firefish@info.firefish.dev) +copyRemoteFollowUrl: Salin URL ikuti jarak jauh diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 3f900d1a5e..7bd3db376d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1902,6 +1902,7 @@ _notification: reacted: がリアクションしました renoted: がブーストしました voted: が投票しました + andCountUsers: と{count}人が{acted}しました _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" @@ -2057,3 +2058,12 @@ incorrectLanguageWarning: "この投稿は{detected}で書かれていると判 markLocalFilesNsfwByDefault: このサーバーの全てのファイルをデフォルトでNSFWに設定する markLocalFilesNsfwByDefaultDescription: この設定が有効でも、ユーザーは自分でNSFWのフラグを外すことができます。また、この設定は既存のファイルには影響しません。 noteEditHistory: 編集履歴 +showAddFileDescriptionAtFirstPost: 説明の無い添付ファイルを投稿しようとした際に説明を書く画面を自動で開く +antennaLimit: 各ユーザーが作れるアンテナの最大数 +inputAccountId: 'あなたのアカウントを入力してください(例: @firefish@info.firefish.dev)' +remoteFollow: リモートフォロー +cannotEditVisibility: 公開範囲は変更できません +useThisAccountConfirm: このアカウントで操作を続けますか? +getQrCode: QRコードを表示 +copyRemoteFollowUrl: リモートからフォローするURLをコピー +foldNotification: 同じ種類の通知をまとめて表示する diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 4dac78713a..2b326c4066 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -564,6 +564,7 @@ deletedNote: "已删除的帖子" invisibleNote: "隐藏的帖子" enableInfiniteScroll: "滚动页面以载入更多内容" visibility: "可见性" +cannotEditVisibility: "不能编辑帖子的可见性" poll: "调查问卷" useCw: "隐藏内容" enablePlayer: "打开播放器" @@ -878,6 +879,8 @@ driveCapOverrideCaption: "输入 0 或以下的值将容量重置为默认值。 requireAdminForView: "您需要使用管理员账号登录才能查看。" isSystemAccount: "该账号由系统自动创建。请不要修改、编辑、删除或以其它方式篡改这个账号,否则可能会破坏您的服务器。" typeToConfirm: "输入 {x} 以确认操作" +useThisAccountConfirm: "您想使用此帐户继续执行此操作吗?" +inputAccountId: "请输入您的帐户(例如 @firefish@info.firefish.dev )" deleteAccount: "删除账号" document: "文档" numberOfPageCache: "缓存页数" @@ -1386,7 +1389,7 @@ _poll: _visibility: public: "公开" publicDescription: "您的帖子将出现在公共时间线上" - home: "不公开" + home: "悄悄公开" homeDescription: "仅发送至首页时间线" followers: "仅关注者" followersDescription: "仅对您的关注者和提及的用户可见" @@ -1787,6 +1790,7 @@ _notification: reacted: 回应了您的帖子 voted: 在您的问卷调查中投了票 renoted: 转发了您的帖子 + andCountUsers: "和其他 {count} 名用户{acted}" _deck: alwaysShowMainColumn: "总是显示主列" columnAlign: "列对齐" @@ -1972,6 +1976,9 @@ origin: 起源 confirm: 确认 importZip: 导入 ZIP exportZip: 导出 ZIP +getQrCode: "获取二维码" +remoteFollow: "远程关注" +copyRemoteFollowUrl: "复制远程关注 URL" emojiPackCreator: 表情包创建工具 objectStorageS3ForcePathStyleDesc: 打开此选项可构建格式为 "s3.amazonaws.com//" 而非 ".s3.amazonaws.com" 的端点 URL。 @@ -2060,3 +2067,4 @@ incorrectLanguageWarning: "看上去您帖子使用的语言是{detected},但 noteEditHistory: "帖子编辑历史" media: 媒体 slashQuote: "斜杠引用" +foldNotification: "将通知按同类型分组" diff --git a/package.json b/package.json index 93594a5698..0e5f0e77ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firefish", - "version": "20240421", + "version": "20240424", "repository": { "type": "git", "url": "https://firefish.dev/firefish/firefish.git" diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml index 93c57dc321..c1b511b992 100644 --- a/packages/backend-rs/Cargo.toml +++ b/packages/backend-rs/Cargo.toml @@ -18,21 +18,21 @@ napi = { workspace = true, optional = true, default-features = false, features = napi-derive = { workspace = true, optional = true } argon2 = { workspace = true, features = ["std"] } -async-trait = { workspace = true } basen = { workspace = true } bcrypt = { workspace = true } -cfg-if = { workspace = true } chrono = { workspace = true } cuid2 = { workspace = true } emojis = { workspace = true } idna = { workspace = true } -jsonschema = { workspace = true } +image = { workspace = true } +nom-exif = { workspace = true } once_cell = { workspace = true } -parse-display = { workspace = true } +openssl = { workspace = true, features = ["vendored"] } rand = { workspace = true } redis = { workspace = true } regex = { workspace = true } -schemars = { workspace = true, features = ["chrono"] } +reqwest = { workspace = true, features = ["blocking"] } +rmp-serde = { workspace = true } sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } @@ -40,6 +40,8 @@ serde_yaml = { workspace = true } strum = { workspace = true, features = ["derive"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } url = { workspace = true } urlencoding = { workspace = true } diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts index 69de833dcc..85fd9fa97a 100644 --- a/packages/backend-rs/index.d.ts +++ b/packages/backend-rs/index.d.ts @@ -3,6 +3,21 @@ /* auto-generated by NAPI-RS */ +export const SECOND: number +export const MINUTE: number +export const HOUR: number +export const DAY: number +export const USER_ONLINE_THRESHOLD: number +export const USER_ACTIVE_THRESHOLD: number +/** + * 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 + * 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 EnvConfig { onlyQueue: boolean onlyServer: boolean @@ -38,7 +53,9 @@ export interface ServerConfig { inboxJobPerSec?: number deliverJobMaxAttempts?: number inboxJobMaxAttempts?: number + /** deprecated */ logLevel?: Array + maxLogLevel?: string syslog?: SysLogConfig proxyRemoteFiles?: boolean mediaProxy?: string @@ -148,7 +165,9 @@ export interface Config { inboxJobPerSec?: number deliverJobMaxAttempts?: number inboxJobMaxAttempts?: number + /** deprecated */ logLevel?: Array + maxLogLevel?: string syslog?: SysLogConfig proxyRemoteFiles?: boolean mediaProxy?: string @@ -156,8 +175,8 @@ export interface Config { reservedUsernames?: Array maxUserSignups?: number isManagedHosting?: boolean - maxNoteLength?: number - maxCaptionLength?: number + maxNoteLength: number + maxCaptionLength: number deepl?: DeepLConfig libreTranslate?: LibreTranslateConfig email?: EmailConfig @@ -193,6 +212,7 @@ export interface Acct { } export function stringToAcct(acct: string): Acct export function acctToString(acct: Acct): string +export function addNoteToAntenna(antennaId: string, note: Note): void /** * @param host punycoded instance host * @returns whether the given host should be blocked @@ -228,6 +248,11 @@ export function sqlLikeEscape(src: string): string export function safeForSql(src: string): boolean /** Convert milliseconds to a human readable string */ export function formatMilliseconds(milliseconds: number): string +export interface ImageSize { + width: number + height: number +} +export function getImageSizeFromUrl(url: string): Promise /** TODO: handle name collisions better */ export interface NoteLikeForGetNoteSummary { fileIds: Array @@ -263,6 +288,8 @@ export interface DecodedReaction { export function decodeReaction(reaction: string): DecodedReaction export function countReactions(reactions: Record): Record export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise +/** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */ +export function removeOldAttestationChallenges(): Promise export interface AbuseUserReport { id: string createdAt: Date @@ -990,10 +1017,10 @@ export interface User { isDeleted: boolean driveCapacityOverrideMb: number | null movedToUri: string | null - alsoKnownAs: string | null speakAsCat: boolean emojiModPerm: UserEmojimodpermEnum isIndexable: boolean + alsoKnownAs: Array | null } export interface UserGroup { id: string @@ -1119,9 +1146,41 @@ export interface Webhook { latestSentAt: Date | null latestStatus: number | null } -export function addNoteToAntenna(antennaId: string, note: Note): void -/** Initializes Cuid2 generator. Must be called before any [create_id]. */ -export function initIdGenerator(length: number, fingerprint: string): void +export function initializeRustLogger(): void +export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise +export function unwatchNote(watcherId: string, noteId: string): Promise +export function publishToChannelStream(channelId: string, userId: string): void +export enum ChatEvent { + Message = 'message', + Read = 'read', + Deleted = 'deleted', + Typing = 'typing' +} +export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): void +export enum ChatIndexEvent { + Message = 'message', + Read = 'read' +} +export function publishToChatIndexStream(userId: string, kind: ChatIndexEvent, object: any): void +export interface PackedEmoji { + id: string + aliases: Array + name: string + category: string | null + host: string | null + url: string + license: string | null + width: number | null + height: number | null +} +export function publishToBroadcastStream(emoji: PackedEmoji): void +export interface AbuseUserReportLike { + id: string + targetUserId: string + reporterId: string + comment: string +} +export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): void export function getTimestamp(id: string): number /** * The generated ID results in the form of `[8 chars timestamp] + [cuid2]`. @@ -1131,5 +1190,7 @@ export function getTimestamp(id: string): number * * Ref: https://github.com/paralleldrive/cuid2#parameterized-length */ -export function genId(date?: Date | undefined | null): string +export function genId(): string +/** Generate an ID using a specific datetime */ +export function genIdAt(date: Date): string export function secureRndstr(length?: number | undefined | null): string diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index b4d86dccdf..663bf917f9 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,12 +310,20 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { loadEnv, loadConfig, stringToAcct, acctToString, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, addNoteToAntenna, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding +const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToModerationStream, getTimestamp, genId, genIdAt, secureRndstr } = nativeBinding +module.exports.SECOND = SECOND +module.exports.MINUTE = MINUTE +module.exports.HOUR = HOUR +module.exports.DAY = DAY +module.exports.USER_ONLINE_THRESHOLD = USER_ONLINE_THRESHOLD +module.exports.USER_ACTIVE_THRESHOLD = USER_ACTIVE_THRESHOLD +module.exports.FILE_TYPE_BROWSERSAFE = FILE_TYPE_BROWSERSAFE module.exports.loadEnv = loadEnv module.exports.loadConfig = loadConfig module.exports.stringToAcct = stringToAcct module.exports.acctToString = acctToString +module.exports.addNoteToAntenna = addNoteToAntenna module.exports.isBlockedServer = isBlockedServer module.exports.isSilencedServer = isSilencedServer module.exports.isAllowedServer = isAllowedServer @@ -329,6 +337,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji module.exports.sqlLikeEscape = sqlLikeEscape module.exports.safeForSql = safeForSql module.exports.formatMilliseconds = formatMilliseconds +module.exports.getImageSizeFromUrl = getImageSizeFromUrl module.exports.getNoteSummary = getNoteSummary module.exports.toMastodonId = toMastodonId module.exports.fromMastodonId = fromMastodonId @@ -341,6 +350,7 @@ module.exports.isOldPasswordAlgorithm = isOldPasswordAlgorithm module.exports.decodeReaction = decodeReaction module.exports.countReactions = countReactions module.exports.toDbReaction = toDbReaction +module.exports.removeOldAttestationChallenges = removeOldAttestationChallenges module.exports.AntennaSrcEnum = AntennaSrcEnum module.exports.DriveFileUsageHintEnum = DriveFileUsageHintEnum module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum @@ -352,8 +362,17 @@ module.exports.RelayStatusEnum = RelayStatusEnum module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum -module.exports.addNoteToAntenna = addNoteToAntenna -module.exports.initIdGenerator = initIdGenerator +module.exports.initializeRustLogger = initializeRustLogger +module.exports.watchNote = watchNote +module.exports.unwatchNote = unwatchNote +module.exports.publishToChannelStream = publishToChannelStream +module.exports.ChatEvent = ChatEvent +module.exports.publishToChatStream = publishToChatStream +module.exports.ChatIndexEvent = ChatIndexEvent +module.exports.publishToChatIndexStream = publishToChatIndexStream +module.exports.publishToBroadcastStream = publishToBroadcastStream +module.exports.publishToModerationStream = publishToModerationStream module.exports.getTimestamp = getTimestamp module.exports.genId = genId +module.exports.genIdAt = genIdAt module.exports.secureRndstr = secureRndstr diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs new file mode 100644 index 0000000000..46fe189aca --- /dev/null +++ b/packages/backend-rs/src/config/constant.rs @@ -0,0 +1,67 @@ +#[crate::export] +pub const SECOND: i32 = 1000; +#[crate::export] +pub const MINUTE: i32 = 60 * SECOND; +#[crate::export] +pub const HOUR: i32 = 60 * MINUTE; +#[crate::export] +pub const DAY: i32 = 24 * HOUR; + +#[crate::export] +pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE; +#[crate::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 <- we need to 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 +#[crate::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 6155f15e54..1bc5eaeb7e 100644 --- a/packages/backend-rs/src/config/mod.rs +++ b/packages/backend-rs/src/config/mod.rs @@ -1,4 +1,5 @@ pub use server::CONFIG; +pub mod constant; pub mod environment; pub mod server; diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs index 5a4ee481cd..6bc5a0a0eb 100644 --- a/packages/backend-rs/src/config/server.rs +++ b/packages/backend-rs/src/config/server.rs @@ -36,8 +36,11 @@ struct ServerConfig { pub deliver_job_max_attempts: Option, pub inbox_job_max_attempts: Option, + /// deprecated pub log_level: Option>, + pub max_log_level: Option, + pub syslog: Option, pub proxy_remote_files: Option, @@ -197,7 +200,11 @@ pub struct Config { pub inbox_job_per_sec: Option, pub deliver_job_max_attempts: Option, pub inbox_job_max_attempts: Option, + + /// deprecated pub log_level: Option>, + + pub max_log_level: Option, pub syslog: Option, pub proxy_remote_files: Option, pub media_proxy: Option, @@ -205,8 +212,8 @@ pub struct Config { pub reserved_usernames: Option>, pub max_user_signups: Option, pub is_managed_hosting: Option, - pub max_note_length: Option, - pub max_caption_length: Option, + pub max_note_length: u32, + pub max_caption_length: u32, pub deepl: Option, pub libre_translate: Option, pub email: Option, @@ -346,6 +353,7 @@ fn load_config() -> Config { deliver_job_max_attempts: server_config.deliver_job_max_attempts, inbox_job_max_attempts: server_config.inbox_job_max_attempts, log_level: server_config.log_level, + max_log_level: server_config.max_log_level, syslog: server_config.syslog, proxy_remote_files: server_config.proxy_remote_files, media_proxy: server_config.media_proxy, @@ -353,8 +361,8 @@ fn load_config() -> Config { reserved_usernames: server_config.reserved_usernames, max_user_signups: server_config.max_user_signups, is_managed_hosting: server_config.is_managed_hosting, - max_note_length: server_config.max_note_length, - max_caption_length: server_config.max_caption_length, + max_note_length: server_config.max_note_length.unwrap_or(3000), + max_caption_length: server_config.max_caption_length.unwrap_or(1500), deepl: server_config.deepl, libre_translate: server_config.libre_translate, email: server_config.email, diff --git a/packages/backend-rs/src/database/postgresql.rs b/packages/backend-rs/src/database/postgresql.rs index ec0945fbe3..212183cbf2 100644 --- a/packages/backend-rs/src/database/postgresql.rs +++ b/packages/backend-rs/src/database/postgresql.rs @@ -1,5 +1,6 @@ use crate::config::CONFIG; -use sea_orm::{Database, DbConn, DbErr}; +use sea_orm::{ConnectOptions, Database, DbConn, DbErr}; +use tracing::log::LevelFilter; static DB_CONN: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -12,7 +13,13 @@ async fn init_database() -> Result<&'static DbConn, DbErr> { CONFIG.db.port, CONFIG.db.db, ); - let conn = Database::connect(database_uri).await?; + let option: ConnectOptions = ConnectOptions::new(database_uri) + .sqlx_logging_level(LevelFilter::Trace) + .to_owned(); + + tracing::info!("Initializing PostgreSQL connection"); + + let conn = Database::connect(option).await?; Ok(DB_CONN.get_or_init(move || conn)) } diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs index f8f6c712de..fe5d3bb5d5 100644 --- a/packages/backend-rs/src/database/redis.rs +++ b/packages/backend-rs/src/database/redis.rs @@ -26,6 +26,8 @@ fn init_redis() -> Result { params.concat() }; + tracing::info!("Initializing Redis connection"); + Client::open(redis_url) } diff --git a/packages/backend-rs/src/misc/add_note_to_antenna.rs b/packages/backend-rs/src/misc/add_note_to_antenna.rs new file mode 100644 index 0000000000..2ce4e655d7 --- /dev/null +++ b/packages/backend-rs/src/misc/add_note_to_antenna.rs @@ -0,0 +1,31 @@ +use crate::database::{redis_conn, redis_key}; +use crate::model::entity::note; +use crate::service::stream; +use crate::util::id::{get_timestamp, InvalidIdErr}; +use redis::{streams::StreamMaxlen, Commands, RedisError}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Redis error: {0}")] + RedisErr(#[from] RedisError), + #[error("Invalid ID: {0}")] + InvalidIdErr(#[from] InvalidIdErr), + #[error("Stream error: {0}")] + StreamErr(#[from] stream::Error), +} + +type Note = note::Model; + +#[crate::export] +pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), Error> { + // for timeline API + redis_conn()?.xadd_maxlen( + redis_key(format!("antennaTimeline:{}", antenna_id)), + StreamMaxlen::Approx(200), + format!("{}-*", get_timestamp(¬e.id)?), + &[("note", ¬e.id)], + )?; + + // for streaming API + Ok(stream::antenna::publish(antenna_id, note)?) +} diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs index 18b550c29b..cee262c35a 100644 --- a/packages/backend-rs/src/misc/check_word_mute.rs +++ b/packages/backend-rs/src/misc/check_word_mute.rs @@ -39,7 +39,7 @@ async fn all_texts(note: NoteLike) -> Result, DbErr> { .flatten(), ); - if let Some(renote_id) = note.renote_id { + if let Some(renote_id) = ¬e.renote_id { if let Some((text, cw)) = note::Entity::find_by_id(renote_id) .select_only() .columns([note::Column::Text, note::Column::Cw]) @@ -53,10 +53,12 @@ async fn all_texts(note: NoteLike) -> Result, DbErr> { if let Some(c) = cw { texts.push(c); } + } else { + tracing::warn!("nonexistent renote id: {:#?}", renote_id); } } - if let Some(reply_id) = note.reply_id { + if let Some(reply_id) = ¬e.reply_id { if let Some((text, cw)) = note::Entity::find_by_id(reply_id) .select_only() .columns([note::Column::Text, note::Column::Cw]) @@ -70,6 +72,8 @@ async fn all_texts(note: NoteLike) -> Result, DbErr> { if let Some(c) = cw { texts.push(c); } + } else { + tracing::warn!("nonexistent reply id: {:#?}", reply_id); } } diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs new file mode 100644 index 0000000000..9c55846424 --- /dev/null +++ b/packages/backend-rs/src/misc/get_image_size.rs @@ -0,0 +1,200 @@ +use crate::misc::redis_cache::{get_cache, set_cache, CacheError}; +use crate::util::http_client; +use image::{io::Reader, ImageError, ImageFormat}; +use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag}; +use std::io::Cursor; +use tokio::sync::Mutex; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Redis cache error: {0}")] + CacheErr(#[from] CacheError), + #[error("Reqwest error: {0}")] + ReqwestErr(#[from] reqwest::Error), + #[error("Image decoding error: {0}")] + ImageErr(#[from] ImageError), + #[error("Image decoding error: {0}")] + IoErr(#[from] std::io::Error), + #[error("Exif extraction error: {0}")] + ExifErr(#[from] nom_exif::Error), + #[error("Emoji meta attempt limit exceeded: {0}")] + TooManyAttempts(String), + #[error("Unsupported image type: {0}")] + UnsupportedImageErr(String), +} + +const BROWSER_SAFE_IMAGE_TYPES: [ImageFormat; 8] = [ + ImageFormat::Png, + ImageFormat::Jpeg, + ImageFormat::Gif, + ImageFormat::WebP, + ImageFormat::Tiff, + ImageFormat::Bmp, + ImageFormat::Ico, + ImageFormat::Avif, +]; + +static MTX_GUARD: Mutex<()> = Mutex::const_new(()); + +#[derive(Debug, PartialEq)] +#[crate::export(object)] +pub struct ImageSize { + pub width: u32, + pub height: u32, +} + +#[crate::export] +pub async fn get_image_size_from_url(url: &str) -> Result { + let attempted: bool; + + { + let _ = MTX_GUARD.lock().await; + + let key = format!("fetchImage:{}", url); + attempted = get_cache::(&key)?.is_some(); + + if !attempted { + set_cache(&key, &true, 10 * 60)?; + } + } + + if attempted { + tracing::warn!("attempt limit exceeded: {}", url); + return Err(Error::TooManyAttempts(url.to_string())); + } + + tracing::info!("retrieving image size from {}", url); + + let image_bytes = http_client()?.get(url).send().await?.bytes().await?; + let reader = Reader::new(Cursor::new(&image_bytes)).with_guessed_format()?; + + let format = reader.format(); + if format.is_none() || !BROWSER_SAFE_IMAGE_TYPES.contains(&format.unwrap()) { + return Err(Error::UnsupportedImageErr(format!("{:?}", format))); + } + + let size = reader.into_dimensions()?; + + let res = ImageSize { + width: size.0, + height: size.1, + }; + + if format.unwrap() != ImageFormat::Jpeg { + return Ok(res); + } + + // handle jpeg orientation + // https://magnushoff.com/articles/jpeg-orientation/ + + let exif = parse_jpeg_exif(&*image_bytes)?; + if exif.is_none() { + return Ok(res); + } + + let orientation = exif.unwrap().get_value(&ExifTag::Orientation)?; + let rotated = + orientation.is_some() && matches!(orientation.unwrap(), EntryValue::U32(v) if v >= 5); + + if !rotated { + return Ok(res); + } + + Ok(ImageSize { + width: size.1, + height: size.0, + }) +} + +#[cfg(test)] +mod unit_test { + use super::{get_image_size_from_url, ImageSize}; + use crate::misc::redis_cache::delete_cache; + use pretty_assertions::assert_eq; + + #[tokio::test] + async fn test_get_image_size() { + let png_url_1 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/splash.png"; + let png_url_2 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/notification-badges/at.png"; + let png_url_3 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/api-doc.png"; + let rotated_jpeg_url = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/test/resources/rotate.jpg"; + let webp_url_1 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/custom/assets/badges/error.webp"; + let webp_url_2 = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/screenshots/1.webp"; + let ico_url = "https://firefish.dev/firefish/firefish/-/raw/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/favicon.ico"; + let gif_url = "https://firefish.dev/firefish/firefish/-/raw/b9c3dfbd3d473cb2cee20c467eeae780bc401271/packages/backend/test/resources/anime.gif"; + let mp3_url = "https://firefish.dev/firefish/firefish/-/blob/5891a90f71a8b9d5ea99c683ade7e485c685d642/packages/backend/assets/sounds/aisha/1.mp3"; + + // Delete caches in case you run this test multiple times + // (should be disabled in CI tasks) + delete_cache(&format!("fetchImage:{}", png_url_1)).unwrap(); + delete_cache(&format!("fetchImage:{}", png_url_2)).unwrap(); + delete_cache(&format!("fetchImage:{}", png_url_3)).unwrap(); + delete_cache(&format!("fetchImage:{}", rotated_jpeg_url)).unwrap(); + delete_cache(&format!("fetchImage:{}", webp_url_1)).unwrap(); + delete_cache(&format!("fetchImage:{}", webp_url_2)).unwrap(); + delete_cache(&format!("fetchImage:{}", ico_url)).unwrap(); + delete_cache(&format!("fetchImage:{}", gif_url)).unwrap(); + delete_cache(&format!("fetchImage:{}", mp3_url)).unwrap(); + + let png_size_1 = ImageSize { + width: 1024, + height: 1024, + }; + let png_size_2 = ImageSize { + width: 96, + height: 96, + }; + let png_size_3 = ImageSize { + width: 1024, + height: 354, + }; + let rotated_jpeg_size = ImageSize { + width: 256, + height: 512, + }; + let webp_size_1 = ImageSize { + width: 256, + height: 256, + }; + let webp_size_2 = ImageSize { + width: 1080, + height: 2340, + }; + let ico_size = ImageSize { + width: 256, + height: 256, + }; + let gif_size = ImageSize { + width: 256, + height: 256, + }; + + assert_eq!( + png_size_1, + get_image_size_from_url(png_url_1).await.unwrap() + ); + assert_eq!( + png_size_2, + get_image_size_from_url(png_url_2).await.unwrap() + ); + assert_eq!( + png_size_3, + get_image_size_from_url(png_url_3).await.unwrap() + ); + assert_eq!( + rotated_jpeg_size, + get_image_size_from_url(rotated_jpeg_url).await.unwrap() + ); + assert_eq!( + webp_size_1, + get_image_size_from_url(webp_url_1).await.unwrap() + ); + assert_eq!( + webp_size_2, + get_image_size_from_url(webp_url_2).await.unwrap() + ); + assert_eq!(ico_size, get_image_size_from_url(ico_url).await.unwrap()); + assert_eq!(gif_size, get_image_size_from_url(gif_url).await.unwrap()); + assert!(get_image_size_from_url(mp3_url).await.is_err()); + } +} diff --git a/packages/backend-rs/src/misc/mastodon_id.rs b/packages/backend-rs/src/misc/mastodon_id.rs index 94ba561b58..9cf5d8d5f5 100644 --- a/packages/backend-rs/src/misc/mastodon_id.rs +++ b/packages/backend-rs/src/misc/mastodon_id.rs @@ -1,6 +1,6 @@ #[crate::export] pub fn to_mastodon_id(firefish_id: &str) -> Option { - let decoded: [u8; 16] = basen::BASE36.decode_var_len(&firefish_id.to_ascii_lowercase())?; + let decoded: [u8; 16] = basen::BASE36.decode_var_len(firefish_id)?; Some(basen::BASE10.encode_var_len(&decoded)) } diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 56aae3b552..0ba15dc8e4 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -1,13 +1,17 @@ pub mod acct; +pub mod add_note_to_antenna; pub mod check_server_block; pub mod check_word_mute; pub mod convert_host; pub mod emoji; pub mod escape_sql; pub mod format_milliseconds; +pub mod get_image_size; pub mod get_note_summary; pub mod mastodon_id; pub mod meta; pub mod nyaify; pub mod password; pub mod reaction; +pub mod redis_cache; +pub mod remove_old_attestation_challenges; diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs index a29ddf95de..f0afe345da 100644 --- a/packages/backend-rs/src/misc/reaction.rs +++ b/packages/backend-rs/src/misc/reaction.rs @@ -97,6 +97,8 @@ pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Resul { return Ok(format!(":{name}@{ascii_host}:")); } + + tracing::info!("nonexistent remote custom emoji: :{name}@{ascii_host}:"); } else { // local emoji // TODO: Does SeaORM have the `exists` method? @@ -109,6 +111,8 @@ pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Resul { return Ok(format!(":{name}:")); } + + tracing::info!("nonexistent local custom emoji: :{name}:"); } }; }; diff --git a/packages/backend-rs/src/misc/redis_cache.rs b/packages/backend-rs/src/misc/redis_cache.rs new file mode 100644 index 0000000000..81ac840b8d --- /dev/null +++ b/packages/backend-rs/src/misc/redis_cache.rs @@ -0,0 +1,94 @@ +use crate::database::{redis_conn, redis_key}; +use redis::{Commands, RedisError}; +use serde::{Deserialize, Serialize}; + +#[derive(thiserror::Error, Debug)] +pub enum CacheError { + #[error("Redis error: {0}")] + RedisError(#[from] RedisError), + #[error("Data serialization error: {0}")] + SerializeError(#[from] rmp_serde::encode::Error), + #[error("Data deserialization error: {0}")] + DeserializeError(#[from] rmp_serde::decode::Error), +} + +fn prefix_key(key: &str) -> String { + redis_key(format!("cache:{}", key)) +} + +pub fn set_cache Deserialize<'a> + Serialize>( + key: &str, + value: &V, + expire_seconds: u64, +) -> Result<(), CacheError> { + redis_conn()?.set_ex( + prefix_key(key), + rmp_serde::encode::to_vec(&value)?, + expire_seconds, + )?; + Ok(()) +} + +pub fn get_cache Deserialize<'a> + Serialize>( + key: &str, +) -> Result, CacheError> { + let serialized_value: Option> = redis_conn()?.get(prefix_key(key))?; + Ok(match serialized_value { + Some(v) => Some(rmp_serde::from_slice::(v.as_ref())?), + None => None, + }) +} + +pub fn delete_cache(key: &str) -> Result<(), CacheError> { + Ok(redis_conn()?.del(prefix_key(key))?) +} + +#[cfg(test)] +mod unit_test { + use super::{get_cache, set_cache}; + use pretty_assertions::assert_eq; + + #[test] + fn set_get_expire() { + #[derive(serde::Deserialize, serde::Serialize, PartialEq, Debug)] + struct Data { + id: u32, + kind: String, + } + + let key_1 = "CARGO_TEST_CACHE_KEY_1"; + let value_1: Vec = vec![1, 2, 3, 4, 5]; + + let key_2 = "CARGO_TEST_CACHE_KEY_2"; + let value_2 = "Hello fedizens".to_string(); + + let key_3 = "CARGO_TEST_CACHE_KEY_3"; + let value_3 = Data { + id: 1000000007, + kind: "prime number".to_string(), + }; + + set_cache(key_1, &value_1, 1).unwrap(); + set_cache(key_2, &value_2, 1).unwrap(); + set_cache(key_3, &value_3, 1).unwrap(); + + let cached_value_1: Vec = get_cache(key_1).unwrap().unwrap(); + let cached_value_2: String = get_cache(key_2).unwrap().unwrap(); + let cached_value_3: Data = get_cache(key_3).unwrap().unwrap(); + + assert_eq!(value_1, cached_value_1); + assert_eq!(value_2, cached_value_2); + assert_eq!(value_3, cached_value_3); + + // wait for the cache to expire + std::thread::sleep(std::time::Duration::from_millis(1100)); + + let expired_value_1: Option> = get_cache(key_1).unwrap(); + let expired_value_2: Option> = get_cache(key_2).unwrap(); + let expired_value_3: Option> = get_cache(key_3).unwrap(); + + assert!(expired_value_1.is_none()); + assert!(expired_value_2.is_none()); + assert!(expired_value_3.is_none()); + } +} diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs new file mode 100644 index 0000000000..a70b784bae --- /dev/null +++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs @@ -0,0 +1,19 @@ +// TODO: We want to get rid of this + +use crate::database::db_conn; +use crate::model::entity::attestation_challenge; +use chrono::{Duration, Local}; +use sea_orm::{ColumnTrait, DbErr, EntityTrait, QueryFilter}; + +/// Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago +#[crate::export] +pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> { + let res = attestation_challenge::Entity::delete_many() + .filter(attestation_challenge::Column::CreatedAt.lt(Local::now() - Duration::minutes(5))) + .exec(db_conn().await?) + .await?; + + tracing::info!("{} attestation challenges are removed", res.rows_affected); + + Ok(()) +} diff --git a/packages/backend-rs/src/model/entity/user.rs b/packages/backend-rs/src/model/entity/user.rs index f8a2b09324..3ceac93fb5 100644 --- a/packages/backend-rs/src/model/entity/user.rs +++ b/packages/backend-rs/src/model/entity/user.rs @@ -71,14 +71,14 @@ pub struct Model { pub drive_capacity_override_mb: Option, #[sea_orm(column_name = "movedToUri")] pub moved_to_uri: Option, - #[sea_orm(column_name = "alsoKnownAs", column_type = "Text", nullable)] - pub also_known_as: Option, #[sea_orm(column_name = "speakAsCat")] pub speak_as_cat: bool, #[sea_orm(column_name = "emojiModPerm")] pub emoji_mod_perm: UserEmojimodpermEnum, #[sea_orm(column_name = "isIndexable")] pub is_indexable: bool, + #[sea_orm(column_name = "alsoKnownAs")] + pub also_known_as: Option>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/packages/backend-rs/src/model/error.rs b/packages/backend-rs/src/model/error.rs deleted file mode 100644 index b3d9f6eda5..0000000000 --- a/packages/backend-rs/src/model/error.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[derive(thiserror::Error, Debug, PartialEq, Eq)] -pub enum Error { - #[error("Failed to parse string: {0}")] - ParseError(#[from] parse_display::ParseError), - #[error("Database error: {0}")] - DbError(#[from] sea_orm::DbErr), - #[error("Requested entity not found")] - NotFound, -} diff --git a/packages/backend-rs/src/model/mod.rs b/packages/backend-rs/src/model/mod.rs index d92cf709a6..e8c3d6a4c6 100644 --- a/packages/backend-rs/src/model/mod.rs +++ b/packages/backend-rs/src/model/mod.rs @@ -1,4 +1 @@ pub mod entity; -pub mod error; -// pub mod repository; -pub mod schema; diff --git a/packages/backend-rs/src/model/repository.rs b/packages/backend-rs/src/model/repository.rs deleted file mode 100644 index be86ff22ba..0000000000 --- a/packages/backend-rs/src/model/repository.rs +++ /dev/null @@ -1,31 +0,0 @@ -use async_trait::async_trait; -use schemars::JsonSchema; - -use super::error::Error; - -/// Repositories have a packer that converts a database model to its -/// corresponding API schema. -#[async_trait] -pub trait Repository { - async fn pack(self) -> Result; - /// Retrieves one model by its id and pack it. - async fn pack_by_id(id: String) -> Result; -} - -mod macros { - /// Provides the default implementation of - /// [crate::model::repository::Repository::pack_by_id]. - macro_rules! impl_pack_by_id { - ($a:ty, $b:ident) => { - match <$a>::find_by_id($b) - .one(crate::database::get_database()?) - .await? - { - None => Err(Error::NotFound), - Some(m) => m.pack().await, - } - }; - } - - pub(crate) use impl_pack_by_id; -} diff --git a/packages/backend-rs/src/model/schema.rs b/packages/backend-rs/src/model/schema.rs deleted file mode 100644 index 843a9351b0..0000000000 --- a/packages/backend-rs/src/model/schema.rs +++ /dev/null @@ -1,18 +0,0 @@ -use jsonschema::JSONSchema; -use schemars::{schema_for, JsonSchema}; - -/// Structs of schema defitions implement this trait in order to -/// provide the JSON Schema validator [`jsonschema::JSONSchema`]. -pub trait Schema { - /// Returns the validator of [JSON Schema Draft - /// 7](https://json-schema.org/specification-links.html#draft-7) with the - /// default settings of [`schemars::gen::SchemaSettings`]. - fn validator() -> JSONSchema { - let root = schema_for!(T); - let schema = serde_json::to_value(&root).expect("Schema definition invalid"); - JSONSchema::options() - .with_draft(jsonschema::Draft::Draft7) - .compile(&schema) - .expect("Unable to compile schema") - } -} diff --git a/packages/backend-rs/src/service/add_note_to_antenna.rs b/packages/backend-rs/src/service/add_note_to_antenna.rs deleted file mode 100644 index 4f294cc881..0000000000 --- a/packages/backend-rs/src/service/add_note_to_antenna.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::database::{redis_conn, redis_key}; -use crate::model::entity::note; -use crate::service::stream::{publish_to_stream, Error, Stream}; -use crate::util::id::get_timestamp; -use redis::{streams::StreamMaxlen, Commands}; - -type Note = note::Model; - -#[crate::export] -pub fn add_note_to_antenna(antenna_id: String, note: &Note) -> Result<(), Error> { - redis_conn()?.xadd_maxlen( - redis_key(format!("antennaTimeline:{}", antenna_id)), - StreamMaxlen::Approx(200), - format!("{}-*", get_timestamp(¬e.id)), - &[("note", ¬e.id)], - )?; - - publish_to_stream( - &Stream::Antenna { antenna_id }, - Some("note"), - Some(serde_json::to_string(note)?), - ) -} diff --git a/packages/backend-rs/src/service/log.rs b/packages/backend-rs/src/service/log.rs new file mode 100644 index 0000000000..07966b77ad --- /dev/null +++ b/packages/backend-rs/src/service/log.rs @@ -0,0 +1,51 @@ +use crate::config::CONFIG; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; + +#[crate::export(js_name = "initializeRustLogger")] +pub fn initialize_logger() { + let mut builder = FmtSubscriber::builder(); + + if let Some(max_level) = &CONFIG.max_log_level { + builder = builder.with_max_level(match max_level.as_str() { + "error" => Level::ERROR, + "warning" => Level::WARN, + "info" => Level::INFO, + "debug" => Level::DEBUG, + "trace" => Level::TRACE, + _ => Level::INFO, // Fallback + }); + } else if let Some(levels) = &CONFIG.log_level { + // `logLevel` config is Deprecated + if levels.contains(&"trace".to_string()) { + builder = builder.with_max_level(Level::TRACE); + } else if levels.contains(&"debug".to_string()) { + builder = builder.with_max_level(Level::DEBUG); + } else if levels.contains(&"info".to_string()) { + builder = builder.with_max_level(Level::INFO); + } else if levels.contains(&"warning".to_string()) { + builder = builder.with_max_level(Level::WARN); + } else if levels.contains(&"error".to_string()) { + builder = builder.with_max_level(Level::ERROR); + } else { + // Fallback + builder = builder.with_max_level(Level::INFO); + } + } else { + // Fallback + builder = builder.with_max_level(Level::INFO); + }; + + let subscriber = builder + .without_time() + .with_level(true) + .with_ansi(true) + .with_target(true) + .with_thread_names(true) + .with_line_number(true) + .log_internal_errors(true) + .compact() + .finish(); + + tracing::subscriber::set_global_default(subscriber).expect("Failed to initialize the logger"); +} diff --git a/packages/backend-rs/src/service/mod.rs b/packages/backend-rs/src/service/mod.rs index cc239e3f9e..0a2644857f 100644 --- a/packages/backend-rs/src/service/mod.rs +++ b/packages/backend-rs/src/service/mod.rs @@ -1,2 +1,3 @@ -pub mod add_note_to_antenna; +pub mod log; +pub mod note; pub mod stream; diff --git a/packages/backend-rs/src/service/note/mod.rs b/packages/backend-rs/src/service/note/mod.rs new file mode 100644 index 0000000000..f5362807d2 --- /dev/null +++ b/packages/backend-rs/src/service/note/mod.rs @@ -0,0 +1 @@ +pub mod watch; diff --git a/packages/backend-rs/src/service/note/watch.rs b/packages/backend-rs/src/service/note/watch.rs new file mode 100644 index 0000000000..f740ec6ce4 --- /dev/null +++ b/packages/backend-rs/src/service/note/watch.rs @@ -0,0 +1,42 @@ +use crate::database::db_conn; +use crate::model::entity::note_watching; +use crate::util::id::gen_id; +use sea_orm::{ActiveValue, ColumnTrait, DbErr, EntityTrait, ModelTrait, QueryFilter}; + +#[crate::export] +pub async fn watch_note( + watcher_id: &str, + note_author_id: &str, + note_id: &str, +) -> Result<(), DbErr> { + if watcher_id != note_author_id { + note_watching::Entity::insert(note_watching::ActiveModel { + id: ActiveValue::set(gen_id()), + created_at: ActiveValue::set(chrono::Local::now().naive_local()), + user_id: ActiveValue::Set(watcher_id.to_string()), + note_user_id: ActiveValue::Set(note_author_id.to_string()), + note_id: ActiveValue::Set(note_id.to_string()), + }) + .exec(db_conn().await?) + .await?; + } + + Ok(()) +} + +#[crate::export] +pub async fn unwatch_note(watcher_id: &str, note_id: &str) -> Result<(), DbErr> { + let db = db_conn().await?; + + let entry = note_watching::Entity::find() + .filter(note_watching::Column::UserId.eq(watcher_id)) + .filter(note_watching::Column::NoteId.eq(note_id)) + .one(db) + .await?; + + if let Some(entry) = entry { + entry.delete(db).await?; + } + + Ok(()) +} diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs index 6c5e6be4dd..3f53dea131 100644 --- a/packages/backend-rs/src/service/stream.rs +++ b/packages/backend-rs/src/service/stream.rs @@ -1,3 +1,10 @@ +pub mod antenna; +pub mod channel; +pub mod chat; +pub mod chat_index; +pub mod custom_emoji; +pub mod moderation; + use crate::config::CONFIG; use crate::database::redis_conn; use redis::{Commands, RedisError}; @@ -7,9 +14,9 @@ pub enum Stream { #[strum(serialize = "internal")] Internal, #[strum(serialize = "broadcast")] - Broadcast, - #[strum(to_string = "adminStream:{user_id}")] - Admin { user_id: String }, + CustomEmoji, + #[strum(to_string = "adminStream:{moderator_id}")] + Moderation { moderator_id: String }, #[strum(to_string = "user:{user_id}")] User { user_id: String }, #[strum(to_string = "channelStream:{channel_id}")] @@ -34,7 +41,7 @@ pub enum Stream { #[strum(to_string = "messagingStream:{group_id}")] GroupChat { group_id: String }, #[strum(to_string = "messagingIndexStream:{user_id}")] - MessagingIndex { user_id: String }, + ChatIndex { user_id: String }, } #[derive(thiserror::Error, Debug)] @@ -49,12 +56,12 @@ pub enum Error { pub fn publish_to_stream( stream: &Stream, - kind: Option<&str>, + kind: Option, value: Option, ) -> Result<(), Error> { let message = if let Some(kind) = kind { format!( - "{{ \"type\": \"{}\", \"body\": {} }}", + "{{\"type\":\"{}\",\"body\":{}}}", kind, value.unwrap_or("null".to_string()), ) @@ -64,10 +71,7 @@ pub fn publish_to_stream( redis_conn()?.publish( &CONFIG.host, - format!( - "{{ \"channel\": \"{}\", \"message\": {} }}", - stream, message, - ), + format!("{{\"channel\":\"{}\",\"message\":{}}}", stream, message), )?; Ok(()) @@ -81,10 +85,10 @@ mod unit_test { #[test] fn channel_to_string() { assert_eq!(Stream::Internal.to_string(), "internal"); - assert_eq!(Stream::Broadcast.to_string(), "broadcast"); + assert_eq!(Stream::CustomEmoji.to_string(), "broadcast"); assert_eq!( - Stream::Admin { - user_id: "9tb42br63g5apjcq".to_string() + Stream::Moderation { + moderator_id: "9tb42br63g5apjcq".to_string() } .to_string(), "adminStream:9tb42br63g5apjcq" diff --git a/packages/backend-rs/src/service/stream/antenna.rs b/packages/backend-rs/src/service/stream/antenna.rs new file mode 100644 index 0000000000..3a829df546 --- /dev/null +++ b/packages/backend-rs/src/service/stream/antenna.rs @@ -0,0 +1,10 @@ +use crate::model::entity::note; +use crate::service::stream::{publish_to_stream, Error, Stream}; + +pub fn publish(antenna_id: String, note: ¬e::Model) -> Result<(), Error> { + publish_to_stream( + &Stream::Antenna { antenna_id }, + Some("note".to_string()), + Some(serde_json::to_string(note)?), + ) +} diff --git a/packages/backend-rs/src/service/stream/channel.rs b/packages/backend-rs/src/service/stream/channel.rs new file mode 100644 index 0000000000..10a04c5e66 --- /dev/null +++ b/packages/backend-rs/src/service/stream/channel.rs @@ -0,0 +1,10 @@ +use crate::service::stream::{publish_to_stream, Error, Stream}; + +#[crate::export(js_name = "publishToChannelStream")] +pub fn publish(channel_id: String, user_id: String) -> Result<(), Error> { + publish_to_stream( + &Stream::Channel { channel_id }, + Some("typing".to_string()), + Some(format!("\"{}\"", user_id)), + ) +} diff --git a/packages/backend-rs/src/service/stream/chat.rs b/packages/backend-rs/src/service/stream/chat.rs new file mode 100644 index 0000000000..3015d921e1 --- /dev/null +++ b/packages/backend-rs/src/service/stream/chat.rs @@ -0,0 +1,34 @@ +use crate::service::stream::{publish_to_stream, Error, Stream}; + +#[derive(strum::Display)] +#[crate::export(string_enum = "camelCase")] +pub enum ChatEvent { + #[strum(serialize = "message")] + Message, + #[strum(serialize = "read")] + Read, + #[strum(serialize = "deleted")] + Deleted, + #[strum(serialize = "typing")] + Typing, +} + +// We want to merge `kind` and `object` into a single enum +// https://github.com/napi-rs/napi-rs/issues/2036 + +#[crate::export(js_name = "publishToChatStream")] +pub fn publish( + sender_user_id: String, + receiver_user_id: String, + kind: ChatEvent, + object: &serde_json::Value, +) -> Result<(), Error> { + publish_to_stream( + &Stream::Chat { + sender_user_id, + receiver_user_id, + }, + Some(kind.to_string()), + Some(serde_json::to_string(object)?), + ) +} diff --git a/packages/backend-rs/src/service/stream/chat_index.rs b/packages/backend-rs/src/service/stream/chat_index.rs new file mode 100644 index 0000000000..eb64384dca --- /dev/null +++ b/packages/backend-rs/src/service/stream/chat_index.rs @@ -0,0 +1,26 @@ +use crate::service::stream::{publish_to_stream, Error, Stream}; + +#[derive(strum::Display)] +#[crate::export(string_enum = "camelCase")] +pub enum ChatIndexEvent { + #[strum(serialize = "message")] + Message, + #[strum(serialize = "read")] + Read, +} + +// We want to merge `kind` and `object` into a single enum +// https://github.com/napi-rs/napi-rs/issues/2036 + +#[crate::export(js_name = "publishToChatIndexStream")] +pub fn publish( + user_id: String, + kind: ChatIndexEvent, + object: &serde_json::Value, +) -> Result<(), Error> { + publish_to_stream( + &Stream::ChatIndex { user_id }, + Some(kind.to_string()), + Some(serde_json::to_string(object)?), + ) +} diff --git a/packages/backend-rs/src/service/stream/custom_emoji.rs b/packages/backend-rs/src/service/stream/custom_emoji.rs new file mode 100644 index 0000000000..21158fc761 --- /dev/null +++ b/packages/backend-rs/src/service/stream/custom_emoji.rs @@ -0,0 +1,27 @@ +use crate::service::stream::{publish_to_stream, Error, Stream}; +use serde::{Deserialize, Serialize}; + +// TODO: define schema type in other place +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[crate::export(object)] +pub struct PackedEmoji { + pub id: String, + pub aliases: Vec, + pub name: String, + pub category: Option, + pub host: Option, + pub url: String, + pub license: Option, + pub width: Option, + pub height: Option, +} + +#[crate::export(js_name = "publishToBroadcastStream")] +pub fn publish(emoji: &PackedEmoji) -> Result<(), Error> { + publish_to_stream( + &Stream::CustomEmoji, + Some("emojiAdded".to_string()), + Some(format!("{{\"emoji\":{}}}", serde_json::to_string(emoji)?)), + ) +} diff --git a/packages/backend-rs/src/service/stream/moderation.rs b/packages/backend-rs/src/service/stream/moderation.rs new file mode 100644 index 0000000000..576bf9fd21 --- /dev/null +++ b/packages/backend-rs/src/service/stream/moderation.rs @@ -0,0 +1,21 @@ +use crate::service::stream::{publish_to_stream, Error, Stream}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[crate::export(object)] +pub struct AbuseUserReportLike { + pub id: String, + pub target_user_id: String, + pub reporter_id: String, + pub comment: String, +} + +#[crate::export(js_name = "publishToModerationStream")] +pub fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> { + publish_to_stream( + &Stream::Moderation { moderator_id }, + Some("newAbuseUserReport".to_string()), + Some(serde_json::to_string(report)?), + ) +} diff --git a/packages/backend-rs/src/util/http_client.rs b/packages/backend-rs/src/util/http_client.rs new file mode 100644 index 0000000000..143e1a2885 --- /dev/null +++ b/packages/backend-rs/src/util/http_client.rs @@ -0,0 +1,24 @@ +use crate::config::CONFIG; +use once_cell::sync::OnceCell; +use reqwest::{Client, Error, NoProxy, Proxy}; +use std::time::Duration; + +static CLIENT: OnceCell = OnceCell::new(); + +pub fn http_client() -> Result { + CLIENT + .get_or_try_init(|| { + let mut builder = Client::builder().timeout(Duration::from_secs(5)); + + if let Some(proxy_url) = &CONFIG.proxy { + let mut proxy = Proxy::all(proxy_url)?; + if let Some(proxy_bypass_hosts) = &CONFIG.proxy_bypass_hosts { + proxy = proxy.no_proxy(NoProxy::from_string(&proxy_bypass_hosts.join(","))); + } + builder = builder.proxy(proxy); + } + + builder.build() + }) + .cloned() +} diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs index 20b2f2b74c..595a8ebb3a 100644 --- a/packages/backend-rs/src/util/id.rs +++ b/packages/backend-rs/src/util/id.rs @@ -1,95 +1,109 @@ //! ID generation utility based on [cuid2] +use crate::config::CONFIG; use basen::BASE36; -use cfg_if::cfg_if; -use chrono::NaiveDateTime; +use chrono::{DateTime, NaiveDateTime, Utc}; use once_cell::sync::OnceCell; use std::cmp; -#[derive(thiserror::Error, Debug, PartialEq, Eq)] -#[error("ID generator has not been initialized yet")] -pub struct ErrorUninitialized; - static FINGERPRINT: OnceCell = OnceCell::new(); static GENERATOR: OnceCell = OnceCell::new(); const TIME_2000: i64 = 946_684_800_000; -const TIMESTAMP_LENGTH: u16 = 8; +const TIMESTAMP_LENGTH: u8 = 8; -/// Initializes Cuid2 generator. Must be called before any [create_id]. -#[crate::export] -pub fn init_id_generator(length: u16, fingerprint: &str) { +/// Initializes Cuid2 generator. +fn init_id_generator(length: u8, fingerprint: &str) { FINGERPRINT.get_or_init(move || format!("{}{}", fingerprint, cuid2::create_id())); GENERATOR.get_or_init(move || { cuid2::CuidConstructor::new() // length to pass shoule be greater than or equal to 8. - .with_length(cmp::max(length - TIMESTAMP_LENGTH, 8)) + .with_length(cmp::max(length - TIMESTAMP_LENGTH, 8).into()) .with_fingerprinter(|| FINGERPRINT.get().unwrap().clone()) }); } -/// Returns Cuid2 with the length specified by [init_id]. Must be called after -/// [init_id], otherwise returns [ErrorUninitialized]. -pub fn create_id(datetime: &NaiveDateTime) -> Result { - match GENERATOR.get() { - None => Err(ErrorUninitialized), - Some(gen) => { - let date_num = cmp::max(0, datetime.and_utc().timestamp_millis() - TIME_2000) as u64; - Ok(format!( - "{:0>8}{}", - BASE36.encode_var_len(&date_num), - gen.create_id() - )) - } +/// Returns Cuid2 with the length specified by [init_id_generator]. +/// It automatically calls [init_id_generator], if the generator has not been initialized. +fn create_id(datetime: &NaiveDateTime) -> String { + if GENERATOR.get().is_none() { + let length = match &CONFIG.cuid { + Some(cuid) => cmp::min(cmp::max(cuid.length.unwrap_or(16), 16), 24), + None => 16, + }; + let fingerprint = match &CONFIG.cuid { + Some(cuid) => cuid.fingerprint.as_deref().unwrap_or_default(), + None => "", + }; + init_id_generator(length, fingerprint); } + let date_num = cmp::max(0, datetime.and_utc().timestamp_millis() - TIME_2000) as u64; + format!( + "{:0>8}{}", + BASE36.encode_var_len(&date_num), + GENERATOR.get().unwrap().create_id() + ) +} + +#[derive(thiserror::Error, Debug)] +#[error("Invalid ID: {id}")] +pub struct InvalidIdErr { + id: String, } #[crate::export] -pub fn get_timestamp(id: &str) -> i64 { +pub fn get_timestamp(id: &str) -> Result { let n: Option = BASE36.decode_var_len(&id[0..8]); - match n { - None => -1, - Some(n) => n as i64 + TIME_2000, + if let Some(n) = n { + Ok(n as i64 + TIME_2000) + } else { + Err(InvalidIdErr { id: id.to_string() }) } } -cfg_if! { - if #[cfg(feature = "napi")] { - use chrono::{DateTime, Utc}; +/// The generated ID results in the form of `[8 chars timestamp] + [cuid2]`. +/// The minimum and maximum lengths are 16 and 24, respectively. +/// With the length of 16, namely 8 for cuid2, roughly 1427399 IDs are needed +/// in the same millisecond to reach 50% chance of collision. +/// +/// Ref: https://github.com/paralleldrive/cuid2#parameterized-length +#[crate::export] +pub fn gen_id() -> String { + create_id(&Utc::now().naive_utc()) +} - /// The generated ID results in the form of `[8 chars timestamp] + [cuid2]`. - /// The minimum and maximum lengths are 16 and 24, respectively. - /// With the length of 16, namely 8 for cuid2, roughly 1427399 IDs are needed - /// in the same millisecond to reach 50% chance of collision. - /// - /// Ref: https://github.com/paralleldrive/cuid2#parameterized-length - #[napi_derive::napi] - pub fn gen_id(date: Option>) -> String { - create_id(&date.unwrap_or_else(Utc::now).naive_utc()).unwrap() - } - } +/// Generate an ID using a specific datetime +#[crate::export] +pub fn gen_id_at(date: DateTime) -> String { + create_id(&date.naive_utc()) } #[cfg(test)] mod unit_test { - use crate::util::id; - use chrono::Utc; + use super::{gen_id, gen_id_at, get_timestamp}; + use chrono::{Duration, Utc}; use pretty_assertions::{assert_eq, assert_ne}; use std::thread; #[test] fn can_create_and_decode_id() { - let now = Utc::now().naive_utc(); - assert_eq!(id::create_id(&now), Err(id::ErrorUninitialized)); - id::init_id_generator(16, ""); - assert_eq!(id::create_id(&now).unwrap().len(), 16); - assert_ne!(id::create_id(&now).unwrap(), id::create_id(&now).unwrap()); - let id1 = thread::spawn(move || id::create_id(&now).unwrap()); - let id2 = thread::spawn(move || id::create_id(&now).unwrap()); + let now = Utc::now(); + assert_eq!(gen_id().len(), 16); + assert_ne!(gen_id_at(now), gen_id_at(now)); + assert_ne!(gen_id(), gen_id()); + + let id1 = thread::spawn(move || gen_id_at(now)); + let id2 = thread::spawn(move || gen_id_at(now)); assert_ne!(id1.join().unwrap(), id2.join().unwrap()); - let test_id = id::create_id(&now).unwrap(); - let timestamp = id::get_timestamp(&test_id); - assert_eq!(now.and_utc().timestamp_millis(), timestamp); + let test_id = gen_id_at(now); + let timestamp = get_timestamp(&test_id).unwrap(); + assert_eq!(now.timestamp_millis(), timestamp); + + let now_id = gen_id_at(now); + let old_id = gen_id_at(now - Duration::milliseconds(1)); + let future_id = gen_id_at(now + Duration::milliseconds(1)); + assert!(old_id < now_id); + assert!(now_id < future_id); } } diff --git a/packages/backend-rs/src/util/mod.rs b/packages/backend-rs/src/util/mod.rs index 1be5a7fd1f..13fb8ee64b 100644 --- a/packages/backend-rs/src/util/mod.rs +++ b/packages/backend-rs/src/util/mod.rs @@ -1,2 +1,5 @@ +pub use http_client::http_client; + +pub mod http_client; pub mod id; pub mod random; diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index f254aaea17..f8126fb6ab 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -9,7 +9,8 @@ import semver from "semver"; import Logger from "@/services/logger.js"; import type { Config } from "backend-rs"; -import { fetchMeta } from "backend-rs"; +import { initializeRustLogger } from "backend-rs"; +import { fetchMeta, removeOldAttestationChallenges } from "backend-rs"; import { config, envOption } from "@/config.js"; import { showMachineInfo } from "@/misc/show-machine-info.js"; import { db, initDb } from "@/db/postgre.js"; @@ -94,6 +95,7 @@ export async function masterMain() { await showMachineInfo(bootLogger); showNodejsVersion(); await connectDb(); + initializeRustLogger(); } catch (e) { bootLogger.error( `Fatal error occurred during initialization:\n${inspect(e)}`, @@ -103,30 +105,26 @@ export async function masterMain() { process.exit(1); } - bootLogger.succ("Firefish initialized"); + bootLogger.info("Firefish initialized"); if (!envOption.disableClustering) { await spawnWorkers(config.clusterLimits); } - bootLogger.succ( + bootLogger.info( `Now listening on port ${config.port} on ${config.url}`, null, true, ); - if ( - !envOption.noDaemons && - config.clusterLimits?.web && - config.clusterLimits?.web >= 1 - ) { + if (!envOption.noDaemons) { import("../daemons/server-stats.js").then((x) => x.default()); import("../daemons/queue-stats.js").then((x) => x.default()); - import("../daemons/janitor.js").then((x) => x.default()); + // Update meta cache every 5 minitues + setInterval(() => fetchMeta(false), 1000 * 60 * 5); + // Remove old attestation challenges + setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30); } - - // Update meta cache every 5 minitues - setInterval(() => fetchMeta(false), 1000 * 60 * 5); } function showEnvironment(): void { @@ -164,7 +162,7 @@ async function connectDb(): Promise { const v = await db .query("SHOW server_version") .then((x) => x[0].server_version); - dbLogger.succ(`Connected: v${v}`); + dbLogger.info(`Connected: v${v}`); } catch (e) { dbLogger.error("Failed to connect to the database", null, true); dbLogger.error(inspect(e)); @@ -200,7 +198,7 @@ async function spawnWorkers( `Starting ${clusterLimits.web} web workers and ${clusterLimits.queue} queue workers (total ${total})...`, ); await Promise.all(workers.map((mode) => spawnWorker(mode))); - bootLogger.succ("All workers started"); + bootLogger.info("All workers started"); } function spawnWorker(mode: "web" | "queue"): Promise { diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts index cae861230f..0acdcd97c6 100644 --- a/packages/backend/src/boot/worker.ts +++ b/packages/backend/src/boot/worker.ts @@ -1,17 +1,11 @@ import cluster from "node:cluster"; -import { config } from "@/config.js"; import { initDb } from "@/db/postgre.js"; -import { initIdGenerator } from "backend-rs"; import os from "node:os"; /** * Init worker process */ export async function workerMain() { - const length = Math.min(Math.max(config.cuid?.length ?? 16, 16), 24); - const fingerprint = config.cuid?.fingerprint ?? ""; - initIdGenerator(length, fingerprint); - await initDb(); if (!process.env.mode || process.env.mode === "web") { diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts deleted file mode 100644 index 2c52e2b009..0000000000 --- a/packages/backend/src/const.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { config } from "@/config.js"; -import { - DB_MAX_IMAGE_COMMENT_LENGTH, - DB_MAX_NOTE_TEXT_LENGTH, -} from "@/misc/hard-limits.js"; - -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/daemons/janitor.ts b/packages/backend/src/daemons/janitor.ts deleted file mode 100644 index 99b809d1c8..0000000000 --- a/packages/backend/src/daemons/janitor.ts +++ /dev/null @@ -1,20 +0,0 @@ -// TODO: 消したい - -const interval = 30 * 60 * 1000; -import { AttestationChallenges } from "@/models/index.js"; -import { LessThan } from "typeorm"; - -/** - * Clean up database occasionally - */ -export default function () { - async function tick() { - await AttestationChallenges.delete({ - createdAt: LessThan(new Date(Date.now() - 5 * 60 * 1000)), - }); - } - - tick(); - - setInterval(tick, interval); -} diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 1295136054..360ccfa38c 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -80,7 +80,7 @@ import { dbLogger } from "./logger.js"; const sqlLogger = dbLogger.createSubLogger("sql", "gray", false); -class MyCustomLogger implements Logger { +class DbLogger implements Logger { private highlight(sql: string) { return highlight.highlight(sql, { language: "sql", @@ -89,15 +89,16 @@ class MyCustomLogger implements Logger { } public logQuery(query: string, parameters?: any[]) { - sqlLogger.info(this.highlight(query).substring(0, 100)); + sqlLogger.trace(this.highlight(query).substring(0, 100)); } public logQueryError(error: string, query: string, parameters?: any[]) { - sqlLogger.error(this.highlight(query)); + sqlLogger.error(error); + sqlLogger.trace(this.highlight(query)); } public logQuerySlow(time: number, query: string, parameters?: any[]) { - sqlLogger.warn(this.highlight(query)); + sqlLogger.trace(this.highlight(query)); } public logSchemaBuild(message: string) { @@ -215,7 +216,7 @@ export const db = new DataSource({ } : false, logging: log, - logger: log ? new MyCustomLogger() : undefined, + logger: log ? new DbLogger() : undefined, maxQueryExecutionTime: 300, entities: entities, migrations: ["../../migration/*.js"], diff --git a/packages/backend/src/migration/1714099399879-alter-aka-type.ts b/packages/backend/src/migration/1714099399879-alter-aka-type.ts new file mode 100644 index 0000000000..ecb031e32d --- /dev/null +++ b/packages/backend/src/migration/1714099399879-alter-aka-type.ts @@ -0,0 +1,36 @@ +import type { MigrationInterface, QueryRunner } from "typeorm"; + +export class AlterAkaType1714099399879 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld"`, + ); + await queryRunner.query( + `ALTER TABLE "user" ADD COLUMN "alsoKnownAs" character varying(512)[]`, + ); + await queryRunner.query( + `UPDATE "user" SET "alsoKnownAs" = string_to_array("alsoKnownAsOld", ',')::character varying[]`, + ); + await queryRunner.query( + `UPDATE "user" SET "alsoKnownAs" = NULL WHERE "alsoKnownAs" = '{}'`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`, + ); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user" RENAME COLUMN "alsoKnownAs" TO "alsoKnownAsOld"`, + ); + await queryRunner.query(`ALTER TABLE "user" ADD COLUMN "alsoKnownAs" text`); + await queryRunner.query( + `UPDATE "user" SET "alsoKnownAs" = array_to_string("alsoKnownAsOld", ',')`, + ); + await queryRunner.query( + `COMMENT ON COLUMN "user"."alsoKnownAs" IS 'URIs the user is known as too'`, + ); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "alsoKnownAsOld"`); + } +} diff --git a/packages/backend/src/misc/download-text-file.ts b/packages/backend/src/misc/download-text-file.ts index 9d3821b20f..a1e615263f 100644 --- a/packages/backend/src/misc/download-text-file.ts +++ b/packages/backend/src/misc/download-text-file.ts @@ -1,5 +1,4 @@ -import * as fs from "node:fs"; -import * as util from "node:util"; +import * as fs from "node:fs/promises"; import Logger from "@/services/logger.js"; import { createTemp } from "./create-temp.js"; import { downloadUrl } from "./download-url.js"; @@ -16,7 +15,7 @@ export async function downloadTextFile(url: string): Promise { // write content at URL to temp file await downloadUrl(url, path); - const text = await util.promisify(fs.readFile)(path, "utf8"); + const text = await fs.readFile(path, "utf-8"); return text; } finally { diff --git a/packages/backend/src/misc/download-url.ts b/packages/backend/src/misc/download-url.ts index f516265106..0a47395947 100644 --- a/packages/backend/src/misc/download-url.ts +++ b/packages/backend/src/misc/download-url.ts @@ -1,6 +1,5 @@ import * as fs from "node:fs"; -import * as stream from "node:stream"; -import * as util from "node:util"; +import * as stream from "node:stream/promises"; import got, * as Got from "got"; import { config } from "@/config.js"; import { getAgentByHostname, StatusError } from "./fetch.js"; @@ -10,16 +9,14 @@ import IPCIDR from "ip-cidr"; import PrivateIp from "private-ip"; import { isValidUrl } from "./is-valid-url.js"; -const pipeline = util.promisify(stream.pipeline); - export async function downloadUrl(url: string, path: string): Promise { if (!isValidUrl(url)) { throw new StatusError("Invalid URL", 400); } - const logger = new Logger("download"); + const downloadLogger = new Logger("download"); - logger.info(`Downloading ${chalk.cyan(url)} ...`); + downloadLogger.debug(`Downloading ${chalk.cyan(url)} ...`); const timeout = 30 * 1000; const operationTimeout = 60 * 1000; @@ -48,7 +45,7 @@ export async function downloadUrl(url: string, path: string): Promise { }) .on("redirect", (res: Got.Response, opts: Got.NormalizedOptions) => { if (!isValidUrl(opts.url)) { - logger.warn(`Invalid URL: ${opts.url}`); + downloadLogger.warn(`Invalid URL: ${opts.url}`); req.destroy(); } }) @@ -60,7 +57,7 @@ export async function downloadUrl(url: string, path: string): Promise { res.ip ) { if (isPrivateIp(res.ip)) { - logger.warn(`Blocked address: ${res.ip}`); + downloadLogger.warn(`Blocked address: ${res.ip}`); req.destroy(); } } @@ -69,14 +66,16 @@ export async function downloadUrl(url: string, path: string): Promise { if (contentLength != null) { const size = Number(contentLength); if (size > maxSize) { - logger.warn(`maxSize exceeded (${size} > ${maxSize}) on response`); + downloadLogger.warn( + `maxSize exceeded (${size} > ${maxSize}) on response`, + ); req.destroy(); } } }) .on("downloadProgress", (progress: Got.Progress) => { if (progress.transferred > maxSize) { - logger.warn( + downloadLogger.warn( `maxSize exceeded (${progress.transferred} > ${maxSize}) on downloadProgress`, ); req.destroy(); @@ -84,7 +83,7 @@ export async function downloadUrl(url: string, path: string): Promise { }); try { - await pipeline(req, fs.createWriteStream(path)); + await stream.pipeline(req, fs.createWriteStream(path)); } catch (e) { if (e instanceof Got.HTTPError) { throw new StatusError( @@ -97,7 +96,7 @@ export async function downloadUrl(url: string, path: string): Promise { } } - logger.succ(`Download finished: ${chalk.cyan(url)}`); + downloadLogger.debug(`Download finished: ${chalk.cyan(url)}`); } export function isPrivateIp(ip: string): boolean { diff --git a/packages/backend/src/misc/emoji-meta.ts b/packages/backend/src/misc/emoji-meta.ts deleted file mode 100644 index 539a034817..0000000000 --- a/packages/backend/src/misc/emoji-meta.ts +++ /dev/null @@ -1,59 +0,0 @@ -import probeImageSize from "probe-image-size"; -import { Mutex } from "redis-semaphore"; - -import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; -import Logger from "@/services/logger.js"; -import { Cache } from "./cache.js"; -import { redisClient } from "@/db/redis.js"; -import { inspect } from "node:util"; - -export type Size = { - width: number; - height: number; -}; - -const cache = new Cache("emojiMeta", 60 * 10); // once every 10 minutes for the same url -const logger = new Logger("emoji"); - -export async function getEmojiSize(url: string): Promise { - let attempted = true; - - const lock = new Mutex(redisClient, "getEmojiSize"); - await lock.acquire(); - - try { - attempted = (await cache.get(url)) === true; - if (!attempted) { - await cache.set(url, true); - } - } finally { - await lock.release(); - } - - if (attempted) { - logger.warn(`Attempt limit exceeded: ${url}`); - throw new Error("Too many attempts"); - } - - try { - logger.debug(`Retrieving emoji size from ${url}`); - const { width, height, mime } = await probeImageSize(url, { - timeout: 5000, - }); - if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) { - throw new Error("Unsupported image type"); - } - return { width, height }; - } catch (e) { - throw new Error(`Unable to retrieve metadata:\n${inspect(e)}`); - } -} - -export function getNormalSize( - { width, height }: Size, - orientation?: number, -): Size { - return (orientation || 0) >= 5 - ? { width: height, height: width } - : { width, height }; -} diff --git a/packages/backend/src/misc/get-file-info.ts b/packages/backend/src/misc/get-file-info.ts index dadbda9e9e..532bbeec56 100644 --- a/packages/backend/src/misc/get-file-info.ts +++ b/packages/backend/src/misc/get-file-info.ts @@ -1,7 +1,7 @@ -import * as fs from "node:fs"; +import * as fs from "node:fs/promises"; +import { createReadStream } from "node:fs"; import * as crypto from "node:crypto"; -import * as stream from "node:stream"; -import * as util from "node:util"; +import * as stream from "node:stream/promises"; import { fileTypeFromFile } from "file-type"; import probeImageSize from "probe-image-size"; import isSvg from "is-svg"; @@ -9,8 +9,6 @@ import sharp from "sharp"; import { encode } from "blurhash"; import { inspect } from "node:util"; -const pipeline = util.promisify(stream.pipeline); - export type FileInfo = { size: number; md5: string; @@ -163,7 +161,7 @@ export async function checkSvg(path: string) { try { const size = await getFileSize(path); if (size > 1 * 1024 * 1024) return false; - return isSvg(fs.readFileSync(path)); + return isSvg(await fs.readFile(path, "utf-8")); } catch { return false; } @@ -173,8 +171,7 @@ export async function checkSvg(path: string) { * Get file size */ export async function getFileSize(path: string): Promise { - const getStat = util.promisify(fs.stat); - return (await getStat(path)).size; + return (await fs.stat(path)).size; } /** @@ -182,7 +179,7 @@ export async function getFileSize(path: string): Promise { */ async function calcHash(path: string): Promise { const hash = crypto.createHash("md5").setEncoding("hex"); - await pipeline(fs.createReadStream(path), hash); + await stream.pipeline(createReadStream(path), hash); return hash.read(); } @@ -196,7 +193,7 @@ async function detectImageSize(path: string): Promise<{ hUnits: string; orientation?: number; }> { - const readable = fs.createReadStream(path); + const readable = createReadStream(path); const imageSize = await probeImageSize(readable); readable.destroy(); return imageSize; @@ -214,7 +211,7 @@ function getBlurhash(path: string): Promise { .toBuffer((err, buffer, { width, height }) => { if (err) return reject(err); - let hash; + let hash: string; try { hash = encode(new Uint8ClampedArray(buffer), width, height, 7, 7); diff --git a/packages/backend/src/misc/hard-limits.ts b/packages/backend/src/misc/hard-limits.ts deleted file mode 100644 index 5ce3e0ac9a..0000000000 --- a/packages/backend/src/misc/hard-limits.ts +++ /dev/null @@ -1,18 +0,0 @@ -// 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; diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts index a8ba62ec20..83fdae442e 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 "@/const.js"; +import { FILE_TYPE_BROWSERSAFE } from "backend-rs"; const dictionary = { "safe-file": FILE_TYPE_BROWSERSAFE, diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts deleted file mode 100644 index fe83a56a55..0000000000 --- a/packages/backend/src/misc/is-quote.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Note } from "@/models/entities/note.js"; - -export default function (note: Note): boolean { - return ( - note.renoteId != null && - (note.text != null || - note.hasPoll || - (note.fileIds != null && note.fileIds.length > 0)) - ); -} diff --git a/packages/backend/src/misc/skipped-instances.ts b/packages/backend/src/misc/skipped-instances.ts index 0c87a524f6..1d2ed1702e 100644 --- a/packages/backend/src/misc/skipped-instances.ts +++ b/packages/backend/src/misc/skipped-instances.ts @@ -2,7 +2,7 @@ import { Brackets } from "typeorm"; import { isBlockedServer } from "backend-rs"; import { Instances } from "@/models/index.js"; import type { Instance } from "@/models/entities/instance.js"; -import { DAY } from "@/const.js"; +import { DAY } from "backend-rs"; // Threshold from last contact after which an instance will be considered // "dead" and should no longer get activities delivered to it. diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 81f564115f..c81d5d7622 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -13,7 +13,6 @@ import { id } from "../id.js"; import { Note } from "./note.js"; import { User } from "./user.js"; import { DriveFolder } from "./drive-folder.js"; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; import { NoteFile } from "./note-file.js"; export type DriveFileUsageHint = "userAvatar" | "userBanner" | null; @@ -73,7 +72,7 @@ export class DriveFile { @Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2 @Column("varchar", { - length: DB_MAX_IMAGE_COMMENT_LENGTH, + length: 8192, nullable: true, comment: "The comment of the DriveFile.", }) diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 152acfedb7..69a2b4dc27 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -88,7 +88,9 @@ export class User { }) public movedToUri: string | null; - @Column("simple-array", { + @Column("varchar", { + length: 512, + array: true, nullable: true, comment: "URIs the user is known as too", }) diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index c877048709..7fa26373b8 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -1,4 +1,4 @@ -import { In } from "typeorm"; +import { In, IsNull, Not } from "typeorm"; import * as mfm from "mfm-js"; import { Note } from "@/models/entities/note.js"; import type { User } from "@/models/entities/user.js"; @@ -10,6 +10,7 @@ import { Followings, Polls, Channels, + Notes, } from "../index.js"; import type { Packed } from "@/misc/schema.js"; import { countReactions, decodeReaction, nyaify } from "backend-rs"; @@ -101,7 +102,7 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } else { // 指定されているかどうか - return note.visibleUserIds.some((id: any) => meId === id); + return note.visibleUserIds.some((id) => meId === id); } } @@ -211,8 +212,25 @@ export const NoteRepository = db.getRepository(Note).extend({ localOnly: note.localOnly || undefined, visibleUserIds: note.visibility === "specified" ? note.visibleUserIds : undefined, + // FIXME: Deleting a post does not decrease these two numbers, causing the number to be wrong renoteCount: note.renoteCount, repliesCount: note.repliesCount, + // TODO: add it to database and use note.quoteCount + quoteCount: Notes.count({ + where: { + renoteId: note.id, + text: Not(IsNull()), + }, + }), + myRenoteCount: me + ? Notes.count({ + where: { + renoteId: note.id, + text: IsNull(), + userId: me.id, + }, + }) + : undefined, reactions: countReactions(note.reactions), reactionEmojis: reactionEmoji, emojis: noteEmoji, diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 040106b410..a5a8771645 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 "@/const.js"; +import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from "backend-rs"; 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/models/schema/note.ts b/packages/backend/src/models/schema/note.ts index fff872b69f..6064919960 100644 --- a/packages/backend/src/models/schema/note.ts +++ b/packages/backend/src/models/schema/note.ts @@ -208,5 +208,15 @@ export const packedNoteSchema = { optional: true, nullable: true, }, + myRenoteCount: { + type: "number", + optional: true, + nullable: false, + }, + quoteCount: { + type: "number", + optional: false, + nullable: false, + }, }, } as const; diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 6272a5e668..e2fb0febe6 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -70,10 +70,10 @@ deliverQueue ), ) .on("failed", (job, err) => - deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`), + deliverLogger.info(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`), ) .on("error", (job: any, err: Error) => - deliverLogger.error(`error ${err}`, { job, e: renderError(err) }), + deliverLogger.warn(`error ${err}`, { job, e: renderError(err) }), ) .on("stalled", (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`), @@ -564,12 +564,12 @@ export default function () { export function destroy() { deliverQueue.once("cleaned", (jobs, status) => { - deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); + deliverLogger.info(`Cleaned ${jobs.length} ${status} jobs`); }); deliverQueue.clean(0, "delayed"); inboxQueue.once("cleaned", (jobs, status) => { - inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`); + inboxLogger.info(`Cleaned ${jobs.length} ${status} jobs`); }); inboxQueue.clean(0, "delayed"); } diff --git a/packages/backend/src/queue/processors/db/delete-account.ts b/packages/backend/src/queue/processors/db/delete-account.ts index b43cdd137c..6e638821c1 100644 --- a/packages/backend/src/queue/processors/db/delete-account.ts +++ b/packages/backend/src/queue/processors/db/delete-account.ts @@ -13,7 +13,7 @@ const logger = queueLogger.createSubLogger("delete-account"); export async function deleteAccount( job: Bull.Job, ): Promise { - logger.info(`Deleting account of ${job.data.user.id} ...`); + logger.info(`Deleting account ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); if (!user) return; @@ -43,7 +43,7 @@ export async function deleteAccount( await Notes.delete(notes.map((note) => note.id)); } - logger.succ("All of notes deleted"); + logger.info(`All posts of user ${job.data.user.id} were deleted`); } { @@ -73,7 +73,7 @@ export async function deleteAccount( } } - logger.succ("All of files deleted"); + logger.info(`All files of user ${job.data.user.id} were deleted`); } { diff --git a/packages/backend/src/queue/processors/db/delete-drive-files.ts b/packages/backend/src/queue/processors/db/delete-drive-files.ts index 28e4771329..c89b11ef03 100644 --- a/packages/backend/src/queue/processors/db/delete-drive-files.ts +++ b/packages/backend/src/queue/processors/db/delete-drive-files.ts @@ -54,8 +54,6 @@ export async function deleteDriveFiles( job.progress(deletedCount / total); } - logger.succ( - `All drive files (${deletedCount}) of ${user.id} has been deleted.`, - ); + logger.info(`${deletedCount} drive files of user ${user.id} were deleted.`); done(); } diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 4fd222b3ef..d4597ec2a2 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -9,6 +9,7 @@ import { createTemp } from "@/misc/create-temp.js"; import { Users, Blockings } from "@/models/index.js"; import { MoreThan } from "typeorm"; import type { DbUserJobData } from "@/queue/types.js"; +import { inspect } from "node:util"; const logger = queueLogger.createSubLogger("export-blocking"); @@ -27,7 +28,7 @@ export async function exportBlocking( // Create temp file const [path, cleanup] = await createTemp(); - logger.info(`Temp file is ${path}`); + logger.info(`temp file created: ${path}`); try { const stream = fs.createWriteStream(path, { flags: "a" }); @@ -63,9 +64,10 @@ export async function exportBlocking( const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + "\n", (err) => { + stream.write(`${content}\n`, (err) => { if (err) { - logger.error(err); + logger.warn("failed"); + logger.info(inspect(err)); rej(err); } else { res(); @@ -83,7 +85,7 @@ export async function exportBlocking( } stream.end(); - logger.succ(`Exported to: ${path}`); + logger.info(`Exported to: ${path}`); const fileName = `blocking-${dateFormat( new Date(), @@ -96,7 +98,7 @@ export async function exportBlocking( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index 157751c1aa..9a947aeb6a 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -29,7 +29,7 @@ export async function exportCustomEmojis( const [path, cleanup] = await createTempDir(); - logger.info(`Temp dir is ${path}`); + logger.info(`temp dir created: ${path}`); const metaPath = `${path}/meta.json`; @@ -41,7 +41,8 @@ export async function exportCustomEmojis( return new Promise((res, rej) => { metaStream.write(text, (err) => { if (err) { - logger.error(err); + logger.warn("Failed to export custom emojis"); + logger.info(inspect(err)); rej(err); } else { res(); @@ -105,7 +106,7 @@ export async function exportCustomEmojis( zlib: { level: 0 }, }); archiveStream.on("close", async () => { - logger.succ(`Exported to: ${archivePath}`); + logger.info(`Exported to: ${archivePath}`); const fileName = `custom-emojis-${dateFormat( new Date(), @@ -118,7 +119,7 @@ export async function exportCustomEmojis( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); cleanup(); archiveCleanup(); done(); diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 65d3673e70..3209edb4f6 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -10,6 +10,7 @@ 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-following"); @@ -28,7 +29,7 @@ export async function exportFollowing( // Create temp file const [path, cleanup] = await createTemp(); - logger.info(`Temp file is ${path}`); + logger.info(`temp file created: ${path}`); try { const stream = fs.createWriteStream(path, { flags: "a" }); @@ -78,9 +79,12 @@ export async function exportFollowing( const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + "\n", (err) => { + stream.write(`${content}\n`, (err) => { if (err) { - logger.error(err); + logger.warn( + `failed to export following users of ${job.data.user.id}`, + ); + logger.info(inspect(err)); rej(err); } else { res(); @@ -91,7 +95,7 @@ export async function exportFollowing( } stream.end(); - logger.succ(`Exported to: ${path}`); + logger.info(`Exported to: ${path}`); const fileName = `following-${dateFormat( new Date(), @@ -104,7 +108,7 @@ export async function exportFollowing( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index f7906ac9f0..0f097d1bc5 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -9,6 +9,7 @@ import { createTemp } from "@/misc/create-temp.js"; import { Users, Mutings } from "@/models/index.js"; import { IsNull, MoreThan } from "typeorm"; import type { DbUserJobData } from "@/queue/types.js"; +import { inspect } from "node:util"; const logger = queueLogger.createSubLogger("export-mute"); @@ -16,7 +17,7 @@ export async function exportMute( job: Bull.Job, done: any, ): Promise { - logger.info(`Exporting mute of ${job.data.user.id} ...`); + logger.info(`Exporting mutes of ${job.data.user.id} ...`); const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { @@ -27,7 +28,7 @@ export async function exportMute( // Create temp file const [path, cleanup] = await createTemp(); - logger.info(`Temp file is ${path}`); + logger.info(`temp file created: ${path}`); try { const stream = fs.createWriteStream(path, { flags: "a" }); @@ -64,9 +65,10 @@ export async function exportMute( const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { - stream.write(content + "\n", (err) => { + stream.write(`${content}\n`, (err) => { if (err) { - logger.error(err); + logger.warn("failed"); + logger.info(inspect(err)); rej(err); } else { res(); @@ -84,7 +86,7 @@ export async function exportMute( } stream.end(); - logger.succ(`Exported to: ${path}`); + logger.info(`Exported to: ${path}`); const fileName = `mute-${dateFormat( new Date(), @@ -97,7 +99,7 @@ export async function exportMute( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index bf53f83603..5635e9ab24 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -10,6 +10,7 @@ import type { Note } from "@/models/entities/note.js"; import type { Poll } from "@/models/entities/poll.js"; import type { DbUserJobData } from "@/queue/types.js"; import { createTemp } from "@/misc/create-temp.js"; +import { inspect } from "node:util"; const logger = queueLogger.createSubLogger("export-notes"); @@ -28,7 +29,7 @@ export async function exportNotes( // Create temp file const [path, cleanup] = await createTemp(); - logger.info(`Temp file is ${path}`); + logger.info(`temp file created: ${path}`); try { const stream = fs.createWriteStream(path, { flags: "a" }); @@ -37,7 +38,8 @@ export async function exportNotes( return new Promise((res, rej) => { stream.write(text, (err) => { if (err) { - logger.error(err); + logger.warn(`failed to export posts of ${job.data.user.id}`); + logger.info(inspect(err)); rej(err); } else { res(); @@ -91,7 +93,7 @@ export async function exportNotes( await write("]"); stream.end(); - logger.succ(`Exported to: ${path}`); + logger.info(`Exported to: ${path}`); const fileName = `notes-${dateFormat( new Date(), @@ -104,7 +106,7 @@ export async function exportNotes( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index e6877f31fc..8e8f665dfe 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -9,6 +9,7 @@ import { createTemp } from "@/misc/create-temp.js"; import { Users, UserLists, UserListJoinings } from "@/models/index.js"; import { In } from "typeorm"; import type { DbUserJobData } from "@/queue/types.js"; +import { inspect } from "node:util"; const logger = queueLogger.createSubLogger("export-user-lists"); @@ -31,7 +32,7 @@ export async function exportUserLists( // Create temp file const [path, cleanup] = await createTemp(); - logger.info(`Temp file is ${path}`); + logger.info(`temp file created: ${path}`); try { const stream = fs.createWriteStream(path, { flags: "a" }); @@ -46,9 +47,10 @@ export async function exportUserLists( const acct = getFullApAccount(u.username, u.host); const content = `${list.name},${acct}`; await new Promise((res, rej) => { - stream.write(content + "\n", (err) => { + stream.write(`${content}\n`, (err) => { if (err) { - logger.error(err); + logger.warn(`failed to export ${list.id}`); + logger.info(inspect(err)); rej(err); } else { res(); @@ -59,7 +61,7 @@ export async function exportUserLists( } stream.end(); - logger.succ(`Exported to: ${path}`); + logger.info(`Exported to: ${path}`); const fileName = `user-lists-${dateFormat( new Date(), @@ -72,7 +74,7 @@ export async function exportUserLists( force: true, }); - logger.succ(`Exported to: ${driveFile.id}`); + logger.info(`Exported to: ${driveFile.id}`); } finally { cleanup(); } diff --git a/packages/backend/src/queue/processors/db/import-blocking.ts b/packages/backend/src/queue/processors/db/import-blocking.ts index e933b60783..ed5e045af0 100644 --- a/packages/backend/src/queue/processors/db/import-blocking.ts +++ b/packages/backend/src/queue/processors/db/import-blocking.ts @@ -66,14 +66,15 @@ export async function importBlocking( // skip myself if (target.id === job.data.user.id) continue; - logger.info(`Block[${linenum}] ${target.id} ...`); + logger.debug(`Block[${linenum}] ${target.id} ...`); await block(user, target); } catch (e) { - logger.warn(`Error in line ${linenum}:\n${inspect(e)}`); + logger.warn(`failed: error in line ${linenum}`); + logger.info(inspect(e)); } } - logger.succ("Imported"); + logger.info("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index f2371429f2..2d332575eb 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -11,14 +11,28 @@ import { addFile } from "@/services/drive/add-file.js"; import { genId } from "backend-rs"; import { db } from "@/db/postgre.js"; import probeImageSize from "probe-image-size"; -import * as path from "path"; +import * as path from "node:path"; const logger = queueLogger.createSubLogger("import-custom-emojis"); +// probeImageSize acceptable extensions +// JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD. +const acceptableExtensions = [ + ".jpeg", + ".jpg", + ".gif", + ".png", + ".webp", + ".bmp", + // ".tiff", // Cannot be used as emoji + // ".svg", // Disable for secure issues + // ".psd", // Cannot be used as emoji +]; + // TODO: 名前衝突時の動作を選べるようにする export async function importCustomEmojis( job: Bull.Job, - done: any, + done: () => void, ): Promise { logger.info("Importing custom emojis ..."); @@ -32,7 +46,7 @@ export async function importCustomEmojis( const [tempPath, cleanup] = await createTempDir(); - logger.info(`Temp dir is ${tempPath}`); + logger.debug(`temp dir created: ${tempPath}`); const destPath = `${tempPath}/emojis.zip`; @@ -62,6 +76,14 @@ export async function importCustomEmojis( if (!record.downloaded) continue; const emojiInfo = record.emoji; const emojiPath = `${outputPath}/${record.fileName}`; + + const extname = path.extname(record.fileName); + + // Skip non-support files + if (!acceptableExtensions.includes(extname.toLowerCase())) { + continue; + } + await Emojis.delete({ name: emojiInfo.name, }); @@ -92,7 +114,7 @@ export async function importCustomEmojis( } else { logger.info("starting emoji import without metadata"); // Since we lack metadata, we import into a randomized category name instead - let categoryName = genId(); + const categoryName = genId(); let containedEmojis = fs.readdirSync(outputPath); @@ -103,7 +125,14 @@ export async function importCustomEmojis( for (const emojiFilename of containedEmojis) { // strip extension and get filename to use as name - const name = path.basename(emojiFilename, path.extname(emojiFilename)); + const extname = path.extname(emojiFilename); + + // Skip non-emoji files, such as LICENSE + if (!acceptableExtensions.includes(extname.toLowerCase())) { + continue; + } + + const name = path.basename(emojiFilename, extname); const emojiPath = `${outputPath}/${emojiFilename}`; logger.info(`importing ${name}`); @@ -143,8 +172,8 @@ export async function importCustomEmojis( cleanup(); - logger.succ("Imported"); + logger.info("Imported"); done(); }); - logger.succ(`Unzipping to ${outputPath}`); + logger.info(`Unzipping to ${outputPath}`); } diff --git a/packages/backend/src/queue/processors/db/import-firefish-post.ts b/packages/backend/src/queue/processors/db/import-firefish-post.ts index be412fc490..88356c2458 100644 --- a/packages/backend/src/queue/processors/db/import-firefish-post.ts +++ b/packages/backend/src/queue/processors/db/import-firefish-post.ts @@ -1,6 +1,6 @@ import * as Post from "@/misc/post.js"; import create from "@/services/note/create.js"; -import { Users } from "@/models/index.js"; +import { NoteFiles, Users } from "@/models/index.js"; import type { DbUserImportMastoPostJobData } from "@/queue/types.js"; import { queueLogger } from "../../logger.js"; import { uploadFromUrl } from "@/services/drive/upload-from-url.js"; @@ -49,7 +49,7 @@ export async function importCkPost( }); files.push(file); } catch (e) { - logger.error(`Skipped adding file to drive: ${url}`); + logger.info(`Skipped adding file to drive: ${url}`); } } const { text, cw, localOnly, createdAt, visibility } = Post.parse(post); @@ -59,9 +59,18 @@ export async function importCkPost( userId: user.id, }); - if (note && (note?.fileIds?.length || 0) < files.length) { + // FIXME: What is this condition? + if (note != null && (note.fileIds?.length || 0) < files.length) { const update: Partial = {}; update.fileIds = files.map((x) => x.id); + + if (update.fileIds != null) { + await NoteFiles.delete({ noteId: note.id }); + await NoteFiles.insert( + update.fileIds.map((fileId) => ({ noteId: note?.id, fileId })), + ); + } + await Notes.update(note.id, update); await NoteEdits.insert({ id: genId(), @@ -71,12 +80,12 @@ export async function importCkPost( fileIds: note.fileIds, updatedAt: new Date(), }); - logger.info(`Note file updated`); + logger.info("Post updated"); } - if (!note) { + if (note == null) { note = await create(user, { createdAt: createdAt, - files: files.length == 0 ? undefined : files, + files: files.length === 0 ? undefined : files, poll: undefined, text: text || undefined, reply: post.replyId ? job.data.parent : null, @@ -90,11 +99,11 @@ export async function importCkPost( apHashtags: undefined, apEmojis: undefined, }); - logger.info(`Create new note`); + logger.debug("New post has been created"); } else { - logger.info(`Note exist`); + logger.info("This post already exists"); } - logger.succ("Imported"); + logger.info("Imported"); if (post.childNotes) { for (const child of post.childNotes) { createImportCkPostJob( diff --git a/packages/backend/src/queue/processors/db/import-following.ts b/packages/backend/src/queue/processors/db/import-following.ts index 77017fa9ff..92a2bac2c8 100644 --- a/packages/backend/src/queue/processors/db/import-following.ts +++ b/packages/backend/src/queue/processors/db/import-following.ts @@ -64,11 +64,12 @@ export async function importFollowing( // skip myself if (target.id === job.data.user.id) continue; - logger.info(`Follow[${linenum}] ${target.id} ...`); + logger.debug(`Follow[${linenum}] ${target.id} ...`); follow(user, target); } catch (e) { - logger.warn(`Error in line ${linenum}:\n${inspect(e)}`); + logger.warn(`Error in line ${linenum}`); + logger.info(inspect(e)); } } } else { @@ -102,15 +103,16 @@ export async function importFollowing( // skip myself if (target.id === job.data.user.id) continue; - logger.info(`Follow[${linenum}] ${target.id} ...`); + logger.debug(`Follow[${linenum}] ${target.id} ...`); follow(user, target); } catch (e) { - logger.warn(`Error in line ${linenum}:\n${inspect(e)}`); + logger.warn(`Error in line ${linenum}`); + logger.info(inspect(e)); } } } - logger.succ("Imported"); + logger.info("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-masto-post.ts b/packages/backend/src/queue/processors/db/import-masto-post.ts index d8f848d206..6a39290741 100644 --- a/packages/backend/src/queue/processors/db/import-masto-post.ts +++ b/packages/backend/src/queue/processors/db/import-masto-post.ts @@ -1,5 +1,5 @@ import create from "@/services/note/create.js"; -import { Users } from "@/models/index.js"; +import { NoteFiles, Users } from "@/models/index.js"; import type { DbUserImportMastoPostJobData } from "@/queue/types.js"; import { queueLogger } from "../../logger.js"; import type Bull from "bull"; @@ -73,7 +73,7 @@ export async function importMastoPost( }); files.push(file); } catch (e) { - logger.error(`Skipped adding file to drive: ${url}`); + logger.warn(`Skipped adding file to drive: ${url}`); } } } @@ -85,9 +85,18 @@ export async function importMastoPost( userId: user.id, }); - if (note && (note?.fileIds?.length || 0) < files.length) { + // FIXME: What is this condition? + if (note != null && (note.fileIds?.length || 0) < files.length) { const update: Partial = {}; update.fileIds = files.map((x) => x.id); + + if (update.fileIds != null) { + await NoteFiles.delete({ noteId: note.id }); + await NoteFiles.insert( + update.fileIds.map((fileId) => ({ noteId: note?.id, fileId })), + ); + } + await Notes.update(note.id, update); await NoteEdits.insert({ id: genId(), @@ -97,14 +106,14 @@ export async function importMastoPost( fileIds: note.fileIds, updatedAt: new Date(), }); - logger.info(`Note file updated`); + logger.info("Post updated"); } - if (!note) { + if (note == null) { note = await create(user, { createdAt: isRenote ? new Date(post.published) : new Date(post.object.published), - files: files.length == 0 ? undefined : files, + files: files.length === 0 ? undefined : files, poll: undefined, text: text || undefined, reply, @@ -118,12 +127,12 @@ export async function importMastoPost( apHashtags: undefined, apEmojis: undefined, }); - logger.info(`Create new note`); + logger.debug("New post has been created"); } else { - logger.info(`Note exist`); + logger.info("This post already exists"); } job.progress(100); done(); - logger.succ("Imported"); + logger.info("Imported"); } diff --git a/packages/backend/src/queue/processors/db/import-muting.ts b/packages/backend/src/queue/processors/db/import-muting.ts index b0d8f40956..af65837e29 100644 --- a/packages/backend/src/queue/processors/db/import-muting.ts +++ b/packages/backend/src/queue/processors/db/import-muting.ts @@ -66,15 +66,16 @@ export async function importMuting( // skip myself if (target.id === job.data.user.id) continue; - logger.info(`Mute[${linenum}] ${target.id} ...`); + logger.debug(`Mute[${linenum}] ${target.id} ...`); await mute(user, target); } catch (e) { - logger.warn(`Error in line ${linenum}: ${inspect(e)}`); + logger.warn(`Error in line ${linenum}`); + logger.info(inspect(e)); } } - logger.succ("Imported"); + logger.info("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-posts.ts b/packages/backend/src/queue/processors/db/import-posts.ts index eee74bddeb..ff9964dd6b 100644 --- a/packages/backend/src/queue/processors/db/import-posts.ts +++ b/packages/backend/src/queue/processors/db/import-posts.ts @@ -45,9 +45,10 @@ export async function importPosts( } } catch (e) { // handle error - logger.warn(`Failed to read Mastodon archive:\n${inspect(e)}`); + logger.warn("Failed to read Mastodon archive"); + logger.info(inspect(e)); } - logger.succ("Mastodon archive imported"); + logger.info("Mastodon archive imported"); done(); return; } @@ -56,24 +57,25 @@ export async function importPosts( try { const parsed = JSON.parse(json); - if (parsed instanceof Array) { - logger.info("Parsing key style posts"); + if (Array.isArray(parsed)) { + logger.info("Parsing *key posts"); const arr = recreateChain(parsed); for (const post of arr) { createImportCkPostJob(job.data.user, post, job.data.signatureCheck); } } else if (parsed instanceof Object) { - logger.info("Parsing animal style posts"); + logger.info("Parsing Mastodon posts"); for (const post of parsed.orderedItems) { createImportMastoPostJob(job.data.user, post, job.data.signatureCheck); } } } catch (e) { // handle error - logger.warn(`Error occured while reading:\n${inspect(e)}`); + logger.warn("an error occured while reading"); + logger.info(inspect(e)); } - logger.succ("Imported"); + logger.info("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index 2bdecfd389..f4e8523ba8 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -86,10 +86,11 @@ export async function importUserLists( pushUserToUserList(target, list!); } catch (e) { - logger.warn(`Error in line ${linenum}:\n${inspect(e)}`); + logger.warn(`Error in line ${linenum}`); + logger.info(inspect(e)); } } - logger.succ("Imported"); + logger.info("Imported"); done(); } diff --git a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts index 14ad1b1014..87170a8472 100644 --- a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts +++ b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts @@ -48,6 +48,6 @@ export default async function cleanRemoteFiles( job.progress(deletedCount / total); } - logger.succ("All cached remote files has been deleted."); + logger.info("All cached remote files are deleted."); done(); } diff --git a/packages/backend/src/queue/processors/system/check-expired-mutings.ts b/packages/backend/src/queue/processors/system/check-expired-mutings.ts index a482d0218a..05518ae8e1 100644 --- a/packages/backend/src/queue/processors/system/check-expired-mutings.ts +++ b/packages/backend/src/queue/processors/system/check-expired-mutings.ts @@ -28,6 +28,6 @@ export async function checkExpiredMutings( } } - logger.succ("All expired mutings checked."); + logger.info("All expired mutings checked."); done(); } diff --git a/packages/backend/src/queue/processors/system/clean-charts.ts b/packages/backend/src/queue/processors/system/clean-charts.ts index 663441e638..22845bb810 100644 --- a/packages/backend/src/queue/processors/system/clean-charts.ts +++ b/packages/backend/src/queue/processors/system/clean-charts.ts @@ -11,6 +11,6 @@ export async function cleanCharts( ): Promise { logger.info("Cleaning active users chart..."); await activeUsersChart.clean(); - logger.succ("Active users chart has been cleaned."); + logger.info("Active users chart has been cleaned."); done(); } diff --git a/packages/backend/src/queue/processors/system/clean.ts b/packages/backend/src/queue/processors/system/clean.ts index fbd45b0bb9..8d5575e887 100644 --- a/packages/backend/src/queue/processors/system/clean.ts +++ b/packages/backend/src/queue/processors/system/clean.ts @@ -4,7 +4,7 @@ import { UserIps } from "@/models/index.js"; import { queueLogger } from "../../logger.js"; -const logger = queueLogger.createSubLogger("clean"); +const logger = queueLogger.createSubLogger("clean-user-ip-log"); export async function clean( job: Bull.Job>, @@ -16,6 +16,6 @@ export async function clean( createdAt: LessThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }); - logger.succ("Cleaned."); + logger.info("Cleaned."); done(); } diff --git a/packages/backend/src/queue/processors/system/local-emoji-size.ts b/packages/backend/src/queue/processors/system/local-emoji-size.ts index 73595ed77b..fbd036ee57 100644 --- a/packages/backend/src/queue/processors/system/local-emoji-size.ts +++ b/packages/backend/src/queue/processors/system/local-emoji-size.ts @@ -3,7 +3,7 @@ import { IsNull } from "typeorm"; import { Emojis } from "@/models/index.js"; import { queueLogger } from "../../logger.js"; -import { getEmojiSize } from "@/misc/emoji-meta.js"; +import { getImageSizeFromUrl } from "backend-rs"; import { inspect } from "node:util"; const logger = queueLogger.createSubLogger("local-emoji-size"); @@ -21,23 +21,22 @@ export async function setLocalEmojiSizes( for (let i = 0; i < emojis.length; i++) { try { - const size = await getEmojiSize(emojis[i].publicUrl); + const size = await getImageSizeFromUrl(emojis[i].publicUrl); await Emojis.update(emojis[i].id, { width: size.width || null, height: size.height || null, }); } catch (e) { - logger.error( - `Unable to set emoji size (${i + 1}/${emojis.length}):\n${inspect(e)}`, - ); + logger.warn(`Unable to set emoji size (${i + 1}/${emojis.length})`); + logger.info(inspect(e)); /* skip if any error happens */ } finally { // wait for 1sec so that this would not overwhelm the object storage. await new Promise((resolve) => setTimeout(resolve, 1000)); - if (i % 10 === 9) logger.succ(`fetched ${i + 1}/${emojis.length} emojis`); + if (i % 10 === 9) logger.info(`fetched ${i + 1}/${emojis.length} emojis`); } } - logger.succ("Done."); + logger.info("Done."); done(); } diff --git a/packages/backend/src/queue/processors/system/verify-links.ts b/packages/backend/src/queue/processors/system/verify-links.ts index 230e55de52..46eabbd831 100644 --- a/packages/backend/src/queue/processors/system/verify-links.ts +++ b/packages/backend/src/queue/processors/system/verify-links.ts @@ -33,12 +33,13 @@ export async function verifyLinks( fields: user.fields, }); } catch (e) { - logger.error(`Failed to update user ${user.userId}:\n${inspect(e)}`); + logger.error(`Failed to update user ${user.userId}`); + logger.info(inspect(e)); done(e); } } } - logger.succ("All links successfully verified."); + logger.info("All links successfully verified."); done(); } diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts index 71e1c89232..47db9ef273 100644 --- a/packages/backend/src/remote/activitypub/deliver-manager.ts +++ b/packages/backend/src/remote/activitypub/deliver-manager.ts @@ -133,7 +133,8 @@ export default class DeliverManager { host: new URL(inbox).host, }); } catch (error) { - apLogger.error(`Invalid Inbox ${inbox}:\n${inspect(error)}`); + apLogger.info(`Invalid Inbox ${inbox}`); + apLogger.debug(inspect(error)); } } diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index bb03bf9a62..486bab635f 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -6,20 +6,19 @@ import { isFollow, getApType } from "../../type.js"; import { apLogger } from "../../logger.js"; import { inspect } from "node:util"; -const logger = apLogger; - export default async ( actor: CacheableRemoteUser, activity: IAccept, ): Promise => { const uri = activity.id || activity; - logger.info(`Accept: ${uri}`); + apLogger.info(`Accept: ${uri}`); const resolver = new Resolver(); const object = await resolver.resolve(activity.object).catch((e) => { - logger.error(`Resolution failed:\n${inspect(e)}`); + apLogger.info(`Failed to resolve AP object: ${e}`); + apLogger.debug(inspect(e)); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index 975e070f92..83dac26294 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -5,15 +5,13 @@ import type { IAnnounce } from "../../type.js"; import { getApId } from "../../type.js"; import { apLogger } from "../../logger.js"; -const logger = apLogger; - export default async ( actor: CacheableRemoteUser, activity: IAnnounce, ): Promise => { const uri = getApId(activity); - logger.info(`Announce: ${uri}`); + apLogger.info(`Announce: ${uri}`); const resolver = new Resolver(); diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 41d1319dbd..d40423ac51 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -13,8 +13,6 @@ import { Notes } from "@/models/index.js"; import { isBlockedServer } from "backend-rs"; import { inspect } from "node:util"; -const logger = apLogger; - /** * Handle announcement activities */ @@ -50,11 +48,14 @@ export default async function ( // Skip if target is 4xx if (e instanceof StatusError) { if (e.isClientError) { - logger.warn(`Ignored announce target ${targetUri} - ${e.statusCode}`); + apLogger.info( + `Ignored announce target ${targetUri} - ${e.statusCode}`, + ); return; } - logger.warn(`Error in announce target ${targetUri}:\n${inspect(e)}`); + apLogger.warn(`Error in announce target ${targetUri}`); + apLogger.debug(inspect(e)); } throw e; } @@ -63,7 +64,7 @@ export default async function ( console.log("skip: invalid actor for this activity"); return; } - logger.info(`Creating the (Re)Note: ${uri}`); + apLogger.info(`Creating (re)note: ${uri}`); const activityAudience = await parseAudience( actor, diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index d3bc1936d5..578479e5e7 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -7,15 +7,13 @@ import { apLogger } from "../../logger.js"; import { toArray, concat, unique } from "@/prelude/array.js"; import { inspect } from "node:util"; -const logger = apLogger; - export default async ( actor: CacheableRemoteUser, activity: ICreate, ): Promise => { const uri = getApId(activity); - logger.info(`Create: ${uri}`); + apLogger.info(`Create: ${uri}`); // copy audiences between activity <=> object. if (typeof activity.object === "object") { @@ -40,13 +38,14 @@ export default async ( const resolver = new Resolver(); const object = await resolver.resolve(activity.object).catch((e) => { - logger.error(`Resolution failed:\n${inspect(e)}`); + apLogger.info(`Failed to resolve AP object: ${e}`); + apLogger.debug(inspect(e)); throw e; }); if (isPost(object)) { createNote(resolver, actor, object, false, activity); } else { - logger.warn(`Unknown type: ${getApType(object)}`); + apLogger.info(`Unknown type: ${getApType(object)}`); } }; diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts index 83c6442dde..51b3777a86 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts @@ -3,13 +3,11 @@ import { createDeleteAccountJob } from "@/queue/index.js"; import type { CacheableRemoteUser } from "@/models/entities/user.js"; import { Users } from "@/models/index.js"; -const logger = apLogger; - export async function deleteActor( actor: CacheableRemoteUser, uri: string, ): Promise { - logger.info(`Deleting the Actor: ${uri}`); + apLogger.info(`Deleting Actor: ${uri}`); if (actor.uri !== uri) { return `skip: delete actor ${actor.uri} !== ${uri}`; diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts index ae3a593d05..f298ff903f 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts @@ -5,13 +5,11 @@ import DbResolver from "../../db-resolver.js"; import { getApLock } from "@/misc/app-lock.js"; import { deleteMessage } from "@/services/messages/delete.js"; -const logger = apLogger; - export default async function ( actor: CacheableRemoteUser, uri: string, ): Promise { - logger.info(`Deleting the Note: ${uri}`); + apLogger.info(`Deleting note: ${uri}`); const lock = await getApLock(uri); diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index b556550c5f..fbe8232bda 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -54,7 +54,8 @@ export async function performActivity( try { await performOneActivity(actor, act); } catch (err) { - apLogger.error(inspect(err)); + apLogger.info(`Failed to perform activity: ${err}`); + apLogger.debug(inspect(err)); } } } else { @@ -88,9 +89,15 @@ async function performOneActivity( } else if (isReject(activity)) { await reject(actor, activity); } else if (isAdd(activity)) { - await add(actor, activity).catch((err) => apLogger.error(inspect(err))); + await add(actor, activity).catch((err) => { + apLogger.warn(`Failed to perform 'add' activity: ${err}`); + apLogger.debug(inspect(err)); + }); } else if (isRemove(activity)) { - await remove(actor, activity).catch((err) => apLogger.error(inspect(err))); + await remove(actor, activity).catch((err) => { + apLogger.warn(`Failed to perform 'remove' activity: ${err}`); + apLogger.debug(inspect(err)); + }); } else if (isAnnounce(activity)) { await announce(actor, activity); } else if (isLike(activity)) { @@ -104,7 +111,7 @@ async function performOneActivity( } else if (isMove(activity)) { await move(actor, activity); } else { - apLogger.warn( + apLogger.info( `Unrecognized activity type: ${(activity as IActivity).type}`, ); } diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index 001231d030..782e0e2aaf 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -14,12 +14,13 @@ export default async ( ): Promise => { const uri = activity.id || activity; - logger.info(`Reject: ${uri}`); + apLogger.info(`Reject: ${uri}`); const resolver = new Resolver(); const object = await resolver.resolve(activity.object).catch((e) => { - logger.error(`Resolution failed:\n${inspect(e)}`); + apLogger.info(`Failed to resolve AP object: ${e}`); + apLogger.debug(inspect(e)); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index c1c18271f9..00647ec750 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -17,8 +17,6 @@ import Resolver from "../../resolver.js"; import { apLogger } from "../../logger.js"; import { inspect } from "node:util"; -const logger = apLogger; - export default async ( actor: CacheableRemoteUser, activity: IUndo, @@ -29,12 +27,13 @@ export default async ( const uri = activity.id || activity; - logger.info(`Undo: ${uri}`); + apLogger.info(`Undo: ${uri}`); const resolver = new Resolver(); const object = await resolver.resolve(activity.object).catch((e) => { - logger.error(`Resolution failed:\n${inspect(e)}`); + apLogger.info(`Failed to resolve AP object: ${e}`); + apLogger.debug(inspect(e)); throw e; }); diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 933f2c99e0..27969971f3 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -18,12 +18,13 @@ export default async ( return "skip: invalid actor"; } - apLogger.debug("Update"); + apLogger.info("Update"); const resolver = new Resolver(); const object = await resolver.resolve(activity.object).catch((e) => { - apLogger.error(`Resolution failed:\n${inspect(e)}`); + apLogger.info(`Failed to resolve AP object: ${e}`); + apLogger.debug(inspect(e)); throw e; }); diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index a6ac698feb..5fb261488f 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -9,9 +9,7 @@ import type { } from "@/models/entities/drive-file.js"; import { DriveFiles } from "@/models/index.js"; import { truncate } from "@/misc/truncate.js"; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; - -const logger = apLogger; +import { config } from "@/config.js"; /** * create an Image. @@ -36,7 +34,7 @@ export async function createImage( throw new Error(`Invalid image, unexpected schema: ${image.url}`); } - logger.info(`Creating the Image: ${image.url}`); + apLogger.info(`Creating an image: ${image.url}`); const instance = await fetchMeta(true); @@ -46,7 +44,7 @@ export async function createImage( uri: image.url, sensitive: image.sensitive, isLink: !instance.cacheRemoteFiles, - comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH), + comment: truncate(image.name, config.maxCaptionLength), usageHint: usage, }); diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 4b685747ba..2d847ef968 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -13,7 +13,13 @@ import { extractPollFromQuestion } from "./question.js"; import vote from "@/services/note/polls/vote.js"; import { apLogger } from "../logger.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; -import { extractHost, isSameOrigin, toPuny } from "backend-rs"; +import { + type ImageSize, + extractHost, + getImageSizeFromUrl, + isSameOrigin, + toPuny, +} from "backend-rs"; import { Emojis, Polls, @@ -44,14 +50,11 @@ import { publishNoteStream } from "@/services/stream.js"; import { extractHashtags } from "@/misc/extract-hashtags.js"; import { UserProfiles } from "@/models/index.js"; import { In } from "typeorm"; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; +import { config } from "@/config.js"; import { truncate } from "@/misc/truncate.js"; -import { type Size, getEmojiSize } from "@/misc/emoji-meta.js"; import { langmap } from "@/misc/langmap.js"; import { inspect } from "node:util"; -const logger = apLogger; - export function validateNote(object: any, uri: string) { const expectHost = extractHost(uri); @@ -112,13 +115,16 @@ export async function createNote( const entryUri = getApId(value); const err = validateNote(object, entryUri); if (err) { - logger.error(`${err.message}`, { - resolver: { - history: resolver.getHistory(), - }, - value: value, - object: object, - }); + apLogger.info(`${err.message}`); + apLogger.debug( + inspect({ + resolver: { + history: resolver.getHistory(), + }, + value: value, + object: object, + }), + ); throw new Error("invalid note"); } @@ -140,8 +146,8 @@ export async function createNote( throw new Error(`unexpected schema of note url: ${url}`); } - logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); - logger.info(`Creating the Note: ${note.id}`); + apLogger.trace(`Note fetched: ${JSON.stringify(note, null, 2)}`); + apLogger.info(`Creating the Note: ${note.id}`); // Skip if note is made before 2007 (1yr before Fedi was created) // OR skip if note is made 3 days in advance @@ -150,13 +156,13 @@ export async function createNote( const FutureCheck = new Date(); FutureCheck.setDate(FutureCheck.getDate() + 3); // Allow some wiggle room for misconfigured hosts if (DateChecker.getFullYear() < 2007) { - logger.warn( + apLogger.info( "Note somehow made before Activitypub was created; discarding", ); return null; } if (DateChecker > FutureCheck) { - logger.warn("Note somehow made after today; discarding"); + apLogger.info("Note somehow made after today; discarding"); return null; } } @@ -169,8 +175,8 @@ export async function createNote( // Skip if author is suspended. if (actor.isSuspended) { - logger.debug( - `User ${actor.usernameLower}@${actor.host} suspended; discarding.`, + apLogger.info( + `User ${actor.usernameLower}@${actor.host} is suspended; discarding.`, ); return null; } @@ -224,7 +230,7 @@ export async function createNote( ? await resolveNote(note.inReplyTo, resolver) .then((x) => { if (x == null) { - logger.warn("Specified inReplyTo, but nout found"); + apLogger.info(`Specified inReplyTo not found: ${note.inReplyTo}`); throw new Error("inReplyTo not found"); } else { return x; @@ -242,7 +248,8 @@ export async function createNote( } } - logger.warn(`Error in inReplyTo ${note.inReplyTo}:\n${inspect(e)}`); + apLogger.info(`Error in inReplyTo ${note.inReplyTo}`); + apLogger.debug(inspect(e)); throw e; }) : null; @@ -336,11 +343,11 @@ export async function createNote( index: number, ): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { - logger.warn( - `vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, + apLogger.info( + `discarding vote to expired poll: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, ); } else if (index >= 0) { - logger.info( + apLogger.info( `vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`, ); await vote(actor, reply, index); @@ -357,7 +364,8 @@ export async function createNote( } const emojis = await extractEmojis(note.tag || [], actor.host).catch((e) => { - logger.info(`extractEmojis:\n${inspect(e)}`); + apLogger.info("Failed to extract emojis"); + apLogger.debug(inspect(e)); return [] as Emoji[]; }); @@ -485,11 +493,16 @@ export async function extractEmojis( tag.icon!.url !== exists.originalUrl || !(exists.width && exists.height) ) { - let size: Size = { width: 0, height: 0 }; - try { - size = await getEmojiSize(tag.icon!.url); - } catch { - /* skip if any error happens */ + let size: ImageSize | null = null; + if (tag.icon?.url != null) { + try { + size = await getImageSizeFromUrl(tag.icon.url); + } catch (err) { + apLogger.info( + `Failed to determine the size of the image: ${tag.icon.url}`, + ); + apLogger.debug(inspect(err)); + } } await Emojis.update( { @@ -501,8 +514,8 @@ export async function extractEmojis( originalUrl: tag.icon!.url, publicUrl: tag.icon!.url, updatedAt: new Date(), - width: size.width || null, - height: size.height || null, + width: size?.width || null, + height: size?.height || null, }, ); @@ -515,11 +528,11 @@ export async function extractEmojis( return exists; } - logger.info(`register emoji host=${host}, name=${name}`); + apLogger.info(`register emoji host=${host}, name=${name}`); - let size: Size = { width: 0, height: 0 }; + let size: ImageSize = { width: 0, height: 0 }; try { - size = await getEmojiSize(tag.icon!.url); + size = await getImageSizeFromUrl(tag.icon!.url); } catch { /* skip if any error happens */ } @@ -619,7 +632,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) { const file = await resolveImage(actor, x, null); const update: Partial = {}; - const altText = truncate(x.name, DB_MAX_IMAGE_COMMENT_LENGTH); + const altText = truncate(x.name, config.maxCaptionLength); if (file.comment !== altText) { update.comment = altText; } diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 4baa2c021b..bb7bc7c033 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -48,8 +48,6 @@ import { resolveNote, extractEmojis } from "./note.js"; import { resolveImage } from "./image.js"; import { inspect } from "node:util"; -const logger = apLogger; - const nameLength = 128; const summaryLength = 2048; @@ -178,7 +176,7 @@ export async function createPerson( const person = validateActor(object, uri); - logger.info(`Creating the Person: ${person.id}`); + apLogger.info(`Creating Person: ${person.id}`); const host = toPuny(new URL(object.id).hostname); @@ -347,7 +345,8 @@ export async function createPerson( throw new Error("already registered"); } } else { - logger.error(inspect(e)); + apLogger.info(`Failed to create a Person actor: ${person.url}`); + apLogger.debug(inspect(e)); throw e; } } @@ -388,7 +387,8 @@ export async function createPerson( //#region Get custom emoji const emojis = await extractEmojis(person.tag || [], host).catch((e) => { - logger.info(`extractEmojis:\n${inspect(e)}`); + apLogger.info("Failed to extract emojis"); + apLogger.debug(inspect(e)); return [] as Emoji[]; }); @@ -399,9 +399,10 @@ export async function createPerson( }); //#endregion - await updateFeatured(user!.id, resolver).catch((err) => - logger.error(inspect(err)), - ); + await updateFeatured(user!.id, resolver).catch((err) => { + apLogger.info(`Failed to update featured collection of ${user.uri}`); + apLogger.debug(inspect(err)); + }); return user!; } @@ -439,7 +440,7 @@ export async function updatePerson( const person = validateActor(object, uri); - logger.info(`Updating the Person: ${person.id}`); + apLogger.info(`Updating the Person: ${person.id}`); // Fetch avatar and header image const [avatar, banner] = await Promise.all( @@ -456,7 +457,8 @@ export async function updatePerson( // Custom pictogram acquisition const emojis = await extractEmojis(person.tag || [], user.host).catch((e) => { - logger.info(`extractEmojis:\n${inspect(e)}`); + apLogger.info("Failed to extract emojis"); + apLogger.debug(inspect(e)); return [] as Emoji[]; }); @@ -626,9 +628,10 @@ export async function updatePerson( }, ); - await updateFeatured(user.id, resolver).catch((err) => - logger.error(inspect(err)), - ); + await updateFeatured(user.id, resolver).catch((err) => { + apLogger.info(`Failed to update featured collection of ${user.uri}`); + apLogger.debug(inspect(err)); + }); } /** @@ -681,7 +684,7 @@ export async function updateFeatured(userId: User["id"], resolver?: Resolver) { if (!Users.isRemoteUser(user)) return; if (!user.featured) return; - logger.info(`Updating the featured: ${user.uri}`); + apLogger.info(`Updating the featured collection: ${user.uri}`); if (resolver == null) resolver = new Resolver(); diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index 51419deecf..abb9c2b2a4 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -76,12 +76,12 @@ export default class Resolver { throw new Error("instance is blocked"); } } - apLogger.debug("Returning existing object:"); - apLogger.debug(JSON.stringify(value, null, 2)); + apLogger.debug("Returning the existing object"); + apLogger.trace(JSON.stringify(value, null, 2)); return value; } - apLogger.debug(`Resolving: ${value}`); + apLogger.info(`Resolving: ${value}`); if (value.includes("#")) { // URLs with fragment parts cannot be resolved correctly because @@ -115,8 +115,10 @@ export default class Resolver { this.user = await getInstanceActor(); } - apLogger.debug("Getting object from remote, authenticated as user:"); - apLogger.debug(JSON.stringify(this.user, null, 2)); + apLogger.info( + `Getting object from remote, authenticated as user ${this.user.id}`, + ); + apLogger.trace(JSON.stringify(this.user, null, 2)); const { finalUrl, content: object } = await apGet(value, this.user); diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index 69a99c767a..cb999cd1bb 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -56,7 +56,7 @@ export async function resolveUser( if (user == null) { const self = await resolveSelf(acctLower); - logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); + logger.info(`return new remote user: ${chalk.magenta(acctLower)}`); return await createPerson(self.href); } diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 1e070f91f9..2ada433eb5 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -43,8 +43,10 @@ const router = new Router(); //#region Routing function inbox(ctx: Router.RouterContext) { + const inboxLogger = serverLogger.createSubLogger("inbox"); + if (ctx.req.headers.host !== config.host) { - serverLogger.warn("inbox: Invalid Host"); + inboxLogger.warn(`regecting invalid host (${ctx.req.headers.host})`); ctx.status = 400; ctx.message = "Invalid Host"; return; @@ -57,7 +59,6 @@ function inbox(ctx: Router.RouterContext) { headers: ["(request-target)", "digest", "host", "date"], }); } catch (e) { - serverLogger.warn(`inbox: signature parse error:\n${inspect(e)}`); ctx.status = 401; if (e instanceof Error) { @@ -67,6 +68,9 @@ function inbox(ctx: Router.RouterContext) { ctx.message = "Missing Required Header"; } + inboxLogger.info(`signature parse error: ${ctx.message}`); + inboxLogger.debug(inspect(e)); + return; } @@ -76,8 +80,8 @@ function inbox(ctx: Router.RouterContext) { .toLowerCase() .match(/^((dsa|rsa|ecdsa)-(sha256|sha384|sha512)|ed25519-sha512|hs2019)$/) ) { - serverLogger.warn( - `inbox: invalid signature algorithm ${signature.algorithm}`, + inboxLogger.info( + `rejecting signature: unknown algorithm (${signature.algorithm})`, ); ctx.status = 401; ctx.message = "Invalid Signature Algorithm"; @@ -92,8 +96,8 @@ function inbox(ctx: Router.RouterContext) { const digest = ctx.req.headers.digest; if (typeof digest !== "string") { - serverLogger.warn( - "inbox: zero or more than one digest header(s) are present", + inboxLogger.info( + "rejecting invalid signature: zero or more than one digest header(s)", ); ctx.status = 401; ctx.message = "Invalid Digest Header"; @@ -103,7 +107,7 @@ function inbox(ctx: Router.RouterContext) { const match = digest.match(/^([0-9A-Za-z-]+)=(.+)$/); if (match == null) { - serverLogger.warn("inbox: unrecognized digest header"); + inboxLogger.info("rejecting signature: unrecognized digest header"); ctx.status = 401; ctx.message = "Invalid Digest Header"; return; @@ -113,7 +117,7 @@ function inbox(ctx: Router.RouterContext) { const expectedDigest = match[2]; if (digestAlgo.toUpperCase() !== "SHA-256") { - serverLogger.warn("inbox: unsupported digest algorithm"); + inboxLogger.info("rejecting signature: unsupported digest algorithm"); ctx.status = 401; ctx.message = "Unsupported Digest Algorithm"; return; @@ -125,7 +129,7 @@ function inbox(ctx: Router.RouterContext) { .digest("base64"); if (expectedDigest !== actualDigest) { - serverLogger.warn("inbox: Digest Mismatch"); + inboxLogger.info("rejecting invalid signature: Digest Mismatch"); ctx.status = 401; ctx.message = "Digest Missmatch"; return; @@ -215,7 +219,9 @@ router.get("/notes/:note", async (ctx, next) => { serverLogger.debug(JSON.stringify(remoteUser, null, 2)); if (remoteUser == null) { - serverLogger.debug("Rejecting: no user"); + serverLogger.info( + "rejecting fetch attempt of private post: no authentication", + ); ctx.status = 401; return; } @@ -225,14 +231,14 @@ router.get("/notes/:note", async (ctx, next) => { serverLogger.debug(JSON.stringify(relation, null, 2)); if (!relation.isFollowing || relation.isBlocked) { - serverLogger.debug( - "Rejecting: authenticated user is not following us or was blocked by us", + serverLogger.info( + "rejecting fetch attempt of private post: user is not a follower or is blocked", ); ctx.status = 403; return; } - serverLogger.debug("Accepting: access criteria met"); + serverLogger.debug("accepting fetch attempt of private post"); } ctx.body = renderActivity(await renderNote(note, false)); diff --git a/packages/backend/src/server/api/common/make-pagination-query.ts b/packages/backend/src/server/api/common/make-pagination-query.ts index a2c3275693..83827b3df1 100644 --- a/packages/backend/src/server/api/common/make-pagination-query.ts +++ b/packages/backend/src/server/api/common/make-pagination-query.ts @@ -1,6 +1,6 @@ -import type { SelectQueryBuilder } from "typeorm"; +import type { ObjectLiteral, SelectQueryBuilder } from "typeorm"; -export function makePaginationQuery( +export function makePaginationQuery( q: SelectQueryBuilder, sinceId?: string, untilId?: string, diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index fc22c843af..47b956e158 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -2,8 +2,12 @@ import { publishMainStream, publishGroupMessagingStream, } from "@/services/stream.js"; -import { publishMessagingStream } from "@/services/stream.js"; -import { publishMessagingIndexStream } from "@/services/stream.js"; +import { + publishToChatStream, + publishToChatIndexStream, + ChatEvent, + ChatIndexEvent, +} from "backend-rs"; import { pushNotification } from "@/services/push-notification.js"; import type { User, IRemoteUser } from "@/models/entities/user.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; @@ -54,8 +58,8 @@ export async function readUserMessagingMessage( ); // Publish event - publishMessagingStream(otherpartyId, userId, "read", messageIds); - publishMessagingIndexStream(userId, "read", messageIds); + publishToChatStream(otherpartyId, userId, ChatEvent.Read, messageIds); + publishToChatIndexStream(userId, ChatIndexEvent.Read, messageIds); if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 @@ -130,7 +134,7 @@ export async function readGroupMessagingMessage( ids: reads, userId: userId, }); - publishMessagingIndexStream(userId, "read", reads); + publishToChatIndexStream(userId, ChatIndexEvent.Read, reads); if (!(await Users.getHasUnreadMessagingMessage(userId))) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts index 8fa7579ceb..59139904bc 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts @@ -1,12 +1,17 @@ import define from "@/server/api/define.js"; import { Emojis, DriveFiles } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { + type ImageSize, + genId, + getImageSizeFromUrl, + publishToBroadcastStream, +} from "backend-rs"; import { insertModerationLog } from "@/services/insert-moderation-log.js"; import { ApiError } from "@/server/api/error.js"; import rndstr from "rndstr"; -import { publishBroadcastStream } from "@/services/stream.js"; import { db } from "@/db/postgre.js"; -import { getEmojiSize } from "@/misc/emoji-meta.js"; +import { apiLogger } from "@/server/api/logger.js"; +import { inspect } from "node:util"; export const meta = { tags: ["admin", "emoji"], @@ -49,7 +54,13 @@ export default define(meta, paramDef, async (ps, me) => { ? file.name.split(".")[0] : `_${rndstr("a-z0-9", 8)}_`; - const size = await getEmojiSize(file.url); + let size: ImageSize | null = null; + try { + size = await getImageSizeFromUrl(file.url); + } catch (err) { + apiLogger.info(`Failed to determine the image size: ${file.url}`); + apiLogger.debug(inspect(err)); + } const emoji = await Emojis.insert({ id: genId(), @@ -62,15 +73,13 @@ export default define(meta, paramDef, async (ps, me) => { publicUrl: file.webpublicUrl ?? file.url, type: file.webpublicType ?? file.type, license: null, - width: size.width || null, - height: size.height || null, + width: size?.width || null, + height: size?.height || null, }).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); await db.queryResultCache!.remove(["meta_emojis"]); - publishBroadcastStream("emojiAdded", { - emoji: await Emojis.pack(emoji.id), - }); + publishToBroadcastStream(await Emojis.pack(emoji)); insertModerationLog(me, "addEmoji", { emojiId: emoji.id, diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts index 424baf4409..9b08076b35 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts @@ -1,12 +1,17 @@ import define from "@/server/api/define.js"; import { Emojis } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { + type ImageSize, + genId, + getImageSizeFromUrl, + publishToBroadcastStream, +} from "backend-rs"; import { ApiError } from "@/server/api/error.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import { uploadFromUrl } from "@/services/drive/upload-from-url.js"; -import { publishBroadcastStream } from "@/services/stream.js"; import { db } from "@/db/postgre.js"; -import { getEmojiSize } from "@/misc/emoji-meta.js"; +import { apiLogger } from "@/server/api/logger.js"; +import { inspect } from "node:util"; export const meta = { tags: ["admin", "emoji"], @@ -76,7 +81,14 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(); } - const size = await getEmojiSize(driveFile.url); + let size: ImageSize | null = null; + + try { + size = await getImageSizeFromUrl(driveFile.url); + } catch (err) { + apiLogger.info(`Failed to determine the image size: ${driveFile.url}`); + apiLogger.debug(inspect(err)); + } const copied = await Emojis.insert({ id: genId(), @@ -88,15 +100,13 @@ export default define(meta, paramDef, async (ps, me) => { publicUrl: driveFile.webpublicUrl ?? driveFile.url, type: driveFile.webpublicType ?? driveFile.type, license: emoji.license, - width: size.width || null, - height: size.height || null, + width: size?.width ?? null, + height: size?.height ?? null, }).then((x) => Emojis.findOneByOrFail(x.identifiers[0])); await db.queryResultCache!.remove(["meta_emojis"]); - publishBroadcastStream("emojiAdded", { - emoji: await Emojis.pack(copied.id), - }); + publishToBroadcastStream(await Emojis.pack(copied)); return { id: copied.id, diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index ecfed950d3..9e41e58c0a 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -1,6 +1,5 @@ import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; -import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import define from "@/server/api/define.js"; export const meta = { @@ -506,8 +505,8 @@ export default define(meta, paramDef, async () => { iconUrl: instance.iconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, + maxNoteTextLength: config.maxNoteLength, // for backward compatibility + maxCaptionTextLength: config.maxCaptionLength, defaultLightTheme: instance.defaultLightTheme, defaultDarkTheme: instance.defaultDarkTheme, enableEmail: instance.enableEmail, diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 5159ce3b4a..72baadb676 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 56307311b2..a24654b2dd 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -4,13 +4,12 @@ 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 { extractHost, isBlockedServer } from "backend-rs"; +import { MINUTE, 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"; import { isActor, isPost, getApId } from "@/remote/activitypub/type.js"; import type { SchemaType } from "@/misc/schema.js"; -import { MINUTE } from "@/const.js"; import { updateQuestion } from "@/remote/activitypub/models/question.js"; import { populatePoll } from "@/models/repositories/note.js"; import { redisClient } from "@/db/redis.js"; diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 50154dd6cc..85ac239f35 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 addf4bbd19..ce1a717d13 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 f6c5573af7..2ab190ed0b 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 "@/const.js"; +import { FILE_TYPE_BROWSERSAFE } from "backend-rs"; 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 44e388a9bd..5b2a70bb94 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,9 +1,8 @@ import { addFile } from "@/services/drive/add-file.js"; import { DriveFiles } from "@/models/index.js"; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; +import { config } from "@/config.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; -import { fetchMeta } from "backend-rs"; -import { MINUTE } from "@/const.js"; +import { MINUTE, fetchMeta } from "backend-rs"; import define from "@/server/api/define.js"; import { apiLogger } from "@/server/api/logger.js"; import { ApiError } from "@/server/api/error.js"; @@ -68,7 +67,7 @@ export const paramDef = { comment: { type: "string", nullable: true, - maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, + maxLength: config.maxCaptionLength, default: null, }, isSensitive: { type: "boolean", default: false }, diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index bde81e007e..4d9567a838 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -1,6 +1,6 @@ import { publishDriveStream } from "@/services/stream.js"; -import { DriveFiles, DriveFolders, Users } from "@/models/index.js"; -import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js"; +import { DriveFiles, DriveFolders } from "@/models/index.js"; +import { config } from "@/config.js"; import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; @@ -57,7 +57,7 @@ export const paramDef = { comment: { type: "string", nullable: true, - maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, + maxLength: config.maxCaptionLength, }, }, required: ["fileId"], 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 cdfcb03089..fea6bf8a3a 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 ca013314e1..8ff6afa320 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 f6e341ad73..956dc14f6f 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 3ef031eb20..740ef5fd93 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 7559062c0f..682e7d963b 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 7cf1d9f7c0..01c772bc3c 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,9 +1,8 @@ import define from "@/server/api/define.js"; import { DriveFiles, GalleryPosts } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { HOUR, genId } from "backend-rs"; import { GalleryPost } from "@/models/entities/gallery-post.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; -import { HOUR } from "@/const.js"; export const meta = { tags: ["gallery"], 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 16c629706c..a8a3d46890 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 633e75335a..d6cfa14ece 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 "@/const.js"; +import { USER_ONLINE_THRESHOLD } from "backend-rs"; 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 30e74ab2f5..68c875aa00 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 "@/const.js"; +import { HOUR } from "backend-rs"; export const meta = { secure: true, 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 07d2997a18..5bce16f0ef 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 7d22a073e6..1b1fcf7241 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 f167bb83cb..9984c7656b 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 "@/const.js"; +import { DAY } from "backend-rs"; 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 b68d889dcd..e3b30d2d98 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 "@/const.js"; +import { MINUTE } from "backend-rs"; 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 58314aced3..b7cb6e6518 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 b7c475698c..8c20ff3591 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 494fb0d420..5ff4c94a29 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 225306ebc5..c81b443608 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,7 @@ 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 { DAY } from "@/const.js"; +import { DAY } from "backend-rs"; import { fetchMeta } from "backend-rs"; export const meta = { 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 ed82a96054..03aa14ac2b 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 9eaeedb39f..1411d9418a 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,7 @@ 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 { DAY } from "@/const.js"; +import { DAY } from "backend-rs"; 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 381bb1dea9..ef6d0c9b35 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -1,6 +1,6 @@ import type { User } from "@/models/entities/user.js"; import { resolveUser } from "@/remote/resolve-user.js"; -import { DAY } from "@/const.js"; +import { DAY } from "backend-rs"; 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 185d1c8dc9..393a358545 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -6,8 +6,7 @@ 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 } from "@/const.js"; -import { verifyPassword } from "backend-rs"; +import { HOUR, verifyPassword } from "backend-rs"; export const meta = { requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 4f65c59a9e..9a2b49cb3d 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -13,7 +13,7 @@ import { normalizeForSearch } from "@/misc/normalize-for-search.js"; import { verifyLink } from "@/services/fetch-rel-me.js"; import { ApiError } from "@/server/api/error.js"; import define from "@/server/api/define.js"; -import { DriveFile } from "@/models/entities/drive-file"; +import type { DriveFile } from "@/models/entities/drive-file"; export const meta = { tags: ["account"], 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 76691dd34d..1b9734c85c 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 "@/const.js"; +import { SECOND, HOUR } from "backend-rs"; export const meta = { tags: ["messaging"], diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index f35ae9cc6b..d4a4172426 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -3,7 +3,6 @@ import { IsNull, MoreThan } from "typeorm"; import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { Ads, Emojis, Users } from "@/models/index.js"; -import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import define from "@/server/api/define.js"; export const meta = { @@ -464,8 +463,8 @@ export default define(meta, paramDef, async (ps, me) => { iconUrl: instance.iconUrl, backgroundImageUrl: instance.backgroundImageUrl, logoImageUrl: instance.logoImageUrl, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため - maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, + maxNoteTextLength: config.maxNoteLength, // for backward compatibility + maxCaptionTextLength: config.maxCaptionLength, emojis: instance.privateMode && !me ? [] : await Emojis.packMany(emojis), // クライアントの手間を減らすためあらかじめJSONに変換しておく defaultLightTheme: instance.defaultLightTheme diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index c2302f4c8d..eb4d9ca5a2 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -11,11 +11,11 @@ import { import type { DriveFile } from "@/models/entities/drive-file.js"; import type { Note } from "@/models/entities/note.js"; import type { Channel } from "@/models/entities/channel.js"; -import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +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 "@/const.js"; +import { HOUR } from "backend-rs"; import { getNote } from "@/server/api/common/getters.js"; import { langmap } from "@/misc/langmap.js"; @@ -108,13 +108,13 @@ export const paramDef = { format: "misskey:id", }, }, - text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, + text: { type: "string", maxLength: config.maxNoteLength, nullable: true }, lang: { type: "string", enum: Object.keys(langmap), nullable: true, }, - cw: { type: "string", nullable: true, maxLength: MAX_NOTE_TEXT_LENGTH }, + cw: { type: "string", nullable: true, maxLength: config.maxNoteLength }, localOnly: { type: "boolean", default: false }, noExtractMentions: { type: "boolean", default: false }, noExtractHashtags: { type: "boolean", default: false }, @@ -164,7 +164,7 @@ export const paramDef = { text: { type: "string", minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, + maxLength: config.maxNoteLength, nullable: false, }, }, diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 54aad1ebad..b5a77af094 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 "@/const.js"; +import { SECOND, HOUR } from "backend-rs"; 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 60d5258ce9..238c01420d 100644 --- a/packages/backend/src/server/api/endpoints/notes/edit.ts +++ b/packages/backend/src/server/api/endpoints/notes/edit.ts @@ -14,11 +14,11 @@ import { import type { DriveFile } from "@/models/entities/drive-file.js"; import type { IMentionedRemoteUsers, Note } from "@/models/entities/note.js"; import type { Channel } from "@/models/entities/channel.js"; -import { MAX_NOTE_TEXT_LENGTH } from "@/const.js"; +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 "@/const.js"; +import { HOUR } from "backend-rs"; import { getNote } from "@/server/api/common/getters.js"; import { Poll } from "@/models/entities/poll.js"; import * as mfm from "mfm-js"; @@ -168,7 +168,7 @@ export const paramDef = { format: "misskey:id", }, }, - text: { type: "string", maxLength: MAX_NOTE_TEXT_LENGTH, nullable: true }, + text: { type: "string", maxLength: config.maxNoteLength, nullable: true }, lang: { type: "string", enum: Object.keys(langmap), @@ -224,7 +224,7 @@ export const paramDef = { text: { type: "string", minLength: 1, - maxLength: MAX_NOTE_TEXT_LENGTH, + maxLength: config.maxNoteLength, nullable: false, }, }, 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 5ddf1f3bf1..795863b003 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 "@/const.js"; +import { SECOND, HOUR } from "backend-rs"; import { publishNoteStream } from "@/services/stream.js"; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 386a3a08df..d6ff8888af 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -42,8 +42,6 @@ export const paramDef = { type: { type: "string", nullable: true }, limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, offset: { type: "integer", default: 0 }, - sinceId: { type: "string", format: "misskey:id" }, - untilId: { type: "string", format: "misskey:id" }, }, required: ["noteId"], } as const; 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 f135bd7ffa..b5f942f650 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 "@/const.js"; +import { SECOND, HOUR } from "backend-rs"; export const meta = { tags: ["reactions", "notes"], diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 683004ebe8..16304dd269 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -42,6 +42,12 @@ export const paramDef = { limit: { type: "integer", minimum: 1, maximum: 100, default: 10 }, sinceId: { type: "string", format: "misskey:id" }, untilId: { type: "string", format: "misskey:id" }, + filter: { + type: "string", + enum: ["all", "renote", "quote"], + nullable: true, + default: null, + }, }, required: ["noteId"], } as const; @@ -53,7 +59,7 @@ export default define(meta, paramDef, async (ps, user) => { throw err; }); - let query = makePaginationQuery( + const query = makePaginationQuery( Notes.createQueryBuilder("note"), ps.sinceId, ps.untilId, @@ -61,6 +67,16 @@ export default define(meta, paramDef, async (ps, user) => { .andWhere("note.renoteId = :renoteId", { renoteId: note.id }) .innerJoinAndSelect("note.user", "user"); + // "all" doesn't filter out anything, it's just there for + // those who prefer to set the parameter explicitly + + if (ps.filter === "renote") { + query.andWhere("note.text IS NULL"); + } + if (ps.filter === "quote") { + query.andWhere("note.text IS NOT NULL"); + } + if (ps.userId) { query.andWhere("user.id = :userId", { userId: ps.userId }); } diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 3f6f260eeb..1b8f544f7b 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 "@/const.js"; +import { SECOND, HOUR } from "backend-rs"; export const meta = { tags: ["notes"], diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index ec13f5aa2a..ae6d65f562 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -1,4 +1,4 @@ -import watch from "@/services/note/watch.js"; +import { watchNote } from "backend-rs"; import define from "@/server/api/define.js"; import { getNote } from "@/server/api/common/getters.js"; import { ApiError } from "@/server/api/error.js"; @@ -34,5 +34,5 @@ export default define(meta, paramDef, async (ps, user) => { throw err; }); - await watch(user.id, note); + await watchNote(user.id, note.userId, note.id); }); diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index 18994fa80c..746b293453 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -1,4 +1,4 @@ -import unwatch from "@/services/note/unwatch.js"; +import { unwatchNote } from "backend-rs"; import define from "@/server/api/define.js"; import { getNote } from "@/server/api/common/getters.js"; import { ApiError } from "@/server/api/error.js"; @@ -34,5 +34,5 @@ export default define(meta, paramDef, async (ps, user) => { throw err; }); - await unwatch(user.id, note); + await unwatchNote(user.id, note.id); }); diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index 35ab2d0450..3b56836d4e 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -3,7 +3,7 @@ import { genId } from "backend-rs"; import { Page } from "@/models/entities/page.js"; import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; -import { HOUR } from "@/const.js"; +import { HOUR } from "backend-rs"; export const meta = { tags: ["pages"], diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index 9509ebfb81..7a4c47a81d 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 "@/const.js"; +import { HOUR } from "backend-rs"; 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 00936c9d22..58beb94a88 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -3,9 +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 { genId } from "backend-rs"; +import { HOUR, genId } from "backend-rs"; import define from "@/server/api/define.js"; -import { HOUR } from "@/const.js"; export const meta = { tags: ["reset password"], diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index 4ea0d618ed..1ff242b097 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 "@/const.js"; +import { DAY } from "backend-rs"; export const meta = { tags: ["users"], diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index fda4aa0bb8..f43cd8ae0f 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,10 +1,8 @@ import * as mfm from "mfm-js"; import sanitizeHtml from "sanitize-html"; -import { publishAdminStream } from "@/services/stream.js"; import { AbuseUserReports, UserProfiles, Users } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { genId, publishToModerationStream } from "backend-rs"; import { sendEmail } from "@/services/send-email.js"; -import { fetchMeta } from "backend-rs"; import { getUser } from "@/server/api/common/getters.js"; import { ApiError } from "@/server/api/error.js"; import define from "@/server/api/define.js"; @@ -86,9 +84,8 @@ export default define(meta, paramDef, async (ps, me) => { ], }); - const meta = await fetchMeta(true); for (const moderator of moderators) { - publishAdminStream(moderator.id, "newAbuseUserReport", { + publishToModerationStream(moderator.id, { id: report.id, targetUserId: report.targetUserId, reporterId: report.reporterId, diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts index 862310fb3e..69a1822892 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts @@ -1,9 +1,8 @@ -import { Entity } from "megalodon"; +import type { Entity } from "megalodon"; import { config } from "@/config.js"; -import { fetchMeta } from "backend-rs"; +import { FILE_TYPE_BROWSERSAFE, fetchMeta } from "backend-rs"; import { Users, Notes } from "@/models/index.js"; import { IsNull } from "typeorm"; -import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js"; export async function getInstance( response: Entity.Instance, @@ -41,7 +40,7 @@ export async function getInstance( max_featured_tags: 20, }, statuses: { - max_characters: MAX_NOTE_TEXT_LENGTH, + max_characters: config.maxNoteLength, max_media_attachments: 16, characters_reserved_per_url: response.uri.length, }, diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index 313719e207..4079573ffb 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -4,7 +4,6 @@ import readNote from "@/services/note/read.js"; import type { User } from "@/models/entities/user.js"; import type { Channel as ChannelModel } from "@/models/entities/channel.js"; import { - Users, Followings, Mutings, RenoteMutings, @@ -15,11 +14,12 @@ import { } from "@/models/index.js"; import type { AccessToken } from "@/models/entities/access-token.js"; import type { UserProfile } from "@/models/entities/user-profile.js"; +import { publishGroupMessagingStream } from "@/services/stream.js"; import { - publishChannelStream, - publishGroupMessagingStream, - publishMessagingStream, -} from "@/services/stream.js"; + publishToChannelStream, + publishToChatStream, + ChatEvent, +} from "backend-rs"; import type { UserGroup } from "@/models/entities/user-group.js"; import type { Packed } from "@/misc/schema.js"; import { readNotification } from "@/server/api/common/read-notification.js"; @@ -513,9 +513,9 @@ export default class Connection { } } - private typingOnChannel(channel: ChannelModel["id"]) { + private typingOnChannel(channelId: ChannelModel["id"]) { if (this.user) { - publishChannelStream(channel, "typing", this.user.id); + publishToChannelStream(channelId, this.user.id); } } @@ -525,10 +525,10 @@ export default class Connection { }) { if (this.user) { if (param.partner) { - publishMessagingStream( + publishToChatStream( param.partner, this.user.id, - "typing", + ChatEvent.Typing, this.user.id, ); } else if (param.group) { diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 12f97d8018..ec7069ba2b 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -7,6 +7,7 @@ import { subscriber as redisClient } from "@/db/redis.js"; import { Users } from "@/models/index.js"; import MainStreamConnection from "./stream/index.js"; import authenticate from "./authenticate.js"; +import { apiLogger } from "@/server/api/logger.js"; export const initializeStreamingServer = (server: http.Server) => { // Init websocket server @@ -48,7 +49,7 @@ export const initializeStreamingServer = (server: http.Server) => { redisClient.on("message", onRedisMessage); const host = `https://${request.host}`; const prepareStream = q.stream?.toString(); - console.log("start", q); + apiLogger.trace("initialized streaming", q); const main = new MainStreamConnection( connection, diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 01ec93e448..4cfe6095cd 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 "@/const.js"; +import { FILE_TYPE_BROWSERSAFE } from "backend-rs"; import { inspect } from "node:util"; const _filename = fileURLToPath(import.meta.url); @@ -28,7 +28,7 @@ const MAX_BYTE_RANGES = 10; const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => { - serverLogger.error(e); + serverLogger.warn(e); ctx.status = 500; ctx.set("Cache-Control", "max-age=300"); }; @@ -109,7 +109,8 @@ export default async function (ctx: Koa.Context) { ); ctx.set("Cache-Control", "max-age=31536000, immutable"); } catch (e) { - serverLogger.error(`${inspect(e)}`); + serverLogger.warn(`failed to fetch/convert ${file.uri}`); + serverLogger.debug(inspect(e)); if (e instanceof StatusError && e.isClientError) { ctx.status = e.statusCode; diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 54d6e8bf5f..3b58c63118 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -52,7 +52,7 @@ if (!["production", "test"].includes(process.env.NODE_ENV || "")) { // Logger app.use( koaLogger((str) => { - serverLogger.info(str); + serverLogger.debug(str); }), ); @@ -177,7 +177,7 @@ mastoRouter.post("/oauth/token", async (ctx) => { console.log(body.code, token); token = body.code; } - if (client_id instanceof Array) { + if (Array.isArray(client_id)) { client_id = client_id.toString(); } else if (!client_id) { client_id = null; @@ -194,10 +194,10 @@ mastoRouter.post("/oauth/token", async (ctx) => { scope: body.scope || "read write follow push", created_at: Math.floor(Date.now() / 1000), }; - serverLogger.info("token-response", ret); + serverLogger.debug("Mastodon API token response", ret); ctx.body = ret; } catch (err: any) { - serverLogger.error(inspect(err)); + serverLogger.warn(err); ctx.status = 401; ctx.body = err.response.data; } @@ -246,7 +246,7 @@ export default () => ); break; default: - serverLogger.error(e); + serverLogger.error(inspect(e)); break; } diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 91e5fd8034..73189b73d9 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -3,7 +3,6 @@ import { config } from "@/config.js"; import { fetchMeta } from "backend-rs"; import { Users, Notes } from "@/models/index.js"; import { IsNull, MoreThan } from "typeorm"; -import { MAX_NOTE_TEXT_LENGTH, MAX_CAPTION_TEXT_LENGTH } from "@/const.js"; import { Cache } from "@/misc/cache.js"; const router = new Router(); @@ -86,8 +85,8 @@ const nodeinfo2 = async () => { postImports: meta.experimentalFeatures?.postImports || false, enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, - maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, - maxCaptionTextLength: MAX_CAPTION_TEXT_LENGTH, + maxNoteTextLength: config.maxNoteLength, + maxCaptionTextLength: config.maxCaptionLength, enableEmail: meta.enableEmail, enableServiceWorker: meta.enableServiceWorker, proxyAccountName: proxyAccount ? proxyAccount.username : null, @@ -113,6 +112,7 @@ router.get(nodeinfo2_0path, async (ctx) => { // @ts-ignore base.software.repository = undefined; + // @ts-ignore base.software.homepage = undefined; ctx.body = { version: "2.0", ...base }; diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index a505d74a0f..4ede3d4bdb 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 "@/const.js"; +import { FILE_TYPE_BROWSERSAFE } from "backend-rs"; import { serverLogger } from "../index.js"; import { isMimeImage } from "@/misc/is-mime-image.js"; import { inspect } from "node:util"; @@ -131,7 +131,8 @@ export async function proxyMedia(ctx: Koa.Context) { ctx.set("Cache-Control", "max-age=31536000, immutable"); ctx.body = image.data; } catch (e) { - serverLogger.error(`${inspect(e)}`); + serverLogger.warn(`failed to proxy ${url}`); + serverLogger.debug(inspect(e)); if (e instanceof StatusError && (e.statusCode === 302 || e.isClientError)) { ctx.status = e.statusCode; diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index b0687f084f..f79eba64e6 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -15,7 +15,14 @@ import { BullAdapter } from "@bull-board/api/bullAdapter.js"; import { KoaAdapter } from "@bull-board/koa"; import { In, IsNull } from "typeorm"; -import { fetchMeta, metaToPugArgs } from "backend-rs"; +import { + MINUTE, + DAY, + getNoteSummary, + stringToAcct, + fetchMeta, + metaToPugArgs, +} from "backend-rs"; import { config } from "@/config.js"; import { Users, @@ -27,13 +34,11 @@ import { Emojis, GalleryPosts, } from "@/models/index.js"; -import { getNoteSummary, stringToAcct } from "backend-rs"; import { queues } from "@/queue/queues.js"; import { genOpenapiSpec } from "../api/openapi/gen-spec.js"; import { urlPreviewHandler } from "./url-preview.js"; import { manifestHandler } from "./manifest.js"; import packFeed from "./feed.js"; -import { MINUTE, DAY } from "@/const.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index 8fd757ef14..f4ad758dc5 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -43,7 +43,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => { lang: lang ?? "en-US", }); - logger.succ(`Got preview of ${url}: ${summary.title}`); + logger.info(`Got preview of ${url}: ${summary.title}`); if ( summary.url && diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index d180bbabf3..9d28112c1d 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -6,7 +6,7 @@ import type S3 from "aws-sdk/clients/s3.js"; // TODO: migrate to SDK v3 import sharp from "sharp"; import { IsNull } from "typeorm"; import { publishMainStream, publishDriveStream } from "@/services/stream.js"; -import { fetchMeta } from "backend-rs"; +import { FILE_TYPE_BROWSERSAFE, fetchMeta, genId } from "backend-rs"; import { contentDisposition } from "@/misc/content-disposition.js"; import { getFileInfo } from "@/misc/get-file-info.js"; import { @@ -18,9 +18,7 @@ import { import { DriveFile } from "@/models/entities/drive-file.js"; import type { DriveFileUsageHint } from "@/models/entities/drive-file.js"; import type { IRemoteUser, User } from "@/models/entities/user.js"; -import { genId } from "backend-rs"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; -import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import { getS3 } from "./s3.js"; import { InternalStorage } from "./internal-storage.js"; @@ -653,7 +651,7 @@ export async function addFile({ ); } - logger.succ(`drive file has been created ${file.id}`); + logger.info(`drive file has been created ${file.id}`); if (user) { DriveFiles.pack(file, { self: true }).then((packedFile) => { diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index e7b084bda1..a3b6fc7e51 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -86,7 +86,7 @@ export async function uploadFromUrl({ requestHeaders, usageHint, }); - logger.succ(`Got: ${driveFile.id}`); + logger.info(`Got: ${driveFile.id}`); return driveFile; } catch (e) { logger.error(`Failed to create drive file:\n${inspect(e)}`); diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index 18e6411e88..bb9f9d0bd1 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -49,7 +49,7 @@ export async function fetchInstanceMetadata( getDescription(info, dom, manifest).catch(() => null), ]); - logger.succ(`Successfuly fetched metadata of ${instance.host}`); + logger.info(`Successfuly fetched metadata of ${instance.host}`); const updates = { infoUpdatedAt: new Date(), @@ -105,7 +105,7 @@ export async function fetchInstanceMetadata( await Instances.update(instance.id, updates); - logger.succ(`Successfuly updated metadata of ${instance.host}`); + logger.info(`Successfuly updated metadata of ${instance.host}`); } catch (e) { logger.error( `Failed to update metadata of ${instance.host}:\n${inspect(e)}`, @@ -172,7 +172,7 @@ async function fetchNodeinfo(instance: Instance): Promise { throw new Error(inspect(e)); }); - logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`); + logger.info(`Successfuly fetched nodeinfo of ${instance.host}`); return info as NodeInfo; } catch (e) { diff --git a/packages/backend/src/services/logger.ts b/packages/backend/src/services/logger.ts index aec4542b82..92fc7c6e79 100644 --- a/packages/backend/src/services/logger.ts +++ b/packages/backend/src/services/logger.ts @@ -11,7 +11,7 @@ type Domain = { color?: string; }; -type Level = "error" | "success" | "warning" | "debug" | "info"; +type Level = "error" | "warning" | "debug" | "info" | "trace"; export default class Logger { private domain: Domain; @@ -47,6 +47,23 @@ export default class Logger { return logger; } + private showThisLog(logLevel: Level, configLevel: string) { + switch (configLevel) { + case "error": + return ["error"].includes(logLevel); + case "warning": + return ["error", "warning"].includes(logLevel); + case "info": + return ["error", "warning", "info"].includes(logLevel); + case "debug": + return ["error", "warning", "info", "debug"].includes(logLevel); + case "trace": + return true; + default: + return ["error", "warning", "info"].includes(logLevel); + } + } + private log( level: Level, message: string, @@ -56,12 +73,13 @@ export default class Logger { store = true, ): void { if ( - !(typeof config.logLevel === "undefined") && - !config.logLevel.includes(level) + (config.maxLogLevel != null && + !this.showThisLog(level, config.maxLogLevel)) || + (config.logLevel != null && !config.logLevel.includes(level)) ) return; if (!this.store) store = false; - if (level === "debug") store = false; + if (level === "debug" || level === "trace") store = false; if (this.parentLogger) { this.parentLogger.log( @@ -84,14 +102,12 @@ export default class Logger { : chalk.red("ERR ") : level === "warning" ? chalk.yellow("WARN") - : level === "success" - ? important - ? chalk.bgGreen.white("DONE") - : chalk.green("DONE") + : level === "info" + ? chalk.cyan("INFO") : level === "debug" - ? chalk.gray("VERB") - : level === "info" - ? chalk.blue("INFO") + ? chalk.green("DEBUG") + : level === "trace" + ? chalk.gray("TRACE") : null; const domains = [this.domain] .concat(subDomains) @@ -105,11 +121,11 @@ export default class Logger { ? chalk.red(message) : level === "warning" ? chalk.yellow(message) - : level === "success" + : level === "info" ? chalk.green(message) : level === "debug" - ? chalk.gray(message) - : level === "info" + ? chalk.blue(message) + : level === "trace" ? message : null; @@ -129,11 +145,11 @@ export default class Logger { ? this.syslogClient.error : level === "warning" ? this.syslogClient.warning - : level === "success" + : level === "debug" ? this.syslogClient.info - : level === "debug" + : level === "info" ? this.syslogClient.info - : level === "info" + : level === "trace" ? this.syslogClient.info : (null as never); @@ -144,7 +160,7 @@ export default class Logger { } } - // Used when the process can't continue (fatal error) + // Only used when the process can't continue (fatal error) public error( x: string | Error, data?: Record | null, @@ -175,16 +191,16 @@ export default class Logger { this.log("warning", message, data, important); } - // Used when something is successful - public succ( + // Other generic logs + public info( message: string, data?: Record | null, important = false, ): void { - this.log("success", message, data, important); + this.log("info", message, data, important); } - // Used for debugging (information necessary for developers but unnecessary for users) + // Only used for debugging (information necessary for developers but unnecessary for users) public debug( message: string, data?: Record | null, @@ -200,12 +216,12 @@ export default class Logger { } } - // Other generic logs - public info( + // Extremely verbose logs for debugging (e.g., SQL Query) + public trace( message: string, data?: Record | null, important = false, ): void { - this.log("info", message, data, important); + this.log("trace", message, data, important); } } diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index 257a132e6b..269d1d0e3c 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -7,11 +7,16 @@ import { Mutings, Users, } from "@/models/index.js"; -import { genId, toPuny } from "backend-rs"; +import { + genId, + publishToChatStream, + publishToChatIndexStream, + toPuny, + ChatEvent, + ChatIndexEvent, +} from "backend-rs"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import { - publishMessagingStream, - publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream, } from "@/services/stream.js"; @@ -52,25 +57,33 @@ export async function createMessage( if (recipientUser) { if (Users.isLocalUser(user)) { // 自分のストリーム - publishMessagingStream( + publishToChatStream( message.userId, recipientUser.id, - "message", + ChatEvent.Message, + messageObj, + ); + publishToChatIndexStream( + message.userId, + ChatIndexEvent.Message, messageObj, ); - publishMessagingIndexStream(message.userId, "message", messageObj); publishMainStream(message.userId, "messagingMessage", messageObj); } if (Users.isLocalUser(recipientUser)) { // 相手のストリーム - publishMessagingStream( + publishToChatStream( recipientUser.id, message.userId, - "message", + ChatEvent.Message, + messageObj, + ); + publishToChatIndexStream( + recipientUser.id, + ChatIndexEvent.Message, messageObj, ); - publishMessagingIndexStream(recipientUser.id, "message", messageObj); publishMainStream(recipientUser.id, "messagingMessage", messageObj); } } else if (recipientGroup) { @@ -82,7 +95,11 @@ export async function createMessage( userGroupId: recipientGroup.id, }); for (const joining of joinings) { - publishMessagingIndexStream(joining.userId, "message", messageObj); + publishToChatIndexStream( + joining.userId, + ChatIndexEvent.Message, + messageObj, + ); publishMainStream(joining.userId, "messagingMessage", messageObj); } } diff --git a/packages/backend/src/services/messages/delete.ts b/packages/backend/src/services/messages/delete.ts index 2d8f6b9baf..745c89380d 100644 --- a/packages/backend/src/services/messages/delete.ts +++ b/packages/backend/src/services/messages/delete.ts @@ -1,10 +1,8 @@ import { config } from "@/config.js"; import { MessagingMessages, Users } from "@/models/index.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; -import { - publishGroupMessagingStream, - publishMessagingStream, -} from "@/services/stream.js"; +import { publishGroupMessagingStream } from "@/services/stream.js"; +import { publishToChatStream, ChatEvent } from "backend-rs"; import { renderActivity } from "@/remote/activitypub/renderer/index.js"; import renderDelete from "@/remote/activitypub/renderer/delete.js"; import renderTombstone from "@/remote/activitypub/renderer/tombstone.js"; @@ -21,17 +19,17 @@ async function postDeleteMessage(message: MessagingMessage) { const recipient = await Users.findOneByOrFail({ id: message.recipientId }); if (Users.isLocalUser(user)) - publishMessagingStream( + publishToChatStream( message.userId, message.recipientId, - "deleted", + ChatEvent.Deleted, message.id, ); if (Users.isLocalUser(recipient)) - publishMessagingStream( + publishToChatStream( message.recipientId, message.userId, - "deleted", + ChatEvent.Deleted, message.id, ); diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 2135847a17..679a2f886e 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -47,6 +47,7 @@ import { addNoteToAntenna, checkWordMute, genId, + genIdAt, isSilencedServer, } from "backend-rs"; import { countSameRenotes } from "@/misc/count-same-renotes.js"; @@ -711,7 +712,7 @@ async function insertNote( data.createdAt = new Date(); } const insert = new Note({ - id: genId(data.createdAt), + id: genIdAt(data.createdAt), createdAt: data.createdAt, fileIds: data.files ? data.files.map((file) => file.id) : [], replyId: data.reply ? data.reply.id : null, diff --git a/packages/backend/src/services/note/unwatch.ts b/packages/backend/src/services/note/unwatch.ts deleted file mode 100644 index b4da5e86da..0000000000 --- a/packages/backend/src/services/note/unwatch.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { User } from "@/models/entities/user.js"; -import { NoteWatchings } from "@/models/index.js"; -import type { Note } from "@/models/entities/note.js"; - -export default async (me: User["id"], note: Note) => { - await NoteWatchings.delete({ - noteId: note.id, - userId: me, - }); -}; diff --git a/packages/backend/src/services/note/watch.ts b/packages/backend/src/services/note/watch.ts deleted file mode 100644 index 682b0822cc..0000000000 --- a/packages/backend/src/services/note/watch.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { User } from "@/models/entities/user.js"; -import type { Note } from "@/models/entities/note.js"; -import { NoteWatchings } from "@/models/index.js"; -import { genId } from "backend-rs"; -import type { NoteWatching } from "@/models/entities/note-watching.js"; - -export default async (me: User["id"], note: Note) => { - // 自分の投稿はwatchできない - if (me === note.userId) { - return; - } - - await NoteWatchings.insert({ - id: genId(), - createdAt: new Date(), - noteId: note.id, - userId: me, - noteUserId: note.userId, - } as NoteWatching); -}; diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 7f1862b74d..1a821302ce 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -5,19 +5,19 @@ import type { UserList } from "@/models/entities/user-list.js"; import type { UserGroup } from "@/models/entities/user-group.js"; import { config } from "@/config.js"; // import type { Antenna } from "@/models/entities/antenna.js"; -import type { Channel } from "@/models/entities/channel.js"; +// import type { Channel } from "@/models/entities/channel.js"; import type { StreamChannels, - AdminStreamTypes, + // AdminStreamTypes, // AntennaStreamTypes, - BroadcastTypes, - ChannelStreamTypes, + // BroadcastTypes, + // ChannelStreamTypes, DriveStreamTypes, GroupMessagingStreamTypes, InternalStreamTypes, MainStreamTypes, - MessagingIndexStreamTypes, - MessagingStreamTypes, + // MessagingIndexStreamTypes, + // MessagingStreamTypes, NoteStreamTypes, UserListStreamTypes, UserStreamTypes, @@ -64,16 +64,17 @@ class Publisher { ); }; - public publishBroadcastStream = ( - type: K, - value?: BroadcastTypes[K], - ): void => { - this.publish( - "broadcast", - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishBroadcastStream = ( + // type: K, + // value?: BroadcastTypes[K], + // ): void => { + // this.publish( + // "broadcast", + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; public publishMainStream = ( userId: User["id"], @@ -110,17 +111,18 @@ class Publisher { }); }; - public publishChannelStream = ( - channelId: Channel["id"], - type: K, - value?: ChannelStreamTypes[K], - ): void => { - this.publish( - `channelStream:${channelId}`, - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishChannelStream = ( + // channelId: Channel["id"], + // type: K, + // value?: ChannelStreamTypes[K], + // ): void => { + // this.publish( + // `channelStream:${channelId}`, + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; public publishUserListStream = ( listId: UserList["id"], @@ -147,18 +149,19 @@ class Publisher { // ); // }; - public publishMessagingStream = ( - userId: User["id"], - otherpartyId: User["id"], - type: K, - value?: MessagingStreamTypes[K], - ): void => { - this.publish( - `messagingStream:${userId}-${otherpartyId}`, - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishMessagingStream = ( + // userId: User["id"], + // otherpartyId: User["id"], + // type: K, + // value?: MessagingStreamTypes[K], + // ): void => { + // this.publish( + // `messagingStream:${userId}-${otherpartyId}`, + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; public publishGroupMessagingStream = < K extends keyof GroupMessagingStreamTypes, @@ -174,35 +177,37 @@ class Publisher { ); }; - public publishMessagingIndexStream = < - K extends keyof MessagingIndexStreamTypes, - >( - userId: User["id"], - type: K, - value?: MessagingIndexStreamTypes[K], - ): void => { - this.publish( - `messagingIndexStream:${userId}`, - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishMessagingIndexStream = < + // K extends keyof MessagingIndexStreamTypes, + // >( + // userId: User["id"], + // type: K, + // value?: MessagingIndexStreamTypes[K], + // ): void => { + // this.publish( + // `messagingIndexStream:${userId}`, + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; public publishNotesStream = (note: Note): void => { this.publish("notesStream", null, note); }; - public publishAdminStream = ( - userId: User["id"], - type: K, - value?: AdminStreamTypes[K], - ): void => { - this.publish( - `adminStream:${userId}`, - type, - typeof value === "undefined" ? null : value, - ); - }; + /* ported to backend-rs */ + // public publishAdminStream = ( + // userId: User["id"], + // type: K, + // value?: AdminStreamTypes[K], + // ): void => { + // this.publish( + // `adminStream:${userId}`, + // type, + // typeof value === "undefined" ? null : value, + // ); + // }; } const publisher = new Publisher(); @@ -211,17 +216,16 @@ export default publisher; export const publishInternalEvent = publisher.publishInternalEvent; export const publishUserEvent = publisher.publishUserEvent; -export const publishBroadcastStream = publisher.publishBroadcastStream; +// export const publishBroadcastStream = publisher.publishBroadcastStream; export const publishMainStream = publisher.publishMainStream; export const publishDriveStream = publisher.publishDriveStream; export const publishNoteStream = publisher.publishNoteStream; export const publishNotesStream = publisher.publishNotesStream; -export const publishChannelStream = publisher.publishChannelStream; +// export const publishChannelStream = publisher.publishChannelStream; export const publishUserListStream = publisher.publishUserListStream; // export const publishAntennaStream = publisher.publishAntennaStream; -export const publishMessagingStream = publisher.publishMessagingStream; +// export const publishMessagingStream = publisher.publishMessagingStream; export const publishGroupMessagingStream = publisher.publishGroupMessagingStream; -export const publishMessagingIndexStream = - publisher.publishMessagingIndexStream; -export const publishAdminStream = publisher.publishAdminStream; +// export const publishMessagingIndexStream = publisher.publishMessagingIndexStream; +// export const publishAdminStream = publisher.publishAdminStream; diff --git a/packages/client/package.json b/packages/client/package.json index 97a5f83ef7..8b9184ad38 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -28,6 +28,7 @@ "@types/matter-js": "0.19.6", "@types/prismjs": "^1.26.3", "@types/punycode": "2.1.4", + "@types/qrcode": "1.5.1", "@types/seedrandom": "3.0.8", "@types/textarea-caret": "^3.0.3", "@types/throttle-debounce": "5.0.2", @@ -60,13 +61,17 @@ "insert-text-at-cursor": "0.3.0", "json5": "2.2.3", "katex": "0.16.10", + "long": "^5.2.3", "libopenmpt-wasm": "github:TheEssem/libopenmpt-packaging#build", "matter-js": "0.19.0", "mfm-js": "0.24.0", + "multer": "1.4.4-lts.1", "moment": "2.30.1", "photoswipe": "5.4.3", "prismjs": "1.29.0", "punycode": "2.3.1", + "qrcode": "1.5.3", + "qrcode-vue3": "^1.6.8", "rollup": "4.14.2", "s-age": "1.1.2", "sass": "1.75.0", diff --git a/packages/client/src/components/MkFollowButton.vue b/packages/client/src/components/MkFollowButton.vue index 0920001c7b..4e2d884749 100644 --- a/packages/client/src/components/MkFollowButton.vue +++ b/packages/client/src/components/MkFollowButton.vue @@ -8,7 +8,7 @@