Merge branch 'develop' into renovate/lock-file-maintenance
181
Cargo.lock
generated
|
@ -235,6 +235,7 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"web-push",
|
"web-push",
|
||||||
|
"zhconv",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -499,6 +500,16 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-oid"
|
name = "const-oid"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -680,6 +691,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "daachorse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63b7ef7a4be509357f4804d0a22e830daddb48f19fd604e4ad32ddce04a94c36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
|
@ -1187,6 +1204,12 @@ version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex-literal"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
|
@ -1517,6 +1540,8 @@ dependencies = [
|
||||||
"mime",
|
"mime",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"polling",
|
"polling",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"slab",
|
"slab",
|
||||||
"sluice",
|
"sluice",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -1525,6 +1550,15 @@ dependencies = [
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -2037,6 +2071,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.1"
|
version = "0.36.1"
|
||||||
|
@ -2529,7 +2572,7 @@ dependencies = [
|
||||||
"built",
|
"built",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"interpolate_name",
|
"interpolate_name",
|
||||||
"itertools",
|
"itertools 0.12.1",
|
||||||
"libc",
|
"libc",
|
||||||
"libfuzzer-sys",
|
"libfuzzer-sys",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2797,6 +2840,23 @@ dependencies = [
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruzstd"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3ffab8f9715a0d455df4bbb9d21e91135aab3cd3ca187af0cd0c3c3f868fdc"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"thiserror-core",
|
||||||
|
"twox-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -2859,7 +2919,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"strum",
|
"strum 0.25.0",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -3380,12 +3440,34 @@ dependencies = [
|
||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.4.1",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
@ -3491,6 +3573,26 @@ dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-core"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c001ee18b7e5e3f62cbf58c7fe220119e68d902bb7443179c0c8aef30090e999"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-core-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-core-impl"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4c60d69f36615a077cc7663b9cb8e42275722d23e58a7fa3d2c7f2915d09d04"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.71",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.63"
|
version = "1.0.63"
|
||||||
|
@ -3530,7 +3632,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
"num-conv",
|
"num-conv",
|
||||||
|
"num_threads",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
|
@ -3732,6 +3837,16 @@ dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twox-hash"
|
||||||
|
version = "1.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
@ -3850,6 +3965,18 @@ version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vergen"
|
||||||
|
version = "8.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cfg-if",
|
||||||
|
"rustversion",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -4286,6 +4413,56 @@ dependencies = [
|
||||||
"syn 2.0.71",
|
"syn 2.0.71",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zhconv"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a5764e8c3c48dce7dd281cdae65c785536d1da3078b484c2254e7bea7b42323"
|
||||||
|
dependencies = [
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"daachorse",
|
||||||
|
"hex-literal",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"lazy_static",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"ruzstd",
|
||||||
|
"sha2",
|
||||||
|
"strum 0.24.1",
|
||||||
|
"vergen",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"zstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "6.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.12+zstd.1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
|
@ -49,10 +49,11 @@ tracing-subscriber = { version = "0.3.18", default-features = false }
|
||||||
url = { version = "2.5.2", default-features = false }
|
url = { version = "2.5.2", default-features = false }
|
||||||
urlencoding = { version = "2.1.3", default-features = false }
|
urlencoding = { version = "2.1.3", default-features = false }
|
||||||
web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false }
|
web-push = { git = "https://github.com/pimeys/rust-web-push.git", rev = "40febe4085e3cef9cdfd539c315e3e945aba0656", default-features = false }
|
||||||
|
zhconv = "0.3.1"
|
||||||
|
|
||||||
# subdependencies
|
# subdependencies
|
||||||
## explicitly list OpenSSL to use the vendored version
|
## explicitly list OpenSSL to use the vendored version
|
||||||
openssl = "0.10.64"
|
openssl = "0.10.65"
|
||||||
|
|
||||||
## some subdependencies require higher Rust version than 1.74 (our MSRV)
|
## some subdependencies require higher Rust version than 1.74 (our MSRV)
|
||||||
## cargo update && cargo update ravif --precise 0.11.5 && cargo update bitstream-io --precise 2.3.0
|
## cargo update && cargo update ravif --precise 0.11.5 && cargo update bitstream-io --precise 2.3.0
|
||||||
|
|
|
@ -29,7 +29,7 @@ cuid2 = { workspace = true }
|
||||||
emojis = { workspace = true }
|
emojis = { workspace = true }
|
||||||
idna = { workspace = true, features = ["std", "compiled_data"] }
|
idna = { workspace = true, features = ["std", "compiled_data"] }
|
||||||
image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
|
image = { workspace = true, features = ["avif", "bmp", "gif", "ico", "jpeg", "png", "tiff", "webp"] }
|
||||||
isahc = { workspace = true, features = ["http2", "text-decoding"] }
|
isahc = { workspace = true, features = ["http2", "text-decoding", "json"] }
|
||||||
nom-exif = { workspace = true }
|
nom-exif = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
openssl = { workspace = true, features = ["vendored"] }
|
openssl = { workspace = true, features = ["vendored"] }
|
||||||
|
@ -49,6 +49,7 @@ tracing-subscriber = { workspace = true, features = ["ansi"] }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
web-push = { workspace = true, features = ["isahc-client"] }
|
web-push = { workspace = true, features = ["isahc-client"] }
|
||||||
|
zhconv = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true, features = ["std"] }
|
pretty_assertions = { workspace = true, features = ["std"] }
|
||||||
|
|
7
packages/backend-rs/index.d.ts
vendored
|
@ -1349,6 +1349,13 @@ export declare function toDbReaction(reaction?: string | undefined | null, host?
|
||||||
|
|
||||||
export declare function toPuny(host: string): string
|
export declare function toPuny(host: string): string
|
||||||
|
|
||||||
|
export declare function translate(text: string, sourceLang: string | undefined | null, targetLang: string): Promise<Translation>
|
||||||
|
|
||||||
|
export interface Translation {
|
||||||
|
sourceLang: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
export declare function unwatchNote(watcherId: string, noteId: string): Promise<void>
|
export declare function unwatchNote(watcherId: string, noteId: string): Promise<void>
|
||||||
|
|
||||||
export declare function updateAntennaCache(): Promise<void>
|
export declare function updateAntennaCache(): Promise<void>
|
||||||
|
|
|
@ -443,6 +443,7 @@ module.exports.storageUsage = nativeBinding.storageUsage
|
||||||
module.exports.stringToAcct = nativeBinding.stringToAcct
|
module.exports.stringToAcct = nativeBinding.stringToAcct
|
||||||
module.exports.toDbReaction = nativeBinding.toDbReaction
|
module.exports.toDbReaction = nativeBinding.toDbReaction
|
||||||
module.exports.toPuny = nativeBinding.toPuny
|
module.exports.toPuny = nativeBinding.toPuny
|
||||||
|
module.exports.translate = nativeBinding.translate
|
||||||
module.exports.unwatchNote = nativeBinding.unwatchNote
|
module.exports.unwatchNote = nativeBinding.unwatchNote
|
||||||
module.exports.updateAntennaCache = nativeBinding.updateAntennaCache
|
module.exports.updateAntennaCache = nativeBinding.updateAntennaCache
|
||||||
module.exports.updateAntennasOnNewNote = nativeBinding.updateAntennasOnNewNote
|
module.exports.updateAntennasOnNewNote = nativeBinding.updateAntennasOnNewNote
|
||||||
|
|
|
@ -17,4 +17,5 @@ pub mod reaction;
|
||||||
pub mod remove_old_attestation_challenges;
|
pub mod remove_old_attestation_challenges;
|
||||||
pub mod should_nyaify;
|
pub mod should_nyaify;
|
||||||
pub mod system_info;
|
pub mod system_info;
|
||||||
|
pub mod translate;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
243
packages/backend-rs/src/misc/translate.rs
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
use crate::{
|
||||||
|
config::{local_server_info, server, CONFIG},
|
||||||
|
util::http_client,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macros::errors]
|
||||||
|
pub enum Error {
|
||||||
|
#[doc = "database error"]
|
||||||
|
#[error(transparent)]
|
||||||
|
Db(#[from] sea_orm::DbErr),
|
||||||
|
#[error("failed to acquire an HTTP client")]
|
||||||
|
HttpClient(#[from] http_client::Error),
|
||||||
|
#[error("invalid http request body")]
|
||||||
|
InvalidRequestBody(#[from] isahc::http::Error),
|
||||||
|
#[error("http request failed")]
|
||||||
|
HttpRequest(#[from] isahc::Error),
|
||||||
|
#[error("failed to serialize the request body")]
|
||||||
|
Serialize(#[from] serde_json::Error),
|
||||||
|
#[error("Libretranslate API url is not set")]
|
||||||
|
MissingApiUrl,
|
||||||
|
#[error("DeepL API key is not set")]
|
||||||
|
MissingApiKey,
|
||||||
|
#[error("no response")]
|
||||||
|
NoResponse,
|
||||||
|
#[error("translator is not set")]
|
||||||
|
NoTranslator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macros::export(object)]
|
||||||
|
pub struct Translation {
|
||||||
|
pub source_lang: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macros::export]
|
||||||
|
pub async fn translate(
|
||||||
|
text: &str,
|
||||||
|
source_lang: Option<&str>,
|
||||||
|
target_lang: &str,
|
||||||
|
) -> Result<Translation, Error> {
|
||||||
|
let config = local_server_info().await?;
|
||||||
|
|
||||||
|
let mut translation = if let Some(api_key) = config.deepl_auth_key {
|
||||||
|
deepl_translate::translate(
|
||||||
|
text,
|
||||||
|
source_lang,
|
||||||
|
target_lang,
|
||||||
|
&api_key,
|
||||||
|
config.deepl_is_pro,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if let Some(api_url) = config.libre_translate_api_url {
|
||||||
|
libre_translate::translate(
|
||||||
|
text,
|
||||||
|
source_lang,
|
||||||
|
target_lang,
|
||||||
|
&api_url,
|
||||||
|
config.libre_translate_api_key.as_deref(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if let Some(server::DeepLConfig {
|
||||||
|
auth_key, is_pro, ..
|
||||||
|
}) = CONFIG.deepl.as_ref()
|
||||||
|
{
|
||||||
|
deepl_translate::translate(
|
||||||
|
text,
|
||||||
|
source_lang,
|
||||||
|
target_lang,
|
||||||
|
auth_key.as_ref().ok_or(Error::MissingApiKey)?,
|
||||||
|
is_pro.unwrap_or(false),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else if let Some(server::LibreTranslateConfig {
|
||||||
|
api_url, api_key, ..
|
||||||
|
}) = CONFIG.libre_translate.as_ref()
|
||||||
|
{
|
||||||
|
libre_translate::translate(
|
||||||
|
text,
|
||||||
|
source_lang,
|
||||||
|
target_lang,
|
||||||
|
api_url.as_ref().ok_or(Error::MissingApiUrl)?,
|
||||||
|
api_key.as_deref(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
return Err(Error::NoTranslator);
|
||||||
|
};
|
||||||
|
|
||||||
|
// DeepL translate and LibreTranslate don't provide zh-Hant-TW translations,
|
||||||
|
// so we convert zh-Hans-CN translations into zh-Hant-TW using zhconv.
|
||||||
|
if ["zh-tw", "zh-hant", "zh-hant-tw"].contains(&target_lang.to_ascii_lowercase().as_str()) {
|
||||||
|
translation.text = zhconv::zhconv(&translation.text, zhconv::Variant::ZhTW)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod deepl_translate {
|
||||||
|
use crate::util::http_client;
|
||||||
|
use isahc::{AsyncReadResponseExt, Request};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Response {
|
||||||
|
translations: Vec<Translation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
struct Translation {
|
||||||
|
detected_source_language: Option<String>,
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn translate(
|
||||||
|
text: &str,
|
||||||
|
source_lang: Option<&str>,
|
||||||
|
target_lang: &str,
|
||||||
|
api_key: &str,
|
||||||
|
is_pro: bool,
|
||||||
|
) -> Result<super::Translation, super::Error> {
|
||||||
|
let client = http_client::client()?;
|
||||||
|
|
||||||
|
let api_url = if is_pro {
|
||||||
|
"https://api.deepl.com/v2/translate"
|
||||||
|
} else {
|
||||||
|
"https://api-free.deepl.com/v2/translate"
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut target_lang = target_lang.split('-').collect::<Vec<&str>>()[0];
|
||||||
|
|
||||||
|
// DeepL API requires us to specify "en-US" or "en-GB" for English
|
||||||
|
// translations ("en" does not work), so we need to address it
|
||||||
|
if target_lang == "en" {
|
||||||
|
target_lang = "en-US";
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = if let Some(source_lang) = source_lang {
|
||||||
|
let source_lang = source_lang.split('-').collect::<Vec<&str>>()[0];
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"text": [text],
|
||||||
|
"source_lang": source_lang,
|
||||||
|
"target_lang": target_lang
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
json!({
|
||||||
|
"text": [text],
|
||||||
|
"target_lang": target_lang
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::post(api_url)
|
||||||
|
.header("Authorization", format!("DeepL-Auth-Key {}", api_key))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(serde_json::to_string(&body)?)?;
|
||||||
|
|
||||||
|
let response = client.send_async(request).await?.json::<Response>().await?;
|
||||||
|
|
||||||
|
let result = response
|
||||||
|
.translations
|
||||||
|
.first()
|
||||||
|
.ok_or(super::Error::NoResponse)?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
Ok(super::Translation {
|
||||||
|
source_lang: source_lang
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.or(result.detected_source_language)
|
||||||
|
.unwrap_or_else(|| "unknown".to_owned()),
|
||||||
|
text: result.text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod libre_translate {
|
||||||
|
use crate::util::http_client;
|
||||||
|
use isahc::{AsyncReadResponseExt, Request};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct Translation {
|
||||||
|
translated_text: String,
|
||||||
|
detected_language: DetectedLanguage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
struct DetectedLanguage {
|
||||||
|
language: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn translate(
|
||||||
|
text: &str,
|
||||||
|
source_lang: Option<&str>,
|
||||||
|
target_lang: &str,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: Option<&str>,
|
||||||
|
) -> Result<super::Translation, super::Error> {
|
||||||
|
let client = http_client::client()?;
|
||||||
|
let target_lang = target_lang.split('-').collect::<Vec<&str>>()[0];
|
||||||
|
|
||||||
|
let body = if let Some(source_lang) = source_lang {
|
||||||
|
let source_lang = source_lang.split('-').collect::<Vec<&str>>()[0];
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"q": [text],
|
||||||
|
"source": source_lang,
|
||||||
|
"target": target_lang,
|
||||||
|
"format": "text",
|
||||||
|
"alternatives": 1,
|
||||||
|
"api_key": api_key.unwrap_or_default()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
json!({
|
||||||
|
"q": [text],
|
||||||
|
"source": "auto",
|
||||||
|
"target": target_lang,
|
||||||
|
"format": "text",
|
||||||
|
"alternatives": 1,
|
||||||
|
"api_key": api_key.unwrap_or_default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let request = Request::post(api_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(serde_json::to_string(&body)?)?;
|
||||||
|
|
||||||
|
let result = client
|
||||||
|
.send_async(request)
|
||||||
|
.await?
|
||||||
|
.json::<Translation>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(super::Translation {
|
||||||
|
source_lang: source_lang
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.unwrap_or(result.detected_language.language),
|
||||||
|
text: result.translated_text,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"extension": ["ts","js","cjs","mjs"],
|
|
||||||
"node-option": [
|
|
||||||
"experimental-specifier-resolution=node",
|
|
||||||
"loader=./test/loader.js"
|
|
||||||
],
|
|
||||||
"slow": 1000,
|
|
||||||
"timeout": 30000,
|
|
||||||
"exit": true
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { loadConfig } from "./built/config.js";
|
|
||||||
import { createRedisConnection } from "./built/redis.js";
|
|
||||||
|
|
||||||
const config = loadConfig();
|
|
||||||
const redis = createRedisConnection(config);
|
|
||||||
|
|
||||||
redis.on("connect", () => redis.disconnect());
|
|
||||||
redis.on("error", (e) => {
|
|
||||||
throw e;
|
|
||||||
});
|
|
|
@ -10,13 +10,10 @@
|
||||||
"migration:run": "typeorm migration:run --dataSource ./built/ormconfig.js",
|
"migration:run": "typeorm migration:run --dataSource ./built/ormconfig.js",
|
||||||
"migration:revert": "typeorm migration:revert --dataSource ./built/ormconfig.js",
|
"migration:revert": "typeorm migration:revert --dataSource ./built/ormconfig.js",
|
||||||
"migration:new": "pnpm node ./scripts/create-migration.mjs",
|
"migration:new": "pnpm node ./scripts/create-migration.mjs",
|
||||||
"check:connect": "node ./check_connect.js",
|
|
||||||
"build": "pnpm tsc --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
|
"build": "pnpm tsc --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
|
||||||
"build:debug": "pnpm tsc --sourceMap --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
|
"build:debug": "pnpm tsc --sourceMap --project tsconfig.json ; pnpm tsc-alias --project tsconfig.json",
|
||||||
"watch": "pnpm tsc --project tsconfig.json --watch ; pnpm tsc-alias --project tsconfig.json --watch",
|
"watch": "pnpm tsc --project tsconfig.json --watch ; pnpm tsc-alias --project tsconfig.json --watch",
|
||||||
"lint": "pnpm biome check --write **/*.ts",
|
"lint": "pnpm biome check --write **/*.ts",
|
||||||
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
|
||||||
"test": "pnpm run mocha",
|
|
||||||
"format": "pnpm biome format * --write"
|
"format": "pnpm biome format * --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -50,7 +47,6 @@
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"decompress": "4.2.1",
|
"decompress": "4.2.1",
|
||||||
"deep-email-validator": "0.1.21",
|
"deep-email-validator": "0.1.21",
|
||||||
"deepl-node": "1.13.0",
|
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"feed": "4.2.2",
|
"feed": "4.2.2",
|
||||||
"file-type": "19.2.0",
|
"file-type": "19.2.0",
|
||||||
|
@ -84,7 +80,6 @@
|
||||||
"nested-property": "4.0.0",
|
"nested-property": "4.0.0",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.14",
|
"nodemailer": "6.9.14",
|
||||||
"opencc-js": "1.0.5",
|
|
||||||
"otpauth": "9.3.1",
|
"otpauth": "9.3.1",
|
||||||
"parse5": "7.1.2",
|
"parse5": "7.1.2",
|
||||||
"pg": "8.12.0",
|
"pg": "8.12.0",
|
||||||
|
@ -140,7 +135,6 @@
|
||||||
"@types/koa__cors": "5.0.0",
|
"@types/koa__cors": "5.0.0",
|
||||||
"@types/koa__multer": "2.0.7",
|
"@types/koa__multer": "2.0.7",
|
||||||
"@types/koa__router": "12.0.4",
|
"@types/koa__router": "12.0.4",
|
||||||
"@types/mocha": "10.0.7",
|
|
||||||
"@types/node": "20.14.11",
|
"@types/node": "20.14.11",
|
||||||
"@types/node-fetch": "2.6.11",
|
"@types/node-fetch": "2.6.11",
|
||||||
"@types/nodemailer": "6.4.15",
|
"@types/nodemailer": "6.4.15",
|
||||||
|
@ -165,7 +159,6 @@
|
||||||
"@types/websocket": "1.0.10",
|
"@types/websocket": "1.0.10",
|
||||||
"@types/ws": "8.5.11",
|
"@types/ws": "8.5.11",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"mocha": "10.6.0",
|
|
||||||
"pug": "3.0.3",
|
"pug": "3.0.3",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"ts-loader": "9.5.1",
|
"ts-loader": "9.5.1",
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
import fetch from "node-fetch";
|
|
||||||
import { Converter } from "opencc-js";
|
|
||||||
import { getAgentByUrl } from "@/misc/fetch.js";
|
|
||||||
import { fetchMeta } from "backend-rs";
|
|
||||||
import type { PostLanguage } from "firefish-js";
|
|
||||||
import * as deepl from "deepl-node";
|
|
||||||
|
|
||||||
// DeepL translate and LibreTranslate don't provide
|
|
||||||
// zh-Hant-TW translations, so we convert zh-Hans-CN
|
|
||||||
// translations into zh-Hant-TW using opencc-js.
|
|
||||||
function convertChinese(convert: boolean, src: string) {
|
|
||||||
if (!convert) return src;
|
|
||||||
const converter = Converter({ from: "cn", to: "twp" });
|
|
||||||
return converter(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stem(lang: PostLanguage): string {
|
|
||||||
let toReturn = lang as string;
|
|
||||||
if (toReturn.includes("-")) toReturn = toReturn.split("-")[0];
|
|
||||||
if (toReturn.includes("_")) toReturn = toReturn.split("_")[0];
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function translate(
|
|
||||||
text: string,
|
|
||||||
from: PostLanguage | null,
|
|
||||||
to: PostLanguage,
|
|
||||||
) {
|
|
||||||
const instance = await fetchMeta();
|
|
||||||
|
|
||||||
if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
|
|
||||||
throw Error("No translator is set up on this server.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const source = from == null ? null : stem(from);
|
|
||||||
const target = stem(to);
|
|
||||||
|
|
||||||
if (instance.libreTranslateApiUrl != null) {
|
|
||||||
const jsonBody = {
|
|
||||||
q: text,
|
|
||||||
source: source ?? "auto",
|
|
||||||
target,
|
|
||||||
format: "text",
|
|
||||||
api_key: instance.libreTranslateApiKey ?? "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const url = new URL(instance.libreTranslateApiUrl);
|
|
||||||
if (url.pathname.endsWith("/")) {
|
|
||||||
url.pathname = url.pathname.slice(0, -1);
|
|
||||||
}
|
|
||||||
if (!url.pathname.endsWith("/translate")) {
|
|
||||||
url.pathname += "/translate";
|
|
||||||
}
|
|
||||||
const res = await fetch(url.toString(), {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(jsonBody),
|
|
||||||
agent: getAgentByUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
const json = (await res.json()) as {
|
|
||||||
detectedLanguage?: {
|
|
||||||
confidence: number;
|
|
||||||
language: string;
|
|
||||||
};
|
|
||||||
translatedText: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
sourceLang: source ?? json.detectedLanguage?.language,
|
|
||||||
text: convertChinese(
|
|
||||||
["zh-hant", "zh-TW"].includes(to),
|
|
||||||
json.translatedText,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const deeplTranslator = new deepl.Translator(instance.deeplAuthKey ?? "");
|
|
||||||
const result = await deeplTranslator.translateText(
|
|
||||||
text,
|
|
||||||
source as deepl.SourceLanguageCode | null,
|
|
||||||
// DeepL API requires us to specify "en-US" or "en-GB" for English
|
|
||||||
// translations ("en" does not work), so we need to address it
|
|
||||||
(target === "en" ? to : target) as deepl.TargetLanguageCode,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sourceLang: source ?? result.detectedSourceLang,
|
|
||||||
text: convertChinese(["zh-hant", "zh-TW"].includes(to), result.text),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ApiError } from "@/server/api/error.js";
|
import { ApiError } from "@/server/api/error.js";
|
||||||
import { getNote } from "@/server/api/common/getters.js";
|
import { getNote } from "@/server/api/common/getters.js";
|
||||||
import { translate } from "@/misc/translate.js";
|
import { translate } from "backend-rs";
|
||||||
import type { PostLanguage } from "firefish-js";
|
|
||||||
import define from "@/server/api/define.js";
|
import define from "@/server/api/define.js";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -47,7 +46,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
|
||||||
return translate(
|
return translate(
|
||||||
note.text,
|
note.text,
|
||||||
note.lang as PostLanguage | null,
|
note.lang as string | null,
|
||||||
ps.targetLang as PostLanguage,
|
ps.targetLang,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
getStubMastoContext,
|
getStubMastoContext,
|
||||||
type MastoContext,
|
type MastoContext,
|
||||||
} from "@/server/api/mastodon/index.js";
|
} from "@/server/api/mastodon/index.js";
|
||||||
import { translate } from "@/misc/translate.js";
|
import { translate } from "backend-rs";
|
||||||
import { createScheduledNoteJob } from "@/queue/index.js";
|
import { createScheduledNoteJob } from "@/queue/index.js";
|
||||||
|
|
||||||
export class NoteHelpers {
|
export class NoteHelpers {
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import rndstr from "rndstr";
|
|
||||||
import { initDb } from "../src/db/postgre.js";
|
|
||||||
import { initTestDb } from "./utils.js";
|
|
||||||
|
|
||||||
describe("ActivityPub", () => {
|
|
||||||
before(async () => {
|
|
||||||
//await initTestDb();
|
|
||||||
await initDb();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Parse minimum object", () => {
|
|
||||||
const host = "https://host1.test";
|
|
||||||
const preferredUsername = `${rndstr("A-Z", 4)}${rndstr("a-z", 4)}`;
|
|
||||||
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
|
|
||||||
|
|
||||||
const actor = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
id: actorId,
|
|
||||||
type: "Person",
|
|
||||||
preferredUsername,
|
|
||||||
inbox: `${actorId}/inbox`,
|
|
||||||
outbox: `${actorId}/outbox`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const post = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
id: `${host}/users/${rndstr("0-9a-z", 8)}`,
|
|
||||||
type: "Note",
|
|
||||||
attributedTo: actor.id,
|
|
||||||
to: "https://www.w3.org/ns/activitystreams#Public",
|
|
||||||
content: "あ",
|
|
||||||
};
|
|
||||||
|
|
||||||
it("Minimum Actor", async () => {
|
|
||||||
const { MockResolver } = await import("./misc/mock-resolver.js");
|
|
||||||
const { createPerson } = await import(
|
|
||||||
"../src/remote/activitypub/models/person.js"
|
|
||||||
);
|
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
|
||||||
resolver._register(actor.id, actor);
|
|
||||||
|
|
||||||
const user = await createPerson(actor.id, resolver);
|
|
||||||
|
|
||||||
assert.deepStrictEqual(user.uri, actor.id);
|
|
||||||
assert.deepStrictEqual(user.username, actor.preferredUsername);
|
|
||||||
assert.deepStrictEqual(user.inbox, actor.inbox);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Minimum Note", async () => {
|
|
||||||
const { MockResolver } = await import("./misc/mock-resolver.js");
|
|
||||||
const { createNote } = await import(
|
|
||||||
"../src/remote/activitypub/models/note.js"
|
|
||||||
);
|
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
|
||||||
resolver._register(actor.id, actor);
|
|
||||||
resolver._register(post.id, post);
|
|
||||||
|
|
||||||
const note = await createNote(post.id, resolver, true);
|
|
||||||
|
|
||||||
assert.deepStrictEqual(note?.uri, post.id);
|
|
||||||
assert.deepStrictEqual(note.visibility, "public");
|
|
||||||
assert.deepStrictEqual(note.text, post.content);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Truncate long name", () => {
|
|
||||||
const host = "https://host1.test";
|
|
||||||
const preferredUsername = `${rndstr("A-Z", 4)}${rndstr("a-z", 4)}`;
|
|
||||||
const actorId = `${host}/users/${preferredUsername.toLowerCase()}`;
|
|
||||||
|
|
||||||
const name = rndstr("0-9a-z", 129);
|
|
||||||
|
|
||||||
const actor = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
id: actorId,
|
|
||||||
type: "Person",
|
|
||||||
preferredUsername,
|
|
||||||
name,
|
|
||||||
inbox: `${actorId}/inbox`,
|
|
||||||
outbox: `${actorId}/outbox`,
|
|
||||||
};
|
|
||||||
|
|
||||||
it("Actor", async () => {
|
|
||||||
const { MockResolver } = await import("./misc/mock-resolver.js");
|
|
||||||
const { createPerson } = await import(
|
|
||||||
"../src/remote/activitypub/models/person.js"
|
|
||||||
);
|
|
||||||
|
|
||||||
const resolver = new MockResolver();
|
|
||||||
resolver._register(actor.id, actor);
|
|
||||||
|
|
||||||
const user = await createPerson(actor.id, resolver);
|
|
||||||
|
|
||||||
assert.deepStrictEqual(user.name, actor.name.substr(0, 128));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,75 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
import httpSignature from "@peertube/http-signature";
|
|
||||||
import { genRsaKeyPair } from "../src/misc/gen-key-pair.js";
|
|
||||||
import {
|
|
||||||
createSignedGet,
|
|
||||||
createSignedPost,
|
|
||||||
} from "../src/remote/activitypub/ap-request.js";
|
|
||||||
|
|
||||||
export const buildParsedSignature = (
|
|
||||||
signingString: string,
|
|
||||||
signature: string,
|
|
||||||
algorithm: string,
|
|
||||||
) => {
|
|
||||||
return {
|
|
||||||
scheme: "Signature",
|
|
||||||
params: {
|
|
||||||
keyId: "KeyID", // dummy, not used for verify
|
|
||||||
algorithm: algorithm,
|
|
||||||
headers: ["(request-target)", "date", "host", "digest"], // dummy, not used for verify
|
|
||||||
signature: signature,
|
|
||||||
},
|
|
||||||
signingString: signingString,
|
|
||||||
algorithm: algorithm.toUpperCase(),
|
|
||||||
keyId: "KeyID", // dummy, not used for verify
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("ap-request", () => {
|
|
||||||
it("createSignedPost with verify", async () => {
|
|
||||||
const keypair = await genRsaKeyPair();
|
|
||||||
const key = { keyId: "x", privateKeyPem: keypair.privateKey };
|
|
||||||
const url = "https://example.com/inbox";
|
|
||||||
const activity = { a: 1 };
|
|
||||||
const body = JSON.stringify(activity);
|
|
||||||
const headers = {
|
|
||||||
"User-Agent": "UA",
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = createSignedPost({
|
|
||||||
key,
|
|
||||||
url,
|
|
||||||
body,
|
|
||||||
additionalHeaders: headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsed = buildParsedSignature(
|
|
||||||
req.signingString,
|
|
||||||
req.signature,
|
|
||||||
"rsa-sha256",
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
|
||||||
assert.deepStrictEqual(result, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("createSignedGet with verify", async () => {
|
|
||||||
const keypair = await genRsaKeyPair();
|
|
||||||
const key = { keyId: "x", privateKeyPem: keypair.privateKey };
|
|
||||||
const url = "https://example.com/outbox";
|
|
||||||
const headers = {
|
|
||||||
"User-Agent": "UA",
|
|
||||||
};
|
|
||||||
|
|
||||||
const req = createSignedGet({ key, url, additionalHeaders: headers });
|
|
||||||
|
|
||||||
const parsed = buildParsedSignature(
|
|
||||||
req.signingString,
|
|
||||||
req.signature,
|
|
||||||
"rsa-sha256",
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = httpSignature.verifySignature(parsed, keypair.publicKey);
|
|
||||||
assert.deepStrictEqual(result, true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,535 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
post,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("API visibility", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Note visibility", async () => {
|
|
||||||
//#region vars
|
|
||||||
/** ヒロイン */
|
|
||||||
let alice: any;
|
|
||||||
/** フォロワー */
|
|
||||||
let follower: any;
|
|
||||||
/** 非フォロワー */
|
|
||||||
let other: any;
|
|
||||||
/** 非フォロワーでもリプライやメンションをされた人 */
|
|
||||||
let target: any;
|
|
||||||
/** specified mentionでmentionを飛ばされる人 */
|
|
||||||
let target2: any;
|
|
||||||
|
|
||||||
/** public-post */
|
|
||||||
let pub: any;
|
|
||||||
/** home-post */
|
|
||||||
let home: any;
|
|
||||||
/** followers-post */
|
|
||||||
let fol: any;
|
|
||||||
/** specified-post */
|
|
||||||
let spe: any;
|
|
||||||
|
|
||||||
/** public-reply to target's post */
|
|
||||||
let pubR: any;
|
|
||||||
/** home-reply to target's post */
|
|
||||||
let homeR: any;
|
|
||||||
/** followers-reply to target's post */
|
|
||||||
let folR: any;
|
|
||||||
/** specified-reply to target's post */
|
|
||||||
let speR: any;
|
|
||||||
|
|
||||||
/** public-mention to target */
|
|
||||||
let pubM: any;
|
|
||||||
/** home-mention to target */
|
|
||||||
let homeM: any;
|
|
||||||
/** followers-mention to target */
|
|
||||||
let folM: any;
|
|
||||||
/** specified-mention to target */
|
|
||||||
let speM: any;
|
|
||||||
|
|
||||||
/** reply target post */
|
|
||||||
let tgt: any;
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
const show = async (noteId: any, by: any) => {
|
|
||||||
return await request(
|
|
||||||
"/notes/show",
|
|
||||||
{
|
|
||||||
noteId,
|
|
||||||
},
|
|
||||||
by,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
//#region prepare
|
|
||||||
// signup
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
follower = await signup({ username: "follower" });
|
|
||||||
other = await signup({ username: "other" });
|
|
||||||
target = await signup({ username: "target" });
|
|
||||||
target2 = await signup({ username: "target2" });
|
|
||||||
|
|
||||||
// follow alice <= follower
|
|
||||||
await request("/following/create", { userId: alice.id }, follower);
|
|
||||||
|
|
||||||
// normal posts
|
|
||||||
pub = await post(alice, { text: "x", visibility: "public" });
|
|
||||||
home = await post(alice, { text: "x", visibility: "home" });
|
|
||||||
fol = await post(alice, { text: "x", visibility: "followers" });
|
|
||||||
spe = await post(alice, {
|
|
||||||
text: "x",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [target.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
// replies
|
|
||||||
tgt = await post(target, { text: "y", visibility: "public" });
|
|
||||||
pubR = await post(alice, {
|
|
||||||
text: "x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "public",
|
|
||||||
});
|
|
||||||
homeR = await post(alice, {
|
|
||||||
text: "x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "home",
|
|
||||||
});
|
|
||||||
folR = await post(alice, {
|
|
||||||
text: "x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "followers",
|
|
||||||
});
|
|
||||||
speR = await post(alice, {
|
|
||||||
text: "x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "specified",
|
|
||||||
});
|
|
||||||
|
|
||||||
// mentions
|
|
||||||
pubM = await post(alice, {
|
|
||||||
text: "@target x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "public",
|
|
||||||
});
|
|
||||||
homeM = await post(alice, {
|
|
||||||
text: "@target x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "home",
|
|
||||||
});
|
|
||||||
folM = await post(alice, {
|
|
||||||
text: "@target x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "followers",
|
|
||||||
});
|
|
||||||
speM = await post(alice, {
|
|
||||||
text: "@target2 x",
|
|
||||||
replyId: tgt.id,
|
|
||||||
visibility: "specified",
|
|
||||||
});
|
|
||||||
//#endregion
|
|
||||||
});
|
|
||||||
|
|
||||||
//#region show post
|
|
||||||
// public
|
|
||||||
it("[show] public-postを自分が見れる", async(async () => {
|
|
||||||
const res = await show(pub.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-postをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pub.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-postを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pub.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-postを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(pub.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// home
|
|
||||||
it("[show] home-postを自分が見れる", async(async () => {
|
|
||||||
const res = await show(home.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-postをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(home.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-postを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(home.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-postを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(home.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// followers
|
|
||||||
it("[show] followers-postを自分が見れる", async(async () => {
|
|
||||||
const res = await show(fol.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-postをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(fol.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-postを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(fol.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-postを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(fol.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// specified
|
|
||||||
it("[show] specified-postを自分が見れる", async(async () => {
|
|
||||||
const res = await show(spe.id, alice);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-postを指定ユーザーが見れる", async(async () => {
|
|
||||||
const res = await show(spe.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-postをフォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(spe.id, follower);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-postを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(spe.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-postを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(spe.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region show reply
|
|
||||||
// public
|
|
||||||
it("[show] public-replyを自分が見れる", async(async () => {
|
|
||||||
const res = await show(pubR.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-replyをされた人が見れる", async(async () => {
|
|
||||||
const res = await show(pubR.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-replyをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pubR.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-replyを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pubR.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-replyを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(pubR.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// home
|
|
||||||
it("[show] home-replyを自分が見れる", async(async () => {
|
|
||||||
const res = await show(homeR.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-replyをされた人が見れる", async(async () => {
|
|
||||||
const res = await show(homeR.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-replyをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(homeR.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-replyを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(homeR.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-replyを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(homeR.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// followers
|
|
||||||
it("[show] followers-replyを自分が見れる", async(async () => {
|
|
||||||
const res = await show(folR.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-replyを非フォロワーでもリプライされていれば見れる", async(async () => {
|
|
||||||
const res = await show(folR.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-replyをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(folR.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-replyを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(folR.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-replyを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(folR.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// specified
|
|
||||||
it("[show] specified-replyを自分が見れる", async(async () => {
|
|
||||||
const res = await show(speR.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-replyを指定ユーザーが見れる", async(async () => {
|
|
||||||
const res = await show(speR.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-replyをされた人が指定されてなくても見れる", async(async () => {
|
|
||||||
const res = await show(speR.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-replyをフォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(speR.id, follower);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-replyを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(speR.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-replyを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(speR.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region show mention
|
|
||||||
// public
|
|
||||||
it("[show] public-mentionを自分が見れる", async(async () => {
|
|
||||||
const res = await show(pubM.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-mentionをされた人が見れる", async(async () => {
|
|
||||||
const res = await show(pubM.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-mentionをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pubM.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-mentionを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(pubM.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] public-mentionを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(pubM.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// home
|
|
||||||
it("[show] home-mentionを自分が見れる", async(async () => {
|
|
||||||
const res = await show(homeM.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-mentionをされた人が見れる", async(async () => {
|
|
||||||
const res = await show(homeM.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-mentionをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(homeM.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-mentionを非フォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(homeM.id, other);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] home-mentionを未認証が見れる", async(async () => {
|
|
||||||
const res = await show(homeM.id, null);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
// followers
|
|
||||||
it("[show] followers-mentionを自分が見れる", async(async () => {
|
|
||||||
const res = await show(folM.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-mentionをメンションされていれば非フォロワーでも見れる", async(async () => {
|
|
||||||
const res = await show(folM.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-mentionをフォロワーが見れる", async(async () => {
|
|
||||||
const res = await show(folM.id, follower);
|
|
||||||
assert.strictEqual(res.body.text, "@target x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-mentionを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(folM.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] followers-mentionを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(folM.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// specified
|
|
||||||
it("[show] specified-mentionを自分が見れる", async(async () => {
|
|
||||||
const res = await show(speM.id, alice);
|
|
||||||
assert.strictEqual(res.body.text, "@target2 x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-mentionを指定ユーザーが見れる", async(async () => {
|
|
||||||
const res = await show(speM.id, target);
|
|
||||||
assert.strictEqual(res.body.text, "@target2 x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-mentionをされた人が指定されてなかったら見れない", async(async () => {
|
|
||||||
const res = await show(speM.id, target2);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-mentionをフォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(speM.id, follower);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-mentionを非フォロワーが見れない", async(async () => {
|
|
||||||
const res = await show(speM.id, other);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[show] specified-mentionを未認証が見れない", async(async () => {
|
|
||||||
const res = await show(speM.id, null);
|
|
||||||
assert.strictEqual(res.status, 404);
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region HTL
|
|
||||||
it("[HTL] public-post が 自分が見れる", async(async () => {
|
|
||||||
const res = await request("/notes/timeline", { limit: 100 }, alice);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
|
||||||
assert.strictEqual(notes[0].text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[HTL] public-post が 非フォロワーから見れない", async(async () => {
|
|
||||||
const res = await request("/notes/timeline", { limit: 100 }, other);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == pub.id);
|
|
||||||
assert.strictEqual(notes.length, 0);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[HTL] followers-post が フォロワーから見れる", async(async () => {
|
|
||||||
const res = await request("/notes/timeline", { limit: 100 }, follower);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == fol.id);
|
|
||||||
assert.strictEqual(notes[0].text, "x");
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region RTL
|
|
||||||
it("[replies] followers-reply が フォロワーから見れる", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/replies",
|
|
||||||
{ noteId: tgt.id, limit: 100 },
|
|
||||||
follower,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
|
||||||
assert.strictEqual(notes[0].text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[replies] followers-reply が 非フォロワー (リプライ先ではない) から見れない", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/replies",
|
|
||||||
{ noteId: tgt.id, limit: 100 },
|
|
||||||
other,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
|
||||||
assert.strictEqual(notes.length, 0);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[replies] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/replies",
|
|
||||||
{ noteId: tgt.id, limit: 100 },
|
|
||||||
target,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
|
||||||
assert.strictEqual(notes[0].text, "x");
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region MTL
|
|
||||||
it("[mentions] followers-reply が 非フォロワー (リプライ先である) から見れる", async(async () => {
|
|
||||||
const res = await request("/notes/mentions", { limit: 100 }, target);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == folR.id);
|
|
||||||
assert.strictEqual(notes[0].text, "x");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("[mentions] followers-mention が 非フォロワー (メンション先である) から見れる", async(async () => {
|
|
||||||
const res = await request("/notes/mentions", { limit: 100 }, target);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
const notes = res.body.filter((n: any) => n.id == folM.id);
|
|
||||||
assert.strictEqual(notes[0].text, "@target x");
|
|
||||||
}));
|
|
||||||
//#endregion
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,92 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
post,
|
|
||||||
react,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
uploadFile,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("API", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
carol = await signup({ username: "carol" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("General validation", () => {
|
|
||||||
it("wrong type", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
string: 42,
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("missing require param", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
string: "a",
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("invalid misskey:id (empty string)", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
id: "",
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("valid misskey:id", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
id: "8wvhjghbxu",
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("default value", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
string: "a",
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.body.default, "hello");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("can set null even if it has default value", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
nullableDefault: null,
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.body.nullableDefault, null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("cannot set undefined if it has default value", async(async () => {
|
|
||||||
const res = await request("/test", {
|
|
||||||
required: true,
|
|
||||||
nullableDefault: undefined,
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.body.nullableDefault, "hello");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,129 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
post,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("Block", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
// alice blocks bob
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
carol = await signup({ username: "carol" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Block作成", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/blocking/create",
|
|
||||||
{
|
|
||||||
userId: bob.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ブロックされているユーザーをフォローできない", async(async () => {
|
|
||||||
const res = await request("/following/create", { userId: alice.id }, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.error.id,
|
|
||||||
"c4ab57cc-4e41-45e9-bfd9-584f61e35ce0",
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ブロックされているユーザーにリアクションできない", async(async () => {
|
|
||||||
const note = await post(alice, { text: "hello" });
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/reactions/create",
|
|
||||||
{ noteId: note.id, reaction: "👍" },
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.error.id,
|
|
||||||
"20ef5475-9f38-4e4c-bd33-de6d979498ec",
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ブロックされているユーザーに返信できない", async(async () => {
|
|
||||||
const note = await post(alice, { text: "hello" });
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{ replyId: note.id, text: "yo" },
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.error.id,
|
|
||||||
"b390d7e1-8a5e-46ed-b625-06271cafd3d3",
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ブロックされているユーザーのノートをRenoteできない", async(async () => {
|
|
||||||
const note = await post(alice, { text: "hello" });
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{ renoteId: note.id, text: "yo" },
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.error.id,
|
|
||||||
"b390d7e1-8a5e-46ed-b625-06271cafd3d3",
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// TODO: ユーザーリストに入れられないテスト
|
|
||||||
|
|
||||||
// TODO: ユーザーリストから除外されるテスト
|
|
||||||
|
|
||||||
it("タイムライン(LTL)にブロックされているユーザーの投稿が含まれない", async(async () => {
|
|
||||||
const aliceNote = await post(alice);
|
|
||||||
const bobNote = await post(bob);
|
|
||||||
const carolNote = await post(carol);
|
|
||||||
|
|
||||||
const res = await request("/notes/local-timeline", {}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === aliceNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === bobNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
});
|
|
|
@ -1,15 +0,0 @@
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
|
||||||
redistest:
|
|
||||||
image: redis:6
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:56312:6379"
|
|
||||||
|
|
||||||
dbtest:
|
|
||||||
image: postgres:13
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:54312:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: "test-misskey"
|
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
|
|
@ -1,865 +0,0 @@
|
||||||
/*
|
|
||||||
process.env.NODE_ENV = 'test';
|
|
||||||
|
|
||||||
import * as assert from 'assert';
|
|
||||||
import * as childProcess from 'child_process';
|
|
||||||
import { async, signup, request, post, react, uploadFile, startServer, shutdownServer } from './utils.js';
|
|
||||||
|
|
||||||
describe('API: Endpoints', () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: 'alice' });
|
|
||||||
bob = await signup({ username: 'bob' });
|
|
||||||
carol = await signup({ username: 'carol' });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('signup', () => {
|
|
||||||
it('不正なユーザー名でアカウントが作成できない', async(async () => {
|
|
||||||
const res = await request('/signup', {
|
|
||||||
username: 'test.',
|
|
||||||
password: 'test'
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('空のパスワードでアカウントが作成できない', async(async () => {
|
|
||||||
const res = await request('/signup', {
|
|
||||||
username: 'test',
|
|
||||||
password: ''
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('正しくアカウントが作成できる', async(async () => {
|
|
||||||
const me = {
|
|
||||||
username: 'test1',
|
|
||||||
password: 'test1'
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request('/signup', me);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.username, me.username);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('同じユーザー名のアカウントは作成できない', async(async () => {
|
|
||||||
await signup({
|
|
||||||
username: 'test2'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/signup', {
|
|
||||||
username: 'test2',
|
|
||||||
password: 'test2'
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('signin', () => {
|
|
||||||
it('間違ったパスワードでサインインできない', async(async () => {
|
|
||||||
await signup({
|
|
||||||
username: 'test3',
|
|
||||||
password: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/signin', {
|
|
||||||
username: 'test3',
|
|
||||||
password: 'bar'
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 403);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('クエリをインジェクションできない', async(async () => {
|
|
||||||
await signup({
|
|
||||||
username: 'test4'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/signin', {
|
|
||||||
username: 'test4',
|
|
||||||
password: {
|
|
||||||
$gt: ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('正しい情報でサインインできる', async(async () => {
|
|
||||||
await signup({
|
|
||||||
username: 'test5',
|
|
||||||
password: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/signin', {
|
|
||||||
username: 'test5',
|
|
||||||
password: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('i/update', () => {
|
|
||||||
it('アカウント設定を更新できる', async(async () => {
|
|
||||||
const myName = '大室櫻子';
|
|
||||||
const myLocation = '七森中';
|
|
||||||
const myBirthday = '2000-09-07';
|
|
||||||
|
|
||||||
const res = await request('/i/update', {
|
|
||||||
name: myName,
|
|
||||||
location: myLocation,
|
|
||||||
birthday: myBirthday
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, myName);
|
|
||||||
assert.strictEqual(res.body.location, myLocation);
|
|
||||||
assert.strictEqual(res.body.birthday, myBirthday);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('名前を空白にできない', async(async () => {
|
|
||||||
const res = await request('/i/update', {
|
|
||||||
name: ' '
|
|
||||||
}, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('誕生日の設定を削除できる', async(async () => {
|
|
||||||
await request('/i/update', {
|
|
||||||
birthday: '2000-09-07'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
const res = await request('/i/update', {
|
|
||||||
birthday: null
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.birthday, null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('不正な誕生日の形式で怒られる', async(async () => {
|
|
||||||
const res = await request('/i/update', {
|
|
||||||
birthday: '2000/09/07'
|
|
||||||
}, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('users/show', () => {
|
|
||||||
it('ユーザーが取得できる', async(async () => {
|
|
||||||
const res = await request('/users/show', {
|
|
||||||
userId: alice.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.id, alice.id);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('ユーザーが存在しなかったら怒る', async(async () => {
|
|
||||||
const res = await request('/users/show', {
|
|
||||||
userId: '000000000000000000000000'
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/users/show', {
|
|
||||||
userId: 'kyoppie'
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notes/show', () => {
|
|
||||||
it('投稿が取得できる', async(async () => {
|
|
||||||
const myPost = await post(alice, {
|
|
||||||
text: 'test'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/notes/show', {
|
|
||||||
noteId: myPost.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.id, myPost.id);
|
|
||||||
assert.strictEqual(res.body.text, myPost.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('投稿が存在しなかったら怒る', async(async () => {
|
|
||||||
const res = await request('/notes/show', {
|
|
||||||
noteId: '000000000000000000000000'
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/notes/show', {
|
|
||||||
noteId: 'kyoppie'
|
|
||||||
});
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notes/reactions/create', () => {
|
|
||||||
it('リアクションできる', async(async () => {
|
|
||||||
const bobPost = await post(bob);
|
|
||||||
|
|
||||||
const alice = await signup({ username: 'alice' });
|
|
||||||
const res = await request('/notes/reactions/create', {
|
|
||||||
noteId: bobPost.id,
|
|
||||||
reaction: '🚀',
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 204);
|
|
||||||
|
|
||||||
const resNote = await request('/notes/show', {
|
|
||||||
noteId: bobPost.id,
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(resNote.status, 200);
|
|
||||||
assert.strictEqual(resNote.body.reactions['🚀'], [alice.id]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('自分の投稿にもリアクションできる', async(async () => {
|
|
||||||
const myPost = await post(alice);
|
|
||||||
|
|
||||||
const res = await request('/notes/reactions/create', {
|
|
||||||
noteId: myPost.id,
|
|
||||||
reaction: '🚀',
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 204);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('二重にリアクションできない', async(async () => {
|
|
||||||
const bobPost = await post(bob);
|
|
||||||
|
|
||||||
await react(alice, bobPost, 'like');
|
|
||||||
|
|
||||||
const res = await request('/notes/reactions/create', {
|
|
||||||
noteId: bobPost.id,
|
|
||||||
reaction: '🚀',
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しない投稿にはリアクションできない', async(async () => {
|
|
||||||
const res = await request('/notes/reactions/create', {
|
|
||||||
noteId: '000000000000000000000000',
|
|
||||||
reaction: '🚀',
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('空のパラメータで怒られる', async(async () => {
|
|
||||||
const res = await request('/notes/reactions/create', {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/notes/reactions/create', {
|
|
||||||
noteId: 'kyoppie',
|
|
||||||
reaction: '🚀',
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('following/create', () => {
|
|
||||||
it('フォローできる', async(async () => {
|
|
||||||
const res = await request('/following/create', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('既にフォローしている場合は怒る', async(async () => {
|
|
||||||
const res = await request('/following/create', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しないユーザーはフォローできない', async(async () => {
|
|
||||||
const res = await request('/following/create', {
|
|
||||||
userId: '000000000000000000000000'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('自分自身はフォローできない', async(async () => {
|
|
||||||
const res = await request('/following/create', {
|
|
||||||
userId: alice.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('空のパラメータで怒られる', async(async () => {
|
|
||||||
const res = await request('/following/create', {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/following/create', {
|
|
||||||
userId: 'foo'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('following/delete', () => {
|
|
||||||
it('フォロー解除できる', async(async () => {
|
|
||||||
await request('/following/create', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
const res = await request('/following/delete', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('フォローしていない場合は怒る', async(async () => {
|
|
||||||
const res = await request('/following/delete', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しないユーザーはフォロー解除できない', async(async () => {
|
|
||||||
const res = await request('/following/delete', {
|
|
||||||
userId: '000000000000000000000000'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('自分自身はフォロー解除できない', async(async () => {
|
|
||||||
const res = await request('/following/delete', {
|
|
||||||
userId: alice.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('空のパラメータで怒られる', async(async () => {
|
|
||||||
const res = await request('/following/delete', {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/following/delete', {
|
|
||||||
userId: 'kyoppie'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drive', () => {
|
|
||||||
it('ドライブ情報を取得できる', async(async () => {
|
|
||||||
await uploadFile({
|
|
||||||
userId: alice.id,
|
|
||||||
size: 256
|
|
||||||
});
|
|
||||||
await uploadFile({
|
|
||||||
userId: alice.id,
|
|
||||||
size: 512
|
|
||||||
});
|
|
||||||
await uploadFile({
|
|
||||||
userId: alice.id,
|
|
||||||
size: 1024
|
|
||||||
});
|
|
||||||
const res = await request('/drive', {}, alice);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
expect(res.body).have.property('usage').eql(1792);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drive/files/create', () => {
|
|
||||||
it('ファイルを作成できる', async(async () => {
|
|
||||||
const res = await uploadFile(alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, 'Lenna.png');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('ファイルに名前を付けられる', async(async () => {
|
|
||||||
const res = await assert.request(server)
|
|
||||||
.post('/drive/files/create')
|
|
||||||
.field('i', alice.token)
|
|
||||||
.field('name', 'Belmond.png')
|
|
||||||
.attach('file', fs.readFileSync(__dirname + '/resources/Lenna.png'), 'Lenna.png');
|
|
||||||
|
|
||||||
expect(res).have.status(200);
|
|
||||||
expect(res.body).be.a('object');
|
|
||||||
expect(res.body).have.property('name').eql('Belmond.png');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('ファイル無しで怒られる', async(async () => {
|
|
||||||
const res = await request('/drive/files/create', {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('SVGファイルを作成できる', async(async () => {
|
|
||||||
const res = await uploadFile(alice, __dirname + '/resources/image.svg');
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, 'image.svg');
|
|
||||||
assert.strictEqual(res.body.type, 'image/svg+xml');
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drive/files/update', () => {
|
|
||||||
it('名前を更新できる', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
const newName = 'いちごパスタ.png';
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
name: newName
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, newName);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('他人のファイルは更新できない', async(async () => {
|
|
||||||
const file = await uploadFile(bob);
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
name: 'いちごパスタ.png'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('親フォルダを更新できる', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: folder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.folderId, folder.id);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('親フォルダを無しにできる', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: folder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: null
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.folderId, null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('他人のフォルダには入れられない', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, bob)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: folder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しないフォルダで怒られる', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: '000000000000000000000000'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('不正なフォルダIDで怒られる', async(async () => {
|
|
||||||
const file = await uploadFile(alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: file.id,
|
|
||||||
folderId: 'foo'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('ファイルが存在しなかったら怒る', async(async () => {
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: '000000000000000000000000',
|
|
||||||
name: 'いちごパスタ.png'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('間違ったIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/drive/files/update', {
|
|
||||||
fileId: 'kyoppie',
|
|
||||||
name: 'いちごパスタ.png'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drive/folders/create', () => {
|
|
||||||
it('フォルダを作成できる', async(async () => {
|
|
||||||
const res = await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, 'test');
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drive/folders/update', () => {
|
|
||||||
it('名前を更新できる', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
name: 'new name'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.name, 'new name');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('他人のフォルダを更新できない', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, bob)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
name: 'new name'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('親フォルダを更新できる', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const parentFolder = (await request('/drive/folders/create', {
|
|
||||||
name: 'parent'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: parentFolder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.parentId, parentFolder.id);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('親フォルダを無しに更新できる', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const parentFolder = (await request('/drive/folders/create', {
|
|
||||||
name: 'parent'
|
|
||||||
}, alice)).body;
|
|
||||||
await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: parentFolder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: null
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.parentId, null);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('他人のフォルダを親フォルダに設定できない', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const parentFolder = (await request('/drive/folders/create', {
|
|
||||||
name: 'parent'
|
|
||||||
}, bob)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: parentFolder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('フォルダが循環するような構造にできない', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const parentFolder = (await request('/drive/folders/create', {
|
|
||||||
name: 'parent'
|
|
||||||
}, alice)).body;
|
|
||||||
await request('/drive/folders/update', {
|
|
||||||
folderId: parentFolder.id,
|
|
||||||
parentId: folder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: parentFolder.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('フォルダが循環するような構造にできない(再帰的)', async(async () => {
|
|
||||||
const folderA = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const folderB = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
const folderC = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
await request('/drive/folders/update', {
|
|
||||||
folderId: folderB.id,
|
|
||||||
parentId: folderA.id
|
|
||||||
}, alice);
|
|
||||||
await request('/drive/folders/update', {
|
|
||||||
folderId: folderC.id,
|
|
||||||
parentId: folderB.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folderA.id,
|
|
||||||
parentId: folderC.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('フォルダが循環するような構造にできない(自身)', async(async () => {
|
|
||||||
const folderA = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folderA.id,
|
|
||||||
parentId: folderA.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しない親フォルダを設定できない', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: '000000000000000000000000'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('不正な親フォルダIDで怒られる', async(async () => {
|
|
||||||
const folder = (await request('/drive/folders/create', {
|
|
||||||
name: 'test'
|
|
||||||
}, alice)).body;
|
|
||||||
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: folder.id,
|
|
||||||
parentId: 'foo'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しないフォルダを更新できない', async(async () => {
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: '000000000000000000000000'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('不正なフォルダIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/drive/folders/update', {
|
|
||||||
folderId: 'foo'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('messaging/messages/create', () => {
|
|
||||||
it('メッセージを送信できる', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: bob.id,
|
|
||||||
text: 'test'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.text, 'test');
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('自分自身にはメッセージを送信できない', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: alice.id,
|
|
||||||
text: 'Yo'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('存在しないユーザーにはメッセージを送信できない', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: '000000000000000000000000',
|
|
||||||
text: 'test'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('不正なユーザーIDで怒られる', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: 'foo',
|
|
||||||
text: 'test'
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('テキストが無くて怒られる', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: bob.id
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('文字数オーバーで怒られる', async(async () => {
|
|
||||||
const res = await request('/messaging/messages/create', {
|
|
||||||
userId: bob.id,
|
|
||||||
text: '!'.repeat(1001)
|
|
||||||
}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notes/replies', () => {
|
|
||||||
it('自分に閲覧権限のない投稿は含まれない', async(async () => {
|
|
||||||
const alicePost = await post(alice, {
|
|
||||||
text: 'foo'
|
|
||||||
});
|
|
||||||
|
|
||||||
await post(bob, {
|
|
||||||
replyId: alicePost.id,
|
|
||||||
text: 'bar',
|
|
||||||
visibility: 'specified',
|
|
||||||
visibleUserIds: [alice.id]
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/notes/replies', {
|
|
||||||
noteId: alicePost.id
|
|
||||||
}, carol);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.length, 0);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('notes/timeline', () => {
|
|
||||||
it('フォロワー限定投稿が含まれる', async(async () => {
|
|
||||||
await request('/following/create', {
|
|
||||||
userId: alice.id
|
|
||||||
}, bob);
|
|
||||||
|
|
||||||
const alicePost = await post(alice, {
|
|
||||||
text: 'foo',
|
|
||||||
visibility: 'followers'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request('/notes/timeline', {}, bob);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.length, 1);
|
|
||||||
assert.strictEqual(res.body[0].id, alicePost.id);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
*/
|
|
|
@ -1,50 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
|
|
||||||
import { parse } from "mfm-js";
|
|
||||||
import { extractMentions } from "../src/misc/extract-mentions.js";
|
|
||||||
|
|
||||||
describe("Extract mentions", () => {
|
|
||||||
it("simple", () => {
|
|
||||||
const ast = parse("@foo @bar @baz")!;
|
|
||||||
const mentions = extractMentions(ast);
|
|
||||||
assert.deepStrictEqual(mentions, [
|
|
||||||
{
|
|
||||||
username: "foo",
|
|
||||||
acct: "@foo",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: "bar",
|
|
||||||
acct: "@bar",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: "baz",
|
|
||||||
acct: "@baz",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("nested", () => {
|
|
||||||
const ast = parse("@foo **@bar** @baz")!;
|
|
||||||
const mentions = extractMentions(ast);
|
|
||||||
assert.deepStrictEqual(mentions, [
|
|
||||||
{
|
|
||||||
username: "foo",
|
|
||||||
acct: "@foo",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: "bar",
|
|
||||||
acct: "@bar",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: "baz",
|
|
||||||
acct: "@baz",
|
|
||||||
host: null,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,213 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import * as openapi from "@redocly/openapi-core";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
port,
|
|
||||||
post,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
simpleGet,
|
|
||||||
startServer,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
// Request Accept
|
|
||||||
const ONLY_AP = "application/activity+json";
|
|
||||||
const PREFER_AP = "application/activity+json, */*";
|
|
||||||
const PREFER_HTML = "text/html, */*";
|
|
||||||
const UNSPECIFIED = "*/*";
|
|
||||||
|
|
||||||
// Response Contet-Type
|
|
||||||
const AP = "application/activity+json; charset=utf-8";
|
|
||||||
const JSON = "application/json; charset=utf-8";
|
|
||||||
const HTML = "text/html; charset=utf-8";
|
|
||||||
|
|
||||||
describe("Fetch resource", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
let alice: any;
|
|
||||||
let alicesPost: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
alicesPost = await post(alice, {
|
|
||||||
text: "test",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Common", () => {
|
|
||||||
it("meta", async(async () => {
|
|
||||||
const res = await request("/meta", {});
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET root", async(async () => {
|
|
||||||
const res = await simpleGet("/");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET docs", async(async () => {
|
|
||||||
const res = await simpleGet("/docs/ja-JP/about");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET api-doc", async(async () => {
|
|
||||||
const res = await simpleGet("/api-doc");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET api.json", async(async () => {
|
|
||||||
const res = await simpleGet("/api.json");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, JSON);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Validate api.json", async(async () => {
|
|
||||||
const config = await openapi.loadConfig();
|
|
||||||
const result = await openapi.bundle({
|
|
||||||
config,
|
|
||||||
ref: `http://localhost:${port}/api.json`,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const problem of result.problems) {
|
|
||||||
console.log(`${problem.message} - ${problem.location[0]?.pointer}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.strictEqual(result.problems.length, 0);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET favicon.ico", async(async () => {
|
|
||||||
const res = await simpleGet("/favicon.ico");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "image/x-icon");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET apple-touch-icon.png", async(async () => {
|
|
||||||
const res = await simpleGet("/apple-touch-icon.png");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "image/png");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET twemoji svg", async(async () => {
|
|
||||||
const res = await simpleGet("/twemoji/2764.svg");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "image/svg+xml");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("GET twemoji svg with hyphen", async(async () => {
|
|
||||||
const res = await simpleGet("/twemoji/2764-fe0f-200d-1f525.svg");
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "image/svg+xml");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("/@:username", () => {
|
|
||||||
it("Only AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}`, PREFER_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer HTML => HTML", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}`, PREFER_HTML);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Unspecified => HTML", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("/users/:id", () => {
|
|
||||||
it("Only AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/users/${alice.id}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer HTML => Redirect to /@:username", async(async () => {
|
|
||||||
const res = await simpleGet(`/users/${alice.id}`, PREFER_HTML);
|
|
||||||
assert.strictEqual(res.status, 302);
|
|
||||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Undecided => HTML", async(async () => {
|
|
||||||
const res = await simpleGet(`/users/${alice.id}`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 302);
|
|
||||||
assert.strictEqual(res.location, `/@${alice.username}`);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("/notes/:id", () => {
|
|
||||||
it("Only AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, ONLY_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer AP => AP", async(async () => {
|
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_AP);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, AP);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Prefer HTML => HTML", async(async () => {
|
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_HTML);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Unspecified => HTML", async(async () => {
|
|
||||||
const res = await simpleGet(`/notes/${alicesPost.id}`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, HTML);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Feeds", () => {
|
|
||||||
it("RSS", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}.rss`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "application/rss+xml; charset=utf-8");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ATOM", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}.atom`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "application/atom+xml; charset=utf-8");
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("JSON", async(async () => {
|
|
||||||
const res = await simpleGet(`/@${alice.username}.json`, UNSPECIFIED);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.type, "application/json; charset=utf-8");
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,283 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
connectStream,
|
|
||||||
post,
|
|
||||||
react,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
simpleGet,
|
|
||||||
startServer,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("FF visibility", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
carol = await signup({ username: "carol" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ffVisibility が public なユーザーのフォロー/フォロワーを誰でも見れる", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "public",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
|
||||||
assert.strictEqual(followersRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ffVisibility が followers なユーザーのフォロー/フォロワーを自分で見れる", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "followers",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
|
||||||
assert.strictEqual(followersRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ffVisibility が followers なユーザーのフォロー/フォロワーを非フォロワーが見れない", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "followers",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 400);
|
|
||||||
assert.strictEqual(followersRes.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ffVisibility が followers なユーザーのフォロー/フォロワーをフォロワーが見れる", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "followers",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/following/create",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
|
||||||
assert.strictEqual(followersRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ffVisibility が private なユーザーのフォロー/フォロワーを自分で見れる", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "private",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followingRes.body), true);
|
|
||||||
assert.strictEqual(followersRes.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(followersRes.body), true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ffVisibility が private なユーザーのフォロー/フォロワーを他人が見れない", async(async () => {
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "private",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await request(
|
|
||||||
"/users/following",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
const followersRes = await request(
|
|
||||||
"/users/followers",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
},
|
|
||||||
bob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(followingRes.status, 400);
|
|
||||||
assert.strictEqual(followersRes.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("AP", () => {
|
|
||||||
it("ffVisibility が public 以外ならばAPからは取得できない", async(async () => {
|
|
||||||
{
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "public",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/following`,
|
|
||||||
"application/activity+json",
|
|
||||||
);
|
|
||||||
const followersRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/followers`,
|
|
||||||
"application/activity+json",
|
|
||||||
);
|
|
||||||
assert.strictEqual(followingRes.status, 200);
|
|
||||||
assert.strictEqual(followersRes.status, 200);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "followers",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/following`,
|
|
||||||
"application/activity+json",
|
|
||||||
).catch((res) => ({ status: res.statusCode }));
|
|
||||||
const followersRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/followers`,
|
|
||||||
"application/activity+json",
|
|
||||||
).catch((res) => ({ status: res.statusCode }));
|
|
||||||
assert.strictEqual(followingRes.status, 403);
|
|
||||||
assert.strictEqual(followersRes.status, 403);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
await request(
|
|
||||||
"/i/update",
|
|
||||||
{
|
|
||||||
ffVisibility: "private",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const followingRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/following`,
|
|
||||||
"application/activity+json",
|
|
||||||
).catch((res) => ({ status: res.statusCode }));
|
|
||||||
const followersRes = await simpleGet(
|
|
||||||
`/users/${alice.id}/followers`,
|
|
||||||
"application/activity+json",
|
|
||||||
).catch((res) => ({ status: res.statusCode }));
|
|
||||||
assert.strictEqual(followingRes.status, 403);
|
|
||||||
assert.strictEqual(followersRes.status, 403);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,209 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
import { dirname } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import { getFileInfo } from "../src/misc/get-file-info.js";
|
|
||||||
import { async } from "./utils.js";
|
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
|
||||||
const _dirname = dirname(_filename);
|
|
||||||
|
|
||||||
describe("Get file info", () => {
|
|
||||||
it("Empty file", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/emptyfile`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 0,
|
|
||||||
md5: "d41d8cd98f00b204e9800998ecf8427e",
|
|
||||||
type: {
|
|
||||||
mime: "application/octet-stream",
|
|
||||||
ext: null,
|
|
||||||
},
|
|
||||||
width: undefined,
|
|
||||||
height: undefined,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Generic JPEG", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/Lenna.jpg`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 25360,
|
|
||||||
md5: "091b3f259662aa31e2ffef4519951168",
|
|
||||||
type: {
|
|
||||||
mime: "image/jpeg",
|
|
||||||
ext: "jpg",
|
|
||||||
},
|
|
||||||
width: 512,
|
|
||||||
height: 512,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Generic APNG", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/anime.png`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 1868,
|
|
||||||
md5: "08189c607bea3b952704676bb3c979e0",
|
|
||||||
type: {
|
|
||||||
mime: "image/apng",
|
|
||||||
ext: "apng",
|
|
||||||
},
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Generic AGIF", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/anime.gif`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 2248,
|
|
||||||
md5: "32c47a11555675d9267aee1a86571e7e",
|
|
||||||
type: {
|
|
||||||
mime: "image/gif",
|
|
||||||
ext: "gif",
|
|
||||||
},
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("PNG with alpha", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/with-alpha.png`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 3772,
|
|
||||||
md5: "f73535c3e1e27508885b69b10cf6e991",
|
|
||||||
type: {
|
|
||||||
mime: "image/png",
|
|
||||||
ext: "png",
|
|
||||||
},
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Generic SVG", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/image.svg`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 505,
|
|
||||||
md5: "b6f52b4b021e7b92cdd04509c7267965",
|
|
||||||
type: {
|
|
||||||
mime: "image/svg+xml",
|
|
||||||
ext: "svg",
|
|
||||||
},
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("SVG with XML definition", async(async () => {
|
|
||||||
// https://github.com/misskey-dev/misskey/issues/4413
|
|
||||||
const path = `${_dirname}/resources/with-xml-def.svg`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 544,
|
|
||||||
md5: "4b7a346cde9ccbeb267e812567e33397",
|
|
||||||
type: {
|
|
||||||
mime: "image/svg+xml",
|
|
||||||
ext: "svg",
|
|
||||||
},
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Dimension limit", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/25000x25000.png`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 75933,
|
|
||||||
md5: "268c5dde99e17cf8fe09f1ab3f97df56",
|
|
||||||
type: {
|
|
||||||
mime: "application/octet-stream", // do not treat as image
|
|
||||||
ext: null,
|
|
||||||
},
|
|
||||||
width: 25000,
|
|
||||||
height: 25000,
|
|
||||||
orientation: undefined,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("Rotate JPEG", async(async () => {
|
|
||||||
const path = `${_dirname}/resources/rotate.jpg`;
|
|
||||||
const info = (await getFileInfo(path, {
|
|
||||||
skipSensitiveDetection: true,
|
|
||||||
})) as any;
|
|
||||||
delete info.warnings;
|
|
||||||
delete info.blurhash;
|
|
||||||
delete info.sensitive;
|
|
||||||
delete info.porn;
|
|
||||||
assert.deepStrictEqual(info, {
|
|
||||||
size: 12624,
|
|
||||||
md5: "68d5b2d8d1d1acbbce99203e3ec3857e",
|
|
||||||
type: {
|
|
||||||
mime: "image/jpeg",
|
|
||||||
ext: "jpg",
|
|
||||||
},
|
|
||||||
width: 512,
|
|
||||||
height: 256,
|
|
||||||
orientation: 8,
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* ts-node/esmローダーに投げる前にpath mappingを解決する
|
|
||||||
* 参考
|
|
||||||
* - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115
|
|
||||||
* - https://nodejs.org/api/esm.html#loaders
|
|
||||||
* ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { resolve as resolveTs, load } from "ts-node/esm";
|
|
||||||
import { loadConfig, createMatchPath } from "tsconfig-paths";
|
|
||||||
import { pathToFileURL } from "url";
|
|
||||||
|
|
||||||
const tsconfig = loadConfig();
|
|
||||||
const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
|
|
||||||
|
|
||||||
export function resolve(specifier, ctx, defaultResolve) {
|
|
||||||
let resolvedSpecifier;
|
|
||||||
if (specifier.endsWith(".js")) {
|
|
||||||
// maybe transpiled
|
|
||||||
const specifierWithoutExtension = specifier.substring(
|
|
||||||
0,
|
|
||||||
specifier.length - ".js".length,
|
|
||||||
);
|
|
||||||
const matchedSpecifier = matchPath(specifierWithoutExtension);
|
|
||||||
if (matchedSpecifier) {
|
|
||||||
resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const matchedSpecifier = matchPath(specifier);
|
|
||||||
if (matchedSpecifier) {
|
|
||||||
resolvedSpecifier = pathToFileURL(matchedSpecifier).href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { load };
|
|
|
@ -1,127 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
import * as mfm from "mfm-js";
|
|
||||||
|
|
||||||
import { fromHtml } from "../src/mfm/from-html.js";
|
|
||||||
import { toHtml } from "../src/mfm/to-html.js";
|
|
||||||
|
|
||||||
describe("toHtml", () => {
|
|
||||||
it("br", () => {
|
|
||||||
const input = "foo\nbar\nbaz";
|
|
||||||
const output = "<p><span>foo<br>bar<br>baz</span></p>";
|
|
||||||
assert.equal(toHtml(mfm.parse(input)), output);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("br alt", () => {
|
|
||||||
const input = "foo\r\nbar\rbaz";
|
|
||||||
const output = "<p><span>foo<br>bar<br>baz</span></p>";
|
|
||||||
assert.equal(toHtml(mfm.parse(input)), output);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("fromHtml", () => {
|
|
||||||
it("p", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<p>a</p><p>b</p>"), "a\n\nb");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("block element", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<div>a</div><div>b</div>"), "a\nb");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("inline element", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<ul><li>a</li><li>b</li></ul>"), "a\nb");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("block code", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml("<pre><code>a\nb</code></pre>"),
|
|
||||||
"```\na\nb\n```",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("inline code", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<code>a</code>"), "`a`");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("quote", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml("<blockquote>a\nb</blockquote>"),
|
|
||||||
"> a\n> b",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("br", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<p>abc<br><br/>d</p>"), "abc\n\nd");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link with different text", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml('<p>a <a href="https://firefish.dev/firefish">c</a> d</p>'),
|
|
||||||
"a [c](https://firefish.dev/firefish) d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link with different text, but not encoded", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml('<p>a <a href="https://firefish.dev/ä">c</a> d</p>'),
|
|
||||||
"a [c](<https://firefish.dev/ä>) d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link with same text", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml(
|
|
||||||
'<p>a <a href="https://firefish.dev/firefish/firefish">https://firefish.dev/firefish/firefish</a> d</p>',
|
|
||||||
),
|
|
||||||
"a https://firefish.dev/firefish/firefish d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link with same text, but not encoded", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml(
|
|
||||||
'<p>a <a href="https://firefish.dev/ä">https://firefish.dev/ä</a> d</p>',
|
|
||||||
),
|
|
||||||
"a <https://firefish.dev/ä> d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link with no url", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml('<p>a <a href="b">c</a> d</p>'),
|
|
||||||
"a [c](b) d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link without href", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<p>a <a>c</a> d</p>"), "a c d");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link without text", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml('<p>a <a href="https://firefish.dev/b"></a> d</p>'),
|
|
||||||
"a https://firefish.dev/b d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("link without both", () => {
|
|
||||||
assert.deepStrictEqual(fromHtml("<p>a <a></a> d</p>"), "a d");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("mention", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml(
|
|
||||||
'<p>a <a href="https://info.firefish.dev/@firefish" class="u-url mention">@firefish</a> d</p>',
|
|
||||||
),
|
|
||||||
"a @firefish@info.firefish.dev d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("hashtag", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
fromHtml('<p>a <a href="https://info.firefish.dev/tags/a">#a</a> d</p>', [
|
|
||||||
"#a",
|
|
||||||
]),
|
|
||||||
"a #a d",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,39 +0,0 @@
|
||||||
import Resolver from "../../src/remote/activitypub/resolver.js";
|
|
||||||
import { IObject } from "../../src/remote/activitypub/type.js";
|
|
||||||
|
|
||||||
type MockResponse = {
|
|
||||||
type: string;
|
|
||||||
content: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MockResolver extends Resolver {
|
|
||||||
private _rs = new Map<string, MockResponse>();
|
|
||||||
public async _register(
|
|
||||||
uri: string,
|
|
||||||
content: string | Record<string, any>,
|
|
||||||
type = "application/activity+json",
|
|
||||||
) {
|
|
||||||
this._rs.set(uri, {
|
|
||||||
type,
|
|
||||||
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async resolve(value: string | IObject): Promise<IObject> {
|
|
||||||
if (typeof value !== "string") return value;
|
|
||||||
|
|
||||||
const r = this._rs.get(value);
|
|
||||||
|
|
||||||
if (!r) {
|
|
||||||
throw {
|
|
||||||
name: "StatusError",
|
|
||||||
statusCode: 404,
|
|
||||||
message: "Not registed for mock",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const object = JSON.parse(r.content);
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
post,
|
|
||||||
react,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
waitFire,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("Mute", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
// alice mutes carol
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
carol = await signup({ username: "carol" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ミュート作成", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/mute/create",
|
|
||||||
{
|
|
||||||
userId: carol.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 204);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("「自分宛ての投稿」にミュートしているユーザーの投稿が含まれない", async(async () => {
|
|
||||||
const bobNote = await post(bob, { text: "@alice hi" });
|
|
||||||
const carolNote = await post(carol, { text: "@alice hi" });
|
|
||||||
|
|
||||||
const res = await request("/notes/mentions", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === bobNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ミュートしているユーザーからメンションされても、hasUnreadMentions が true にならない", async(async () => {
|
|
||||||
// 状態リセット
|
|
||||||
await request("/i/read-all-unread-notes", {}, alice);
|
|
||||||
|
|
||||||
await post(carol, { text: "@alice hi" });
|
|
||||||
|
|
||||||
const res = await request("/i", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.body.hasUnreadMentions, false);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ミュートしているユーザーからメンションされても、ストリームに unreadMention イベントが流れてこない", async () => {
|
|
||||||
// 状態リセット
|
|
||||||
await request("/i/read-all-unread-notes", {}, alice);
|
|
||||||
|
|
||||||
const fired = await waitFire(
|
|
||||||
alice,
|
|
||||||
"main",
|
|
||||||
() => post(carol, { text: "@alice hi" }),
|
|
||||||
(msg) => msg.type === "unreadMention",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ミュートしているユーザーからメンションされても、ストリームに unreadNotification イベントが流れてこない", async () => {
|
|
||||||
// 状態リセット
|
|
||||||
await request("/i/read-all-unread-notes", {}, alice);
|
|
||||||
await request("/notifications/mark-all-as-read", {}, alice);
|
|
||||||
|
|
||||||
const fired = await waitFire(
|
|
||||||
alice,
|
|
||||||
"main",
|
|
||||||
() => post(carol, { text: "@alice hi" }),
|
|
||||||
(msg) => msg.type === "unreadNotification",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Timeline", () => {
|
|
||||||
it("タイムラインにミュートしているユーザーの投稿が含まれない", async(async () => {
|
|
||||||
const aliceNote = await post(alice);
|
|
||||||
const bobNote = await post(bob);
|
|
||||||
const carolNote = await post(carol);
|
|
||||||
|
|
||||||
const res = await request("/notes/local-timeline", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === aliceNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === bobNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("タイムラインにミュートしているユーザーの投稿のRenoteが含まれない", async(async () => {
|
|
||||||
const aliceNote = await post(alice);
|
|
||||||
const carolNote = await post(carol);
|
|
||||||
const bobNote = await post(bob, {
|
|
||||||
renoteId: carolNote.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request("/notes/local-timeline", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === aliceNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === bobNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Notification", () => {
|
|
||||||
it("通知にミュートしているユーザーの通知が含まれない(リアクション)", async(async () => {
|
|
||||||
const aliceNote = await post(alice);
|
|
||||||
await react(bob, aliceNote, "like");
|
|
||||||
await react(carol, aliceNote, "like");
|
|
||||||
|
|
||||||
const res = await request("/i/notifications", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((notification: any) => notification.userId === bob.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((notification: any) => notification.userId === carol.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,517 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import { Note } from "../src/models/entities/note.js";
|
|
||||||
import {
|
|
||||||
api,
|
|
||||||
async,
|
|
||||||
initTestDb,
|
|
||||||
post,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
uploadUrl,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("Note", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
let Notes: any;
|
|
||||||
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
const connection = await initTestDb(true);
|
|
||||||
Notes = connection.getRepository(Note);
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("投稿できる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "test",
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ファイルを添付できる", async(async () => {
|
|
||||||
const file = await uploadUrl(
|
|
||||||
alice,
|
|
||||||
"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
fileIds: [file.id],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(res.body.createdNote.fileIds, [file.id]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("他人のファイルは無視", async(async () => {
|
|
||||||
const file = await uploadUrl(
|
|
||||||
bob,
|
|
||||||
"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
fileIds: [file.id],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("存在しないファイルは無視", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
fileIds: ["000000000000000000000000"],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("不正なファイルIDは無視", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
fileIds: ["kyoppie"],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(res.body.createdNote.fileIds, []);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("返信できる", async(async () => {
|
|
||||||
const bobPost = await post(bob, {
|
|
||||||
text: "foo",
|
|
||||||
});
|
|
||||||
|
|
||||||
const alicePost = {
|
|
||||||
text: "bar",
|
|
||||||
replyId: bobPost.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", alicePost, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
|
||||||
assert.strictEqual(res.body.createdNote.replyId, alicePost.replyId);
|
|
||||||
assert.strictEqual(res.body.createdNote.reply.text, bobPost.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("renoteできる", async(async () => {
|
|
||||||
const bobPost = await post(bob, {
|
|
||||||
text: "test",
|
|
||||||
});
|
|
||||||
|
|
||||||
const alicePost = {
|
|
||||||
renoteId: bobPost.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", alicePost, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
|
||||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("引用renoteできる", async(async () => {
|
|
||||||
const bobPost = await post(bob, {
|
|
||||||
text: "test",
|
|
||||||
});
|
|
||||||
|
|
||||||
const alicePost = {
|
|
||||||
text: "test",
|
|
||||||
renoteId: bobPost.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", alicePost, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.text, alicePost.text);
|
|
||||||
assert.strictEqual(res.body.createdNote.renoteId, alicePost.renoteId);
|
|
||||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("文字数ぎりぎりで怒られない", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "!".repeat(3000),
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("文字数オーバーで怒られる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "!".repeat(3001),
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("存在しないリプライ先で怒られる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "test",
|
|
||||||
replyId: "000000000000000000000000",
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("存在しないrenote対象で怒られる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
renoteId: "000000000000000000000000",
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("不正なリプライ先IDで怒られる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "test",
|
|
||||||
replyId: "foo",
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("不正なrenote対象IDで怒られる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
renoteId: "foo",
|
|
||||||
};
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("存在しないユーザーにメンションできる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "@ghost yo",
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("同じユーザーに複数メンションしても内部的にまとめられる", async(async () => {
|
|
||||||
const post = {
|
|
||||||
text: "@bob @bob @bob yo",
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await request("/notes/create", post, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.text, post.text);
|
|
||||||
|
|
||||||
const noteDoc = await Notes.findOneBy({ id: res.body.createdNote.id });
|
|
||||||
assert.deepStrictEqual(noteDoc.mentions, [bob.id]);
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("notes/create", () => {
|
|
||||||
it("投票を添付できる", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
poll: {
|
|
||||||
choices: ["foo", "bar"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(
|
|
||||||
typeof res.body === "object" && !Array.isArray(res.body),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.body.createdNote.poll != null, true);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("投票の選択肢が無くて怒られる", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
poll: {},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("投票の選択肢が無くて怒られる (空の配列)", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
poll: {
|
|
||||||
choices: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("投票の選択肢が1つで怒られる", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
poll: {
|
|
||||||
choices: ["Strawberry Pasta"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("投票できる", async(async () => {
|
|
||||||
const { body } = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
poll: {
|
|
||||||
choices: ["sakura", "izumi", "ako"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 1,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 204);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("複数投票できない", async(async () => {
|
|
||||||
const { body } = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
poll: {
|
|
||||||
choices: ["sakura", "izumi", "ako"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 0,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 2,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("許可されている場合は複数投票できる", async(async () => {
|
|
||||||
const { body } = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
poll: {
|
|
||||||
choices: ["sakura", "izumi", "ako"],
|
|
||||||
multiple: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 0,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 1,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 2,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 204);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("締め切られている場合は投票できない", async(async () => {
|
|
||||||
const { body } = await request(
|
|
||||||
"/notes/create",
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
poll: {
|
|
||||||
choices: ["sakura", "izumi", "ako"],
|
|
||||||
expiredAfter: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
await new Promise((x) => setTimeout(x, 2));
|
|
||||||
|
|
||||||
const res = await request(
|
|
||||||
"/notes/polls/vote",
|
|
||||||
{
|
|
||||||
noteId: body.createdNote.id,
|
|
||||||
choice: 1,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("notes/delete", () => {
|
|
||||||
it("delete a reply", async(async () => {
|
|
||||||
const mainNoteRes = await api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "main post",
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
const replyOneRes = await api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "reply one",
|
|
||||||
replyId: mainNoteRes.body.createdNote.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
const replyTwoRes = await api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "reply two",
|
|
||||||
replyId: mainNoteRes.body.createdNote.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
const deleteOneRes = await api(
|
|
||||||
"notes/delete",
|
|
||||||
{
|
|
||||||
noteId: replyOneRes.body.createdNote.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(deleteOneRes.status, 204);
|
|
||||||
let mainNote = await Notes.findOneBy({
|
|
||||||
id: mainNoteRes.body.createdNote.id,
|
|
||||||
});
|
|
||||||
assert.strictEqual(mainNote.repliesCount, 1);
|
|
||||||
|
|
||||||
const deleteTwoRes = await api(
|
|
||||||
"notes/delete",
|
|
||||||
{
|
|
||||||
noteId: replyTwoRes.body.createdNote.id,
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(deleteTwoRes.status, 204);
|
|
||||||
mainNote = await Notes.findOneBy({ id: mainNoteRes.body.createdNote.id });
|
|
||||||
assert.strictEqual(mainNote.repliesCount, 0);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,18 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
import { just, nothing } from "../../src/prelude/maybe.js";
|
|
||||||
|
|
||||||
describe("just", () => {
|
|
||||||
it("has a value", () => {
|
|
||||||
assert.deepStrictEqual(just(3).isJust(), true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has the inverse called get", () => {
|
|
||||||
assert.deepStrictEqual(just(3).get(), 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("nothing", () => {
|
|
||||||
it("has no value", () => {
|
|
||||||
assert.deepStrictEqual(nothing().isJust(), false);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
import * as assert from "node:assert";
|
|
||||||
import { query } from "../../src/prelude/url.js";
|
|
||||||
|
|
||||||
describe("url", () => {
|
|
||||||
it("query", () => {
|
|
||||||
const s = query({
|
|
||||||
foo: "ふぅ",
|
|
||||||
bar: "b a r",
|
|
||||||
baz: undefined,
|
|
||||||
});
|
|
||||||
assert.deepStrictEqual(s, "foo=%E3%81%B5%E3%81%85&bar=b%20a%20r");
|
|
||||||
});
|
|
||||||
});
|
|
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 463 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#FF40A4" d="M128 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C164 84 144 76 128 80"/><path fill="#FFBF40" d="M192 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C228 84 208 76 192 80"/><path fill="#408EFF" d="M64 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8C28 172 48 180 64 176s20-24 20-48v-16c0-8 8-16 20.3-8C100 84 80 76 64 80"/></svg>
|
|
Before Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.7 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#FF40A4" d="M128 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C164 84 144 76 128 80"/><path fill="#FFBF40" d="M192 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8 4.3 20 24.3 28 40.3 24s20-24 20-48v-16c0-8 8-16 20.3-8C228 84 208 76 192 80"/><path fill="#408EFF" d="M64 80c-16 4-20 24-20 48v16c0 8-8 16-20.3 8C28 172 48 180 64 176s20-24 20-48v-16c0-8 8-16 20.3-8C100 84 80 76 64 80"/></svg>
|
|
Before Width: | Height: | Size: 504 B |
|
@ -1,766 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import { Following } from "../src/models/entities/following.js";
|
|
||||||
import {
|
|
||||||
api,
|
|
||||||
connectStream,
|
|
||||||
initTestDb,
|
|
||||||
post,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
waitFire,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("Streaming", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
let Followings: any;
|
|
||||||
|
|
||||||
const follow = async (follower: any, followee: any) => {
|
|
||||||
await Followings.save({
|
|
||||||
id: "a",
|
|
||||||
createdAt: new Date(),
|
|
||||||
followerId: follower.id,
|
|
||||||
followeeId: followee.id,
|
|
||||||
followerHost: follower.host,
|
|
||||||
followerInbox: null,
|
|
||||||
followerSharedInbox: null,
|
|
||||||
followeeHost: followee.host,
|
|
||||||
followeeInbox: null,
|
|
||||||
followeeSharedInbox: null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("Streaming", () => {
|
|
||||||
// Local users
|
|
||||||
let ayano: any;
|
|
||||||
let kyoko: any;
|
|
||||||
let chitose: any;
|
|
||||||
|
|
||||||
// Remote users
|
|
||||||
let akari: any;
|
|
||||||
let chinatsu: any;
|
|
||||||
|
|
||||||
let kyokoNote: any;
|
|
||||||
let list: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
const connection = await initTestDb(true);
|
|
||||||
Followings = connection.getRepository(Following);
|
|
||||||
|
|
||||||
ayano = await signup({ username: "ayano" });
|
|
||||||
kyoko = await signup({ username: "kyoko" });
|
|
||||||
chitose = await signup({ username: "chitose" });
|
|
||||||
|
|
||||||
akari = await signup({ username: "akari", host: "example.com" });
|
|
||||||
chinatsu = await signup({ username: "chinatsu", host: "example.com" });
|
|
||||||
|
|
||||||
kyokoNote = await post(kyoko, { text: "foo" });
|
|
||||||
|
|
||||||
// Follow: ayano => kyoko
|
|
||||||
await api("following/create", { userId: kyoko.id }, ayano);
|
|
||||||
|
|
||||||
// Follow: ayano => akari
|
|
||||||
await follow(ayano, akari);
|
|
||||||
|
|
||||||
// List: chitose => ayano, kyoko
|
|
||||||
list = await api(
|
|
||||||
"users/lists/create",
|
|
||||||
{
|
|
||||||
name: "my list",
|
|
||||||
},
|
|
||||||
chitose,
|
|
||||||
).then((x) => x.body);
|
|
||||||
|
|
||||||
await api(
|
|
||||||
"users/lists/push",
|
|
||||||
{
|
|
||||||
listId: list.id,
|
|
||||||
userId: ayano.id,
|
|
||||||
},
|
|
||||||
chitose,
|
|
||||||
);
|
|
||||||
|
|
||||||
await api(
|
|
||||||
"users/lists/push",
|
|
||||||
{
|
|
||||||
listId: list.id,
|
|
||||||
userId: kyoko.id,
|
|
||||||
},
|
|
||||||
chitose,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Events", () => {
|
|
||||||
it("mention event", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
kyoko,
|
|
||||||
"main", // kyoko:main
|
|
||||||
() => post(ayano, { text: "foo @kyoko bar" }), // ayano mention => kyoko
|
|
||||||
(msg) => msg.type === "mention" && msg.body.userId === ayano.id, // wait ayano
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renote event", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
kyoko,
|
|
||||||
"main", // kyoko:main
|
|
||||||
() => post(ayano, { renoteId: kyokoNote.id }), // ayano renote
|
|
||||||
(msg) => msg.type === "renote" && msg.body.renoteId === kyokoNote.id, // wait renote
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Home Timeline", () => {
|
|
||||||
it("自分の投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"homeTimeline", // ayano:Home
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano), // ayano posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.text === "foo",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているユーザーの投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"homeTimeline", // ayano:home
|
|
||||||
() => api("notes/create", { text: "foo" }, kyoko), // kyoko posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
kyoko,
|
|
||||||
"homeTimeline", // kyoko:home
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano), // ayano posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === ayano.id, // wait ayano
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているユーザーのダイレクト投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"homeTimeline", // ayano:home
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [ayano.id],
|
|
||||||
},
|
|
||||||
kyoko,
|
|
||||||
), // kyoko dm => ayano
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているユーザーでも自分が指定されていないダイレクト投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"homeTimeline", // ayano:home
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [chitose.id],
|
|
||||||
},
|
|
||||||
kyoko,
|
|
||||||
), // kyoko dm => chitose
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
}); // Home
|
|
||||||
|
|
||||||
describe("Local Timeline", () => {
|
|
||||||
it("自分の投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano), // ayano posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.text === "foo",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーの投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, chitose), // chitose posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("リモートユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしてたとしてもリモートユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, akari), // akari posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ホーム指定の投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko home posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているローカルユーザーのダイレクト投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [ayano.id],
|
|
||||||
},
|
|
||||||
kyoko,
|
|
||||||
), // kyoko DM => ayano
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"localTimeline", // ayano:Local
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{ text: "foo", visibility: "followers" },
|
|
||||||
chitose,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Recommended Timeline", () => {
|
|
||||||
it("自分の投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano), // ayano posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.text === "foo",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーの投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, chitose), // chitose posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("リモートユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしてたとしてもリモートユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo" }, akari), // akari posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ホーム指定の投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko home posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているローカルユーザーのダイレクト投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [ayano.id],
|
|
||||||
},
|
|
||||||
kyoko,
|
|
||||||
), // kyoko DM => ayano
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"recommendedTimeline", // ayano:Local
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{ text: "foo", visibility: "followers" },
|
|
||||||
chitose,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Hybrid Timeline", () => {
|
|
||||||
it("自分の投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano), // ayano posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.text === "foo",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーの投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() => api("notes/create", { text: "foo" }, chitose), // chitose posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているリモートユーザーの投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() => api("notes/create", { text: "foo" }, akari), // akari posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === akari.id, // wait akari
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないリモートユーザーの投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているユーザーのダイレクト投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [ayano.id],
|
|
||||||
},
|
|
||||||
kyoko,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしているユーザーのホーム投稿が流れる", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() => api("notes/create", { text: "foo", visibility: "home" }, kyoko),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーのホーム投稿は流れない", async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() =>
|
|
||||||
api("notes/create", { text: "foo", visibility: "home" }, chitose),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないローカルユーザーのフォロワー宛て投稿は流れない", () =>
|
|
||||||
async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"hybridTimeline", // ayano:Hybrid
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{ text: "foo", visibility: "followers" },
|
|
||||||
chitose,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Global Timeline", () => {
|
|
||||||
it("フォローしていないローカルユーザーの投稿が流れる", () => async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"globalTimeline", // ayano:Global
|
|
||||||
() => api("notes/create", { text: "foo" }, chitose), // chitose posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chitose.id, // wait chitose
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("フォローしていないリモートユーザーの投稿が流れる", () => async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"globalTimeline", // ayano:Global
|
|
||||||
() => api("notes/create", { text: "foo" }, chinatsu), // chinatsu posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chinatsu.id, // wait chinatsu
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ホーム投稿は流れない", () => async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
ayano,
|
|
||||||
"globalTimeline", // ayano:Global
|
|
||||||
() => api("notes/create", { text: "foo", visibility: "home" }, kyoko), // kyoko posts
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id, // wait kyoko
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("UserList Timeline", () => {
|
|
||||||
it("リストに入れているユーザーの投稿が流れる", () => async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
chitose,
|
|
||||||
"userList",
|
|
||||||
() => api("notes/create", { text: "foo" }, ayano),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === ayano.id,
|
|
||||||
{ listId: list.id },
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("リストに入れていないユーザーの投稿は流れない", () => async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
chitose,
|
|
||||||
"userList",
|
|
||||||
() => api("notes/create", { text: "foo" }, chinatsu),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === chinatsu.id,
|
|
||||||
{ listId: list.id },
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// #4471
|
|
||||||
it("リストに入れているユーザーのダイレクト投稿が流れる", () =>
|
|
||||||
async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
chitose,
|
|
||||||
"userList",
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{
|
|
||||||
text: "foo",
|
|
||||||
visibility: "specified",
|
|
||||||
visibleUserIds: [chitose.id],
|
|
||||||
},
|
|
||||||
ayano,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === ayano.id,
|
|
||||||
{ listId: list.id },
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// #4335
|
|
||||||
it("リストに入れているがフォローはしてないユーザーのフォロワー宛て投稿は流れない", () =>
|
|
||||||
async () => {
|
|
||||||
const fired = await waitFire(
|
|
||||||
chitose,
|
|
||||||
"userList",
|
|
||||||
() =>
|
|
||||||
api(
|
|
||||||
"notes/create",
|
|
||||||
{ text: "foo", visibility: "followers" },
|
|
||||||
kyoko,
|
|
||||||
),
|
|
||||||
(msg) => msg.type === "note" && msg.body.userId === kyoko.id,
|
|
||||||
{ listId: list.id },
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Hashtag Timeline", () => {
|
|
||||||
it("指定したハッシュタグの投稿が流れる", () =>
|
|
||||||
new Promise<void>(async (done) => {
|
|
||||||
const ws = await connectStream(
|
|
||||||
chitose,
|
|
||||||
"hashtag",
|
|
||||||
({ type, body }) => {
|
|
||||||
if (type == "note") {
|
|
||||||
assert.deepStrictEqual(body.text, "#foo");
|
|
||||||
ws.close();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: [["foo"]],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo",
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("指定したハッシュタグの投稿が流れる (AND)", () =>
|
|
||||||
new Promise<void>(async (done) => {
|
|
||||||
let fooCount = 0;
|
|
||||||
let barCount = 0;
|
|
||||||
let fooBarCount = 0;
|
|
||||||
|
|
||||||
const ws = await connectStream(
|
|
||||||
chitose,
|
|
||||||
"hashtag",
|
|
||||||
({ type, body }) => {
|
|
||||||
if (type == "note") {
|
|
||||||
if (body.text === "#foo") fooCount++;
|
|
||||||
if (body.text === "#bar") barCount++;
|
|
||||||
if (body.text === "#foo #bar") fooBarCount++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: [["foo", "bar"]],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo #bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
assert.strictEqual(fooCount, 0);
|
|
||||||
assert.strictEqual(barCount, 0);
|
|
||||||
assert.strictEqual(fooBarCount, 1);
|
|
||||||
ws.close();
|
|
||||||
done();
|
|
||||||
}, 3000);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("指定したハッシュタグの投稿が流れる (OR)", () =>
|
|
||||||
new Promise<void>(async (done) => {
|
|
||||||
let fooCount = 0;
|
|
||||||
let barCount = 0;
|
|
||||||
let fooBarCount = 0;
|
|
||||||
let piyoCount = 0;
|
|
||||||
|
|
||||||
const ws = await connectStream(
|
|
||||||
chitose,
|
|
||||||
"hashtag",
|
|
||||||
({ type, body }) => {
|
|
||||||
if (type == "note") {
|
|
||||||
if (body.text === "#foo") fooCount++;
|
|
||||||
if (body.text === "#bar") barCount++;
|
|
||||||
if (body.text === "#foo #bar") fooBarCount++;
|
|
||||||
if (body.text === "#piyo") piyoCount++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: [["foo"], ["bar"]],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo #bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#piyo",
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
assert.strictEqual(fooCount, 1);
|
|
||||||
assert.strictEqual(barCount, 1);
|
|
||||||
assert.strictEqual(fooBarCount, 1);
|
|
||||||
assert.strictEqual(piyoCount, 0);
|
|
||||||
ws.close();
|
|
||||||
done();
|
|
||||||
}, 3000);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("指定したハッシュタグの投稿が流れる (AND + OR)", () =>
|
|
||||||
new Promise<void>(async (done) => {
|
|
||||||
let fooCount = 0;
|
|
||||||
let barCount = 0;
|
|
||||||
let fooBarCount = 0;
|
|
||||||
let piyoCount = 0;
|
|
||||||
let waaaCount = 0;
|
|
||||||
|
|
||||||
const ws = await connectStream(
|
|
||||||
chitose,
|
|
||||||
"hashtag",
|
|
||||||
({ type, body }) => {
|
|
||||||
if (type == "note") {
|
|
||||||
if (body.text === "#foo") fooCount++;
|
|
||||||
if (body.text === "#bar") barCount++;
|
|
||||||
if (body.text === "#foo #bar") fooBarCount++;
|
|
||||||
if (body.text === "#piyo") piyoCount++;
|
|
||||||
if (body.text === "#waaa") waaaCount++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
q: [["foo", "bar"], ["piyo"]],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#foo #bar",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#piyo",
|
|
||||||
});
|
|
||||||
|
|
||||||
post(chitose, {
|
|
||||||
text: "#waaa",
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
assert.strictEqual(fooCount, 0);
|
|
||||||
assert.strictEqual(barCount, 0);
|
|
||||||
assert.strictEqual(fooBarCount, 1);
|
|
||||||
assert.strictEqual(piyoCount, 1);
|
|
||||||
assert.strictEqual(waaaCount, 0);
|
|
||||||
ws.close();
|
|
||||||
done();
|
|
||||||
}, 3000);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,161 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
connectStream,
|
|
||||||
post,
|
|
||||||
react,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("Note thread mute", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
let alice: any;
|
|
||||||
let bob: any;
|
|
||||||
let carol: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
bob = await signup({ username: "bob" });
|
|
||||||
carol = await signup({ username: "carol" });
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("notes/mentions にミュートしているスレッドの投稿が含まれない", async(async () => {
|
|
||||||
const bobNote = await post(bob, { text: "@alice @carol root note" });
|
|
||||||
const aliceReply = await post(alice, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @carol child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
|
|
||||||
|
|
||||||
const carolReply = await post(carol, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @alice child note",
|
|
||||||
});
|
|
||||||
const carolReplyWithoutMention = await post(carol, {
|
|
||||||
replyId: aliceReply.id,
|
|
||||||
text: "child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request("/notes/mentions", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === bobNote.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolReply.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === carolReplyWithoutMention.id),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない", async(async () => {
|
|
||||||
// 状態リセット
|
|
||||||
await request("/i/read-all-unread-notes", {}, alice);
|
|
||||||
|
|
||||||
const bobNote = await post(bob, { text: "@alice @carol root note" });
|
|
||||||
|
|
||||||
await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
|
|
||||||
|
|
||||||
const carolReply = await post(carol, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @alice child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request("/i", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(res.body.hasUnreadMentions, false);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない", () =>
|
|
||||||
new Promise(async (done) => {
|
|
||||||
// 状態リセット
|
|
||||||
await request("/i/read-all-unread-notes", {}, alice);
|
|
||||||
|
|
||||||
const bobNote = await post(bob, { text: "@alice @carol root note" });
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/notes/thread-muting/create",
|
|
||||||
{ noteId: bobNote.id },
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fired = false;
|
|
||||||
|
|
||||||
const ws = await connectStream(alice, "main", async ({ type, body }) => {
|
|
||||||
if (type === "unreadMention") {
|
|
||||||
if (body === bobNote.id) return;
|
|
||||||
fired = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const carolReply = await post(carol, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @alice child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
assert.strictEqual(fired, false);
|
|
||||||
ws.close();
|
|
||||||
done();
|
|
||||||
}, 5000);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("i/notifications にミュートしているスレッドの通知が含まれない", async(async () => {
|
|
||||||
const bobNote = await post(bob, { text: "@alice @carol root note" });
|
|
||||||
const aliceReply = await post(alice, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @carol child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
await request("/notes/thread-muting/create", { noteId: bobNote.id }, alice);
|
|
||||||
|
|
||||||
const carolReply = await post(carol, {
|
|
||||||
replyId: bobNote.id,
|
|
||||||
text: "@bob @alice child note",
|
|
||||||
});
|
|
||||||
const carolReplyWithoutMention = await post(carol, {
|
|
||||||
replyId: aliceReply.id,
|
|
||||||
text: "child note",
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await request("/i/notifications", {}, alice);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some(
|
|
||||||
(notification: any) => notification.note.id === carolReply.id,
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some(
|
|
||||||
(notification: any) =>
|
|
||||||
notification.note.id === carolReplyWithoutMention.id,
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい
|
|
||||||
}));
|
|
||||||
});
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"noEmitOnError": false,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noImplicitReturns": true,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"declaration": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"target": "es2021",
|
|
||||||
"module": "es2020",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"removeComments": false,
|
|
||||||
"noLib": false,
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"strictPropertyInitialization": false,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"baseUrl": "./",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["../src/*"]
|
|
||||||
},
|
|
||||||
"typeRoots": ["../node_modules/@types", "../src/@types"],
|
|
||||||
"lib": ["esnext"]
|
|
||||||
},
|
|
||||||
"compileOnSave": false,
|
|
||||||
"include": ["./**/*.ts"]
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
process.env.NODE_ENV = "test";
|
|
||||||
|
|
||||||
import * as assert from "node:assert";
|
|
||||||
import type * as childProcess from "node:child_process";
|
|
||||||
import {
|
|
||||||
async,
|
|
||||||
post,
|
|
||||||
request,
|
|
||||||
shutdownServer,
|
|
||||||
signup,
|
|
||||||
startServer,
|
|
||||||
uploadUrl,
|
|
||||||
} from "./utils.js";
|
|
||||||
|
|
||||||
describe("users/notes", () => {
|
|
||||||
let p: childProcess.ChildProcess;
|
|
||||||
|
|
||||||
let alice: any;
|
|
||||||
let jpgNote: any;
|
|
||||||
let pngNote: any;
|
|
||||||
let jpgPngNote: any;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
p = await startServer();
|
|
||||||
alice = await signup({ username: "alice" });
|
|
||||||
const jpg = await uploadUrl(
|
|
||||||
alice,
|
|
||||||
"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg",
|
|
||||||
);
|
|
||||||
const png = await uploadUrl(
|
|
||||||
alice,
|
|
||||||
"https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png",
|
|
||||||
);
|
|
||||||
jpgNote = await post(alice, {
|
|
||||||
fileIds: [jpg.id],
|
|
||||||
});
|
|
||||||
pngNote = await post(alice, {
|
|
||||||
fileIds: [png.id],
|
|
||||||
});
|
|
||||||
jpgPngNote = await post(alice, {
|
|
||||||
fileIds: [jpg.id, png.id],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await shutdownServer(p);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ファイルタイプ指定 (jpg)", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/users/notes",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
fileType: ["image/jpeg"],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.length, 2);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === jpgNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === jpgPngNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("ファイルタイプ指定 (jpg or png)", async(async () => {
|
|
||||||
const res = await request(
|
|
||||||
"/users/notes",
|
|
||||||
{
|
|
||||||
userId: alice.id,
|
|
||||||
fileType: ["image/jpeg", "image/png"],
|
|
||||||
},
|
|
||||||
alice,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(res.status, 200);
|
|
||||||
assert.strictEqual(Array.isArray(res.body), true);
|
|
||||||
assert.strictEqual(res.body.length, 3);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === jpgNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === pngNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
assert.strictEqual(
|
|
||||||
res.body.some((note: any) => note.id === jpgPngNote.id),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
});
|
|
|
@ -1,403 +0,0 @@
|
||||||
import { SIGKILL } from "constants";
|
|
||||||
import * as childProcess from "node:child_process";
|
|
||||||
import * as fs from "node:fs";
|
|
||||||
import * as http from "node:http";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { dirname } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import type { Entities, endpoints } from "firefish-js";
|
|
||||||
import FormData from "form-data";
|
|
||||||
import got from "got";
|
|
||||||
import fetch from "node-fetch";
|
|
||||||
import { DataSource } from "typeorm";
|
|
||||||
import WebSocket from "ws";
|
|
||||||
import loadConfig from "../src/config/load.js";
|
|
||||||
import { entities } from "../src/db/postgre.js";
|
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
|
||||||
const _dirname = dirname(_filename);
|
|
||||||
|
|
||||||
const config = loadConfig();
|
|
||||||
export const port = config.port;
|
|
||||||
|
|
||||||
export const async = (fn: Function) => (done: Function) => {
|
|
||||||
fn().then(
|
|
||||||
() => {
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
(err: Error) => {
|
|
||||||
done(err);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const api = async (endpoint: string, params: any, me?: any) => {
|
|
||||||
endpoint = endpoint.replace(/^\//, "");
|
|
||||||
|
|
||||||
const auth = me
|
|
||||||
? {
|
|
||||||
i: me.token,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const res = await got<string>(`http://localhost:${port}/api/${endpoint}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(Object.assign(auth, params)),
|
|
||||||
retry: {
|
|
||||||
limit: 0,
|
|
||||||
},
|
|
||||||
hooks: {
|
|
||||||
beforeError: [
|
|
||||||
(error) => {
|
|
||||||
const { response } = error;
|
|
||||||
if (response && response.body) console.warn(response.body);
|
|
||||||
return error;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const status = res.statusCode;
|
|
||||||
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
body,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const request = async (
|
|
||||||
endpoint: string,
|
|
||||||
params: any,
|
|
||||||
me?: any,
|
|
||||||
): Promise<{ body: any; status: number }> => {
|
|
||||||
const auth = me
|
|
||||||
? {
|
|
||||||
i: me.token,
|
|
||||||
}
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(Object.assign(auth, params)),
|
|
||||||
});
|
|
||||||
|
|
||||||
const status = res.status;
|
|
||||||
const body = res.status !== 204 ? await res.json().catch() : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
body,
|
|
||||||
status,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const signup = async (params?: any): Promise<any> => {
|
|
||||||
const q = Object.assign(
|
|
||||||
{
|
|
||||||
username: "test",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await api("signup", q);
|
|
||||||
|
|
||||||
return res.body;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const post = async (
|
|
||||||
user: any,
|
|
||||||
params?: Endpoints["notes/create"]["req"],
|
|
||||||
): Promise<entities.Note> => {
|
|
||||||
const q = Object.assign(
|
|
||||||
{
|
|
||||||
text: "test",
|
|
||||||
},
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await api("notes/create", q, user);
|
|
||||||
|
|
||||||
return res.body ? res.body.createdNote : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const react = async (
|
|
||||||
user: any,
|
|
||||||
note: any,
|
|
||||||
reaction: string,
|
|
||||||
): Promise<any> => {
|
|
||||||
await api(
|
|
||||||
"notes/reactions/create",
|
|
||||||
{
|
|
||||||
noteId: note.id,
|
|
||||||
reaction: reaction,
|
|
||||||
},
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload file
|
|
||||||
* @param user User
|
|
||||||
* @param _path Optional, absolute path or relative from ./resources/
|
|
||||||
*/
|
|
||||||
export const uploadFile = async (user: any, _path?: string): Promise<any> => {
|
|
||||||
const absPath =
|
|
||||||
_path == null
|
|
||||||
? `${_dirname}/resources/Lenna.jpg`
|
|
||||||
: path.isAbsolute(_path)
|
|
||||||
? _path
|
|
||||||
: `${_dirname}/resources/${_path}`;
|
|
||||||
|
|
||||||
const formData = new FormData() as any;
|
|
||||||
formData.append("i", user.token);
|
|
||||||
formData.append("file", fs.createReadStream(absPath));
|
|
||||||
formData.append("force", "true");
|
|
||||||
|
|
||||||
const res = await got<string>(
|
|
||||||
`http://localhost:${port}/api/drive/files/create`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
retry: {
|
|
||||||
limit: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null;
|
|
||||||
|
|
||||||
return body;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const uploadUrl = async (user: any, url: string) => {
|
|
||||||
let file: any;
|
|
||||||
|
|
||||||
const ws = await connectStream(user, "main", (msg) => {
|
|
||||||
if (msg.type === "driveFileCreated") {
|
|
||||||
file = msg.body;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await api(
|
|
||||||
"drive/files/upload-from-url",
|
|
||||||
{
|
|
||||||
url,
|
|
||||||
force: true,
|
|
||||||
},
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
|
|
||||||
await sleep(5000);
|
|
||||||
ws.close();
|
|
||||||
|
|
||||||
return file;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function connectStream(
|
|
||||||
user: any,
|
|
||||||
channel: string,
|
|
||||||
listener: (message: Record<string, any>) => any,
|
|
||||||
params?: any,
|
|
||||||
): Promise<WebSocket> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
const ws = new WebSocket(
|
|
||||||
`ws://localhost:${port}/streaming?i=${user.token}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
ws.on("open", () => {
|
|
||||||
ws.on("message", (data) => {
|
|
||||||
const msg = JSON.parse(data.toString());
|
|
||||||
if (msg.type === "channel" && msg.body.id === "a") {
|
|
||||||
listener(msg.body);
|
|
||||||
} else if (msg.type === "connected" && msg.body.id === "a") {
|
|
||||||
res(ws);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "connect",
|
|
||||||
body: {
|
|
||||||
channel: channel,
|
|
||||||
id: "a",
|
|
||||||
pong: true,
|
|
||||||
params: params,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const waitFire = async (
|
|
||||||
user: any,
|
|
||||||
channel: string,
|
|
||||||
trgr: () => any,
|
|
||||||
cond: (msg: Record<string, any>) => boolean,
|
|
||||||
params?: any,
|
|
||||||
) => {
|
|
||||||
return new Promise<boolean>(async (res, rej) => {
|
|
||||||
let timer: NodeJS.Timeout;
|
|
||||||
|
|
||||||
let ws: WebSocket;
|
|
||||||
try {
|
|
||||||
ws = await connectStream(
|
|
||||||
user,
|
|
||||||
channel,
|
|
||||||
(msg) => {
|
|
||||||
if (cond(msg)) {
|
|
||||||
ws.close();
|
|
||||||
if (timer) clearTimeout(timer);
|
|
||||||
res(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
params,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
rej(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ws!) return;
|
|
||||||
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
ws.close();
|
|
||||||
res(false);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await trgr();
|
|
||||||
} catch (e) {
|
|
||||||
ws.close();
|
|
||||||
if (timer) clearTimeout(timer);
|
|
||||||
rej(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const simpleGet = async (
|
|
||||||
path: string,
|
|
||||||
accept = "*/*",
|
|
||||||
): Promise<{ status?: number; type?: string; location?: string }> => {
|
|
||||||
// node-fetchだと3xxを取れない
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const req = http.request(
|
|
||||||
`http://localhost:${port}${path}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: accept,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(res) => {
|
|
||||||
if (res.statusCode! >= 400) {
|
|
||||||
reject(res);
|
|
||||||
} else {
|
|
||||||
resolve({
|
|
||||||
status: res.statusCode,
|
|
||||||
type: res.headers["content-type"],
|
|
||||||
location: res.headers.location,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export function launchServer(
|
|
||||||
callbackSpawnedProcess: (p: childProcess.ChildProcess) => void,
|
|
||||||
moreProcess: () => Promise<void> = async () => {},
|
|
||||||
) {
|
|
||||||
return (done: (err?: Error) => any) => {
|
|
||||||
const p = childProcess.spawn("node", [_dirname + "/../index.js"], {
|
|
||||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
||||||
env: { NODE_ENV: "test", PATH: process.env.PATH },
|
|
||||||
});
|
|
||||||
callbackSpawnedProcess(p);
|
|
||||||
p.on("message", (message) => {
|
|
||||||
if (message === "ok")
|
|
||||||
moreProcess()
|
|
||||||
.then(() => done())
|
|
||||||
.catch((e) => done(e));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function initTestDb(justBorrow = false, initEntities?: any[]) {
|
|
||||||
if (process.env.NODE_ENV !== "test") throw "NODE_ENV is not a test";
|
|
||||||
|
|
||||||
const db = new DataSource({
|
|
||||||
type: "postgres",
|
|
||||||
host: config.db.host,
|
|
||||||
port: config.db.port,
|
|
||||||
username: config.db.user,
|
|
||||||
password: config.db.pass,
|
|
||||||
database: config.db.db,
|
|
||||||
synchronize: true && !justBorrow,
|
|
||||||
dropSchema: true && !justBorrow,
|
|
||||||
entities: initEntities || entities,
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.initialize();
|
|
||||||
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function startServer(
|
|
||||||
timeout = 60 * 1000,
|
|
||||||
): Promise<childProcess.ChildProcess> {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
const t = setTimeout(() => {
|
|
||||||
p.kill(SIGKILL);
|
|
||||||
rej("timeout to start");
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
const p = childProcess.spawn("node", [_dirname + "/../built/index.js"], {
|
|
||||||
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
||||||
env: { NODE_ENV: "test", PATH: process.env.PATH },
|
|
||||||
});
|
|
||||||
|
|
||||||
p.on("error", (e) => rej(e));
|
|
||||||
|
|
||||||
p.on("message", (message) => {
|
|
||||||
if (message === "ok") {
|
|
||||||
clearTimeout(t);
|
|
||||||
res(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shutdownServer(
|
|
||||||
p: childProcess.ChildProcess,
|
|
||||||
timeout = 20 * 1000,
|
|
||||||
) {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
const t = setTimeout(() => {
|
|
||||||
p.kill(SIGKILL);
|
|
||||||
res("force exit");
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
p.once("exit", () => {
|
|
||||||
clearTimeout(t);
|
|
||||||
res("exited");
|
|
||||||
});
|
|
||||||
|
|
||||||
p.kill();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sleep(msec: number) {
|
|
||||||
return new Promise<void>((res) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
res();
|
|
||||||
}, msec);
|
|
||||||
});
|
|
||||||
}
|
|
216
pnpm-lock.yaml
|
@ -129,9 +129,6 @@ importers:
|
||||||
deep-email-validator:
|
deep-email-validator:
|
||||||
specifier: 0.1.21
|
specifier: 0.1.21
|
||||||
version: 0.1.21
|
version: 0.1.21
|
||||||
deepl-node:
|
|
||||||
specifier: 1.13.0
|
|
||||||
version: 1.13.0
|
|
||||||
escape-regexp:
|
escape-regexp:
|
||||||
specifier: 0.0.1
|
specifier: 0.0.1
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
@ -231,9 +228,6 @@ importers:
|
||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: 6.9.14
|
specifier: 6.9.14
|
||||||
version: 6.9.14
|
version: 6.9.14
|
||||||
opencc-js:
|
|
||||||
specifier: 1.0.5
|
|
||||||
version: 1.0.5
|
|
||||||
otpauth:
|
otpauth:
|
||||||
specifier: 9.3.1
|
specifier: 9.3.1
|
||||||
version: 9.3.1
|
version: 9.3.1
|
||||||
|
@ -394,9 +388,6 @@ importers:
|
||||||
'@types/koa__router':
|
'@types/koa__router':
|
||||||
specifier: 12.0.4
|
specifier: 12.0.4
|
||||||
version: 12.0.4
|
version: 12.0.4
|
||||||
'@types/mocha':
|
|
||||||
specifier: 10.0.7
|
|
||||||
version: 10.0.7
|
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 20.14.11
|
specifier: 20.14.11
|
||||||
version: 20.14.11
|
version: 20.14.11
|
||||||
|
@ -469,9 +460,6 @@ importers:
|
||||||
cross-env:
|
cross-env:
|
||||||
specifier: 7.0.3
|
specifier: 7.0.3
|
||||||
version: 7.0.3
|
version: 7.0.3
|
||||||
mocha:
|
|
||||||
specifier: 10.6.0
|
|
||||||
version: 10.6.0
|
|
||||||
pug:
|
pug:
|
||||||
specifier: 3.0.3
|
specifier: 3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
|
@ -1001,13 +989,11 @@ packages:
|
||||||
'@biomejs/cli-darwin-arm64@1.8.3':
|
'@biomejs/cli-darwin-arm64@1.8.3':
|
||||||
resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==}
|
resolution: {integrity: sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@biomejs/cli-darwin-x64@1.8.3':
|
'@biomejs/cli-darwin-x64@1.8.3':
|
||||||
resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==}
|
resolution: {integrity: sha512-UeW44L/AtbmOF7KXLCoM+9PSgPo0IDcyEUfIoOXYeANaNXXf9mLUwV1GeF2OWjyic5zj6CnAJ9uzk2LT3v/wAw==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@biomejs/cli-linux-arm64-musl@1.8.3':
|
'@biomejs/cli-linux-arm64-musl@1.8.3':
|
||||||
|
@ -1019,7 +1005,6 @@ packages:
|
||||||
'@biomejs/cli-linux-arm64@1.8.3':
|
'@biomejs/cli-linux-arm64@1.8.3':
|
||||||
resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==}
|
resolution: {integrity: sha512-fed2ji8s+I/m8upWpTJGanqiJ0rnlHOK3DdxsyVLZQ8ClY6qLuPc9uehCREBifRJLl/iJyQpHIRufLDeotsPtw==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@biomejs/cli-linux-x64-musl@1.8.3':
|
'@biomejs/cli-linux-x64-musl@1.8.3':
|
||||||
|
@ -1031,7 +1016,6 @@ packages:
|
||||||
'@biomejs/cli-linux-x64@1.8.3':
|
'@biomejs/cli-linux-x64@1.8.3':
|
||||||
resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==}
|
resolution: {integrity: sha512-I8G2QmuE1teISyT8ie1HXsjFRz9L1m5n83U1O6m30Kw+kPMPSKjag6QGUn+sXT8V+XWIZxFFBoTDEDZW2KPDDw==}
|
||||||
engines: {node: '>=14.21.3'}
|
engines: {node: '>=14.21.3'}
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@biomejs/cli-win32-arm64@1.8.3':
|
'@biomejs/cli-win32-arm64@1.8.3':
|
||||||
|
@ -2500,9 +2484,6 @@ packages:
|
||||||
'@types/minimist@1.2.5':
|
'@types/minimist@1.2.5':
|
||||||
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
|
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
|
||||||
|
|
||||||
'@types/mocha@10.0.7':
|
|
||||||
resolution: {integrity: sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==}
|
|
||||||
|
|
||||||
'@types/mute-stream@0.0.4':
|
'@types/mute-stream@0.0.4':
|
||||||
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
|
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
|
||||||
|
|
||||||
|
@ -2780,10 +2761,6 @@ packages:
|
||||||
ajv@8.17.1:
|
ajv@8.17.1:
|
||||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||||
|
|
||||||
ansi-colors@4.1.3:
|
|
||||||
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
ansi-escapes@4.3.2:
|
ansi-escapes@4.3.2:
|
||||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -2979,9 +2956,6 @@ packages:
|
||||||
broadcast-channel@7.0.0:
|
broadcast-channel@7.0.0:
|
||||||
resolution: {integrity: sha512-a2tW0Ia1pajcPBOGUF2jXlDnvE9d5/dg6BG9h60OmRUcZVr/veUrU8vEQFwwQIhwG3KVzYwSk3v2nRRGFgQDXQ==}
|
resolution: {integrity: sha512-a2tW0Ia1pajcPBOGUF2jXlDnvE9d5/dg6BG9h60OmRUcZVr/veUrU8vEQFwwQIhwG3KVzYwSk3v2nRRGFgQDXQ==}
|
||||||
|
|
||||||
browser-stdout@1.3.1:
|
|
||||||
resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
|
|
||||||
|
|
||||||
browserify-zlib@0.1.4:
|
browserify-zlib@0.1.4:
|
||||||
resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==}
|
resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==}
|
||||||
|
|
||||||
|
@ -3608,10 +3582,6 @@ packages:
|
||||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
decamelize@4.0.0:
|
|
||||||
resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
decimal.js@10.4.3:
|
decimal.js@10.4.3:
|
||||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||||
|
|
||||||
|
@ -3653,10 +3623,6 @@ packages:
|
||||||
deep-equal@1.0.1:
|
deep-equal@1.0.1:
|
||||||
resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
|
resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==}
|
||||||
|
|
||||||
deepl-node@1.13.0:
|
|
||||||
resolution: {integrity: sha512-pm8Al5B+/fRHiIKoreoSmv2RlXidF18+CznhtLILiYcj3EbxZpIhxWO8cgXCCsCTrUDMAbScIl8CuH3AqLPpGg==}
|
|
||||||
engines: {node: '>=12.0'}
|
|
||||||
|
|
||||||
deepmerge@4.3.1:
|
deepmerge@4.3.1:
|
||||||
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
@ -3711,10 +3677,6 @@ packages:
|
||||||
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
|
||||||
engines: {node: '>=0.3.1'}
|
engines: {node: '>=0.3.1'}
|
||||||
|
|
||||||
diff@5.2.0:
|
|
||||||
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
|
||||||
engines: {node: '>=0.3.1'}
|
|
||||||
|
|
||||||
dijkstrajs@1.0.3:
|
dijkstrajs@1.0.3:
|
||||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
|
@ -4032,17 +3994,9 @@ packages:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
find-up@5.0.0:
|
|
||||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
fix-esm@1.0.1:
|
fix-esm@1.0.1:
|
||||||
resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==}
|
resolution: {integrity: sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==}
|
||||||
|
|
||||||
flat@5.0.2:
|
|
||||||
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
fluent-ffmpeg@2.1.3:
|
fluent-ffmpeg@2.1.3:
|
||||||
resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
|
resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
@ -4076,10 +4030,6 @@ packages:
|
||||||
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
|
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
|
||||||
engines: {node: '>= 18'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
form-data@3.0.1:
|
|
||||||
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
form-data@4.0.0:
|
form-data@4.0.0:
|
||||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -4179,11 +4129,6 @@ packages:
|
||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
deprecated: Glob versions prior to v9 are no longer supported
|
||||||
|
|
||||||
glob@8.1.0:
|
|
||||||
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
deprecated: Glob versions prior to v9 are no longer supported
|
|
||||||
|
|
||||||
globals@11.12.0:
|
globals@11.12.0:
|
||||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
@ -4498,10 +4443,6 @@ packages:
|
||||||
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
|
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-plain-obj@2.1.0:
|
|
||||||
resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-plain-obj@4.1.0:
|
is-plain-obj@4.1.0:
|
||||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -4964,10 +4905,6 @@ packages:
|
||||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
locate-path@6.0.0:
|
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
lodash-es@4.17.21:
|
lodash-es@4.17.21:
|
||||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||||
|
|
||||||
|
@ -5023,10 +4960,6 @@ packages:
|
||||||
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
|
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
loglevel@1.9.1:
|
|
||||||
resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==}
|
|
||||||
engines: {node: '>= 0.6.0'}
|
|
||||||
|
|
||||||
long@5.2.3:
|
long@5.2.3:
|
||||||
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
|
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
|
||||||
|
|
||||||
|
@ -5172,11 +5105,6 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
mocha@10.6.0:
|
|
||||||
resolution: {integrity: sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==}
|
|
||||||
engines: {node: '>= 14.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
mock-socket@9.3.1:
|
mock-socket@9.3.1:
|
||||||
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
|
resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
@ -5354,9 +5282,6 @@ packages:
|
||||||
only@0.0.2:
|
only@0.0.2:
|
||||||
resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
|
resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==}
|
||||||
|
|
||||||
opencc-js@1.0.5:
|
|
||||||
resolution: {integrity: sha512-LD+1SoNnZdlRwtYTjnQdFrSVCAaYpuDqL5CkmOaHOkKoKh7mFxUicLTRVNLU5C+Jmi1vXQ3QL4jWdgSaa4sKjg==}
|
|
||||||
|
|
||||||
opencollective-postinstall@2.0.3:
|
opencollective-postinstall@2.0.3:
|
||||||
resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
|
resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -5396,10 +5321,6 @@ packages:
|
||||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-locate@5.0.0:
|
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
p-queue@6.6.2:
|
p-queue@6.6.2:
|
||||||
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
|
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -6835,9 +6756,6 @@ packages:
|
||||||
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
||||||
workerpool@6.5.1:
|
|
||||||
resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
|
|
||||||
|
|
||||||
wrap-ansi@6.2.0:
|
wrap-ansi@6.2.0:
|
||||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -6931,10 +6849,6 @@ packages:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
yargs-unparser@2.0.0:
|
|
||||||
resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
yargs@15.4.1:
|
yargs@15.4.1:
|
||||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -7007,7 +6921,7 @@ snapshots:
|
||||||
'@babel/traverse': 7.24.8
|
'@babel/traverse': 7.24.8
|
||||||
'@babel/types': 7.24.9
|
'@babel/types': 7.24.9
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
|
@ -7205,7 +7119,7 @@ snapshots:
|
||||||
'@babel/helper-split-export-declaration': 7.24.7
|
'@babel/helper-split-export-declaration': 7.24.7
|
||||||
'@babel/parser': 7.24.8
|
'@babel/parser': 7.24.8
|
||||||
'@babel/types': 7.24.9
|
'@babel/types': 7.24.9
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
globals: 11.12.0
|
globals: 11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -7905,7 +7819,7 @@ snapshots:
|
||||||
|
|
||||||
'@koa/router@12.0.1':
|
'@koa/router@12.0.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
http-errors: 2.0.0
|
http-errors: 2.0.0
|
||||||
koa-compose: 4.1.0
|
koa-compose: 4.1.0
|
||||||
methods: 1.1.2
|
methods: 1.1.2
|
||||||
|
@ -7925,7 +7839,7 @@ snapshots:
|
||||||
'@ladjs/koa-views@9.0.0(@babel/core@7.24.9)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
|
'@ladjs/koa-views@9.0.0(@babel/core@7.24.9)(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ladjs/consolidate': 1.0.4(@babel/core@7.24.9)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
|
'@ladjs/consolidate': 1.0.4(@babel/core@7.24.9)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
get-paths: 0.0.7
|
get-paths: 0.0.7
|
||||||
koa-send: 5.0.1
|
koa-send: 5.0.1
|
||||||
mz: 2.7.0
|
mz: 2.7.0
|
||||||
|
@ -8011,7 +7925,7 @@ snapshots:
|
||||||
'@octokit/rest': 21.0.1
|
'@octokit/rest': 21.0.1
|
||||||
clipanion: 3.2.1(typanion@3.14.0)
|
clipanion: 3.2.1(typanion@3.14.0)
|
||||||
colorette: 2.0.20
|
colorette: 2.0.20
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
inquirer: 10.0.4
|
inquirer: 10.0.4
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
|
@ -8034,7 +7948,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@napi-rs/lzma': 1.3.1
|
'@napi-rs/lzma': 1.3.1
|
||||||
'@napi-rs/tar': 0.1.1
|
'@napi-rs/tar': 0.1.1
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -8686,8 +8600,6 @@ snapshots:
|
||||||
|
|
||||||
'@types/minimist@1.2.5': {}
|
'@types/minimist@1.2.5': {}
|
||||||
|
|
||||||
'@types/mocha@10.0.7': {}
|
|
||||||
|
|
||||||
'@types/mute-stream@0.0.4':
|
'@types/mute-stream@0.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.14.11
|
'@types/node': 20.14.11
|
||||||
|
@ -9006,7 +8918,7 @@ snapshots:
|
||||||
|
|
||||||
agent-base@7.1.1:
|
agent-base@7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -9028,8 +8940,6 @@ snapshots:
|
||||||
json-schema-traverse: 1.0.0
|
json-schema-traverse: 1.0.0
|
||||||
require-from-string: 2.0.2
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
ansi-colors@4.1.3: {}
|
|
||||||
|
|
||||||
ansi-escapes@4.3.2:
|
ansi-escapes@4.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
type-fest: 0.21.3
|
type-fest: 0.21.3
|
||||||
|
@ -9256,8 +9166,6 @@ snapshots:
|
||||||
p-queue: 6.6.2
|
p-queue: 6.6.2
|
||||||
unload: 2.4.1
|
unload: 2.4.1
|
||||||
|
|
||||||
browser-stdout@1.3.1: {}
|
|
||||||
|
|
||||||
browserify-zlib@0.1.4:
|
browserify-zlib@0.1.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
pako: 0.2.9
|
pako: 0.2.9
|
||||||
|
@ -9756,11 +9664,9 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
|
|
||||||
debug@4.3.5(supports-color@8.1.1):
|
debug@4.3.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
optionalDependencies:
|
|
||||||
supports-color: 8.1.1
|
|
||||||
|
|
||||||
decamelize-keys@1.1.1:
|
decamelize-keys@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -9769,8 +9675,6 @@ snapshots:
|
||||||
|
|
||||||
decamelize@1.2.0: {}
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
decamelize@4.0.0: {}
|
|
||||||
|
|
||||||
decimal.js@10.4.3: {}
|
decimal.js@10.4.3: {}
|
||||||
|
|
||||||
decompress-response@6.0.0:
|
decompress-response@6.0.0:
|
||||||
|
@ -9828,15 +9732,6 @@ snapshots:
|
||||||
|
|
||||||
deep-equal@1.0.1: {}
|
deep-equal@1.0.1: {}
|
||||||
|
|
||||||
deepl-node@1.13.0:
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 20.14.11
|
|
||||||
axios: 1.7.2
|
|
||||||
form-data: 3.0.1
|
|
||||||
loglevel: 1.9.1
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
|
|
||||||
deepmerge@4.3.1: {}
|
deepmerge@4.3.1: {}
|
||||||
|
|
||||||
defer-to-connect@2.0.1: {}
|
defer-to-connect@2.0.1: {}
|
||||||
|
@ -9872,8 +9767,6 @@ snapshots:
|
||||||
|
|
||||||
diff@4.0.2: {}
|
diff@4.0.2: {}
|
||||||
|
|
||||||
diff@5.2.0: {}
|
|
||||||
|
|
||||||
dijkstrajs@1.0.3: {}
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
dir-glob@3.0.1:
|
dir-glob@3.0.1:
|
||||||
|
@ -10226,11 +10119,6 @@ snapshots:
|
||||||
locate-path: 5.0.0
|
locate-path: 5.0.0
|
||||||
path-exists: 4.0.0
|
path-exists: 4.0.0
|
||||||
|
|
||||||
find-up@5.0.0:
|
|
||||||
dependencies:
|
|
||||||
locate-path: 6.0.0
|
|
||||||
path-exists: 4.0.0
|
|
||||||
|
|
||||||
fix-esm@1.0.1:
|
fix-esm@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.9
|
'@babel/core': 7.24.9
|
||||||
|
@ -10239,8 +10127,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
flat@5.0.2: {}
|
|
||||||
|
|
||||||
fluent-ffmpeg@2.1.3:
|
fluent-ffmpeg@2.1.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
async: 0.2.10
|
async: 0.2.10
|
||||||
|
@ -10268,12 +10154,6 @@ snapshots:
|
||||||
|
|
||||||
form-data-encoder@4.0.2: {}
|
form-data-encoder@4.0.2: {}
|
||||||
|
|
||||||
form-data@3.0.1:
|
|
||||||
dependencies:
|
|
||||||
asynckit: 0.4.0
|
|
||||||
combined-stream: 1.0.8
|
|
||||||
mime-types: 2.1.35
|
|
||||||
|
|
||||||
form-data@4.0.0:
|
form-data@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
asynckit: 0.4.0
|
asynckit: 0.4.0
|
||||||
|
@ -10380,14 +10260,6 @@ snapshots:
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
|
|
||||||
glob@8.1.0:
|
|
||||||
dependencies:
|
|
||||||
fs.realpath: 1.0.0
|
|
||||||
inflight: 1.0.6
|
|
||||||
inherits: 2.0.4
|
|
||||||
minimatch: 5.1.6
|
|
||||||
once: 1.4.0
|
|
||||||
|
|
||||||
globals@11.12.0: {}
|
globals@11.12.0: {}
|
||||||
|
|
||||||
globby@11.1.0:
|
globby@11.1.0:
|
||||||
|
@ -10540,7 +10412,7 @@ snapshots:
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.1
|
agent-base: 7.1.1
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -10557,7 +10429,7 @@ snapshots:
|
||||||
https-proxy-agent@7.0.5:
|
https-proxy-agent@7.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.1
|
agent-base: 7.1.1
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -10623,7 +10495,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ioredis/commands': 1.2.0
|
'@ioredis/commands': 1.2.0
|
||||||
cluster-key-slot: 1.1.2
|
cluster-key-slot: 1.1.2
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
denque: 2.1.0
|
denque: 2.1.0
|
||||||
lodash.defaults: 4.2.0
|
lodash.defaults: 4.2.0
|
||||||
lodash.isarguments: 3.1.0
|
lodash.isarguments: 3.1.0
|
||||||
|
@ -10708,8 +10580,6 @@ snapshots:
|
||||||
|
|
||||||
is-plain-obj@1.1.0: {}
|
is-plain-obj@1.1.0: {}
|
||||||
|
|
||||||
is-plain-obj@2.1.0: {}
|
|
||||||
|
|
||||||
is-plain-obj@4.1.0: {}
|
is-plain-obj@4.1.0: {}
|
||||||
|
|
||||||
is-plain-object@5.0.0: {}
|
is-plain-object@5.0.0: {}
|
||||||
|
@ -10781,7 +10651,7 @@ snapshots:
|
||||||
|
|
||||||
istanbul-lib-source-maps@4.0.1:
|
istanbul-lib-source-maps@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
istanbul-lib-coverage: 3.2.2
|
istanbul-lib-coverage: 3.2.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -11309,7 +11179,7 @@ snapshots:
|
||||||
|
|
||||||
koa-mount@4.0.0:
|
koa-mount@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
koa-compose: 4.1.0
|
koa-compose: 4.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -11318,7 +11188,7 @@ snapshots:
|
||||||
|
|
||||||
koa-router@10.1.1:
|
koa-router@10.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
http-errors: 1.8.1
|
http-errors: 1.8.1
|
||||||
koa-compose: 4.1.0
|
koa-compose: 4.1.0
|
||||||
methods: 1.1.2
|
methods: 1.1.2
|
||||||
|
@ -11328,7 +11198,7 @@ snapshots:
|
||||||
|
|
||||||
koa-send@5.0.1:
|
koa-send@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
http-errors: 1.8.1
|
http-errors: 1.8.1
|
||||||
resolve-path: 1.4.0
|
resolve-path: 1.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -11344,7 +11214,7 @@ snapshots:
|
||||||
koa-views@7.0.2(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3):
|
koa-views@7.0.2(@types/koa@2.15.0)(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
consolidate: 0.16.0(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
|
consolidate: 0.16.0(ejs@3.1.10)(lodash@4.17.21)(pug@3.0.3)
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
get-paths: 0.0.7
|
get-paths: 0.0.7
|
||||||
koa-send: 5.0.1
|
koa-send: 5.0.1
|
||||||
mz: 2.7.0
|
mz: 2.7.0
|
||||||
|
@ -11415,7 +11285,7 @@ snapshots:
|
||||||
content-disposition: 0.5.4
|
content-disposition: 0.5.4
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookies: 0.8.0
|
cookies: 0.8.0
|
||||||
debug: 4.3.3
|
debug: 4.3.5
|
||||||
delegates: 1.0.0
|
delegates: 1.0.0
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
destroy: 1.2.0
|
destroy: 1.2.0
|
||||||
|
@ -11443,7 +11313,7 @@ snapshots:
|
||||||
content-disposition: 0.5.4
|
content-disposition: 0.5.4
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookies: 0.9.1
|
cookies: 0.9.1
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
delegates: 1.0.0
|
delegates: 1.0.0
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
destroy: 1.2.0
|
destroy: 1.2.0
|
||||||
|
@ -11492,10 +11362,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 4.1.0
|
p-locate: 4.1.0
|
||||||
|
|
||||||
locate-path@6.0.0:
|
|
||||||
dependencies:
|
|
||||||
p-locate: 5.0.0
|
|
||||||
|
|
||||||
lodash-es@4.17.21: {}
|
lodash-es@4.17.21: {}
|
||||||
|
|
||||||
lodash.assignin@4.2.0: {}
|
lodash.assignin@4.2.0: {}
|
||||||
|
@ -11535,8 +11401,6 @@ snapshots:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
is-unicode-supported: 0.1.0
|
is-unicode-supported: 0.1.0
|
||||||
|
|
||||||
loglevel@1.9.1: {}
|
|
||||||
|
|
||||||
long@5.2.3: {}
|
long@5.2.3: {}
|
||||||
|
|
||||||
lowercase-keys@2.0.0: {}
|
lowercase-keys@2.0.0: {}
|
||||||
|
@ -11661,29 +11525,6 @@ snapshots:
|
||||||
|
|
||||||
mkdirp@2.1.6: {}
|
mkdirp@2.1.6: {}
|
||||||
|
|
||||||
mocha@10.6.0:
|
|
||||||
dependencies:
|
|
||||||
ansi-colors: 4.1.3
|
|
||||||
browser-stdout: 1.3.1
|
|
||||||
chokidar: 3.6.0
|
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
|
||||||
diff: 5.2.0
|
|
||||||
escape-string-regexp: 4.0.0
|
|
||||||
find-up: 5.0.0
|
|
||||||
glob: 8.1.0
|
|
||||||
he: 1.2.0
|
|
||||||
js-yaml: 4.1.0
|
|
||||||
log-symbols: 4.1.0
|
|
||||||
minimatch: 5.1.6
|
|
||||||
ms: 2.1.3
|
|
||||||
serialize-javascript: 6.0.2
|
|
||||||
strip-json-comments: 3.1.1
|
|
||||||
supports-color: 8.1.1
|
|
||||||
workerpool: 6.5.1
|
|
||||||
yargs: 16.2.0
|
|
||||||
yargs-parser: 20.2.9
|
|
||||||
yargs-unparser: 2.0.0
|
|
||||||
|
|
||||||
mock-socket@9.3.1: {}
|
mock-socket@9.3.1: {}
|
||||||
|
|
||||||
moment@2.30.1: {}
|
moment@2.30.1: {}
|
||||||
|
@ -11844,8 +11685,6 @@ snapshots:
|
||||||
|
|
||||||
only@0.0.2: {}
|
only@0.0.2: {}
|
||||||
|
|
||||||
opencc-js@1.0.5: {}
|
|
||||||
|
|
||||||
opencollective-postinstall@2.0.3: {}
|
opencollective-postinstall@2.0.3: {}
|
||||||
|
|
||||||
opentype.js@0.4.11: {}
|
opentype.js@0.4.11: {}
|
||||||
|
@ -11874,10 +11713,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 2.3.0
|
p-limit: 2.3.0
|
||||||
|
|
||||||
p-locate@5.0.0:
|
|
||||||
dependencies:
|
|
||||||
p-limit: 3.1.0
|
|
||||||
|
|
||||||
p-queue@6.6.2:
|
p-queue@6.6.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 4.0.7
|
eventemitter3: 4.0.7
|
||||||
|
@ -12359,7 +12194,7 @@ snapshots:
|
||||||
|
|
||||||
redis-semaphore@5.6.0(ioredis@5.4.1):
|
redis-semaphore@5.6.0(ioredis@5.4.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ioredis: 5.4.1
|
ioredis: 5.4.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -13020,7 +12855,7 @@ snapshots:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cli-highlight: 2.1.11
|
cli-highlight: 2.1.11
|
||||||
dayjs: 1.11.12
|
dayjs: 1.11.12
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
dotenv: 16.4.5
|
dotenv: 16.4.5
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
mkdirp: 2.1.6
|
mkdirp: 2.1.6
|
||||||
|
@ -13146,7 +12981,7 @@ snapshots:
|
||||||
vite-plugin-compression@0.5.1(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3)):
|
vite-plugin-compression@0.5.1(vite@5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
debug: 4.3.5(supports-color@8.1.1)
|
debug: 4.3.5
|
||||||
fs-extra: 10.1.0
|
fs-extra: 10.1.0
|
||||||
vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3)
|
vite: 5.3.4(@types/node@20.14.11)(sass@1.77.8)(terser@5.31.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -13315,8 +13150,6 @@ snapshots:
|
||||||
assert-never: 1.3.0
|
assert-never: 1.3.0
|
||||||
babel-walk: 3.0.0-canary-5
|
babel-walk: 3.0.0-canary-5
|
||||||
|
|
||||||
workerpool@6.5.1: {}
|
|
||||||
|
|
||||||
wrap-ansi@6.2.0:
|
wrap-ansi@6.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
|
@ -13392,13 +13225,6 @@ snapshots:
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
yargs-unparser@2.0.0:
|
|
||||||
dependencies:
|
|
||||||
camelcase: 6.3.0
|
|
||||||
decamelize: 4.0.0
|
|
||||||
flat: 5.0.2
|
|
||||||
is-plain-obj: 2.1.0
|
|
||||||
|
|
||||||
yargs@15.4.1:
|
yargs@15.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 6.0.0
|
cliui: 6.0.0
|
||||||
|
|