diff --git a/.gitignore b/.gitignore index ea8c4bf..9751b70 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target + +/*.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a46a8e0..02de07a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,13 +203,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713c6293af093c202ad318e8f7bdc1de1a36d7a793bb77f7fc6bd6f1788659a9" dependencies = [ "compio-buf", + "compio-dispatcher", "compio-driver", "compio-fs", "compio-io", "compio-log", "compio-macros", "compio-net", + "compio-process", + "compio-quic", "compio-runtime", + "compio-signal", ] [[package]] @@ -223,6 +227,18 @@ dependencies = [ "libc", ] +[[package]] +name = "compio-dispatcher" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cdf8c613be826be410d8744ab30acc49cc5134a78e2aa25efae9efa44bed6a7" +dependencies = [ + "compio-driver", + "compio-runtime", + "flume", + "futures-channel", +] + [[package]] name = "compio-driver" version = "0.8.1" @@ -237,10 +253,13 @@ dependencies = [ "crossbeam-channel", "crossbeam-queue", "futures-util", + "io-uring", + "io_uring_buf_ring", "libc", "once_cell", "paste", "polling", + "slab", "socket2", "windows-sys 0.52.0", ] @@ -314,6 +333,43 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "compio-process" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3867cfe7b23eaae89ff815aba4fdde61cb6fd55f81fd368128300c6b7e645016" +dependencies = [ + "cfg-if", + "compio-buf", + "compio-driver", + "compio-io", + "compio-runtime", + "futures-util", + "windows-sys 0.52.0", +] + +[[package]] +name = "compio-quic" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f107e044329f1e171930801b09bfc6e764c5e171e45c7a3e382f98561da619a" +dependencies = [ + "cfg_aliases", + "compio-buf", + "compio-io", + "compio-log", + "compio-net", + "compio-runtime", + "flume", + "futures-util", + "libc", + "quinn-proto", + "rustc-hash", + "rustls", + "thiserror 2.0.12", + "windows-sys 0.52.0", +] + [[package]] name = "compio-runtime" version = "0.8.1" @@ -330,10 +386,27 @@ dependencies = [ "libc", "once_cell", "scoped-tls", + "slab", "socket2", "windows-sys 0.52.0", ] +[[package]] +name = "compio-signal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d2931880b03b33d4df7d2b8a008e93731366d185358c7442fc8d24d5f9c1bd" +dependencies = [ + "compio-buf", + "compio-driver", + "compio-runtime", + "libc", + "once_cell", + "os_pipe", + "slab", + "windows-sys 0.52.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -480,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -493,6 +566,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -562,6 +647,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + [[package]] name = "gimli" version = "0.31.1" @@ -651,6 +763,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "io_uring_buf_ring" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8867874ff5758b47c1dac069e6e86541432f9da8be9111c5e94154134f07d0" +dependencies = [ + "bytes", + "io-uring", + "rustix", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -718,6 +852,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "md5" version = "0.8.0" @@ -745,6 +885,26 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "nom" version = "7.1.3" @@ -986,6 +1146,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -1004,6 +1173,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + [[package]] name = "quote" version = "1.0.40" @@ -1013,6 +1202,41 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -1033,12 +1257,32 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1058,7 +1302,41 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -1102,6 +1380,7 @@ dependencies = [ "cbc", "chrono", "cipher", + "compio", "digest", "flate2", "hmac-sha256", @@ -1111,6 +1390,7 @@ dependencies = [ "serde_json", "snafu", "strum 0.27.2", + "tokio", ] [[package]] @@ -1118,6 +1398,7 @@ name = "sdgb-cli" version = "0.1.0" dependencies = [ "compio", + "futures-util", "nyquest-preset", "palc", "sdgb-api", @@ -1125,6 +1406,7 @@ dependencies = [ "snafu", "spdlog-rs", "strum 0.27.2", + "tokio", ] [[package]] @@ -1308,6 +1590,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.109" @@ -1370,6 +1658,47 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +dependencies = [ + "backtrace", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -1402,6 +1731,9 @@ name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] [[package]] name = "typenum" @@ -1415,6 +1747,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1427,6 +1765,21 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1485,6 +1838,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "widestring" version = "1.2.0" @@ -1788,3 +2151,38 @@ checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 9e3a459..0a471e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,13 @@ members = ["sdgb-api", "sdgb-cli"] resolver = "3" [workspace.dependencies] -sdgb-api = { path = "./sdgb-api" } +sdgb-api = { path = "./sdgb-api", default-features = false } snafu = { version = "0.8.6", features = ["backtrace", "rust_1_81"] } serde_json = "1.0.141" strum = { version = "0.27.2", features = ["derive"] } +tokio = { version = "1", features = ["rt-multi-thread"] } +compio = { version = "0.15.0", features = ["runtime"] } [profile.release] lto = true diff --git a/sdgb-api/Cargo.toml b/sdgb-api/Cargo.toml index 723fff3..7ddab9e 100644 --- a/sdgb-api/Cargo.toml +++ b/sdgb-api/Cargo.toml @@ -5,10 +5,17 @@ edition = "2024" license = "GPL-3.0" +[features] +default = ["compio"] +compio = ["dep:compio"] +tokio = ["dep:tokio"] + [dependencies] snafu = { workspace = true } serde_json = { workspace = true } -strum = {workspace = true} +strum = { workspace = true } +tokio = { workspace = true, optional = true } +compio = { workspace = true, optional = true } # hashing digest = "0.10.7" diff --git a/sdgb-api/src/error.rs b/sdgb-api/src/error.rs index 42e8802..4b37771 100644 --- a/sdgb-api/src/error.rs +++ b/sdgb-api/src/error.rs @@ -3,24 +3,36 @@ use snafu::Snafu; #[derive(Debug, Snafu)] pub enum ApiError { + JoinError, + #[snafu(display("api returned nothing!"))] EmptyResponse, #[snafu(display("encrypt data: {error}"))] - Pad { error: PadError }, + Pad { + error: PadError, + }, #[snafu(display("decrypt data: {error}"))] - Unpad { error: UnpadError }, + Unpad { + error: UnpadError, + }, #[snafu(display("io error: {source}"))] #[snafu(context(false))] - IO { source: std::io::Error }, + IO { + source: std::io::Error, + }, #[snafu(display("json error: {source}"))] #[snafu(context(false))] - JSON { source: serde_json::Error }, + JSON { + source: serde_json::Error, + }, #[snafu(display("request error: {source}"))] #[snafu(context(false))] - Request { source: nyquest::Error }, + Request { + source: nyquest::Error, + }, } impl From for ApiError { diff --git a/sdgb-api/src/title/mod.rs b/sdgb-api/src/title/mod.rs index f91d32c..49308d1 100644 --- a/sdgb-api/src/title/mod.rs +++ b/sdgb-api/src/title/mod.rs @@ -62,16 +62,25 @@ pub trait MaiVersionExt: MaiVersion { fn request_raw( client: &AsyncClient, api: APIMethod, - agent_extra: impl Display, + agent_extra: impl Display + Send + 'static, data: D, ) -> impl Future, ApiError>> where - D: Serialize, + D: Serialize + Send + 'static, { + #[cfg(feature = "compio")] + use compio::runtime::spawn_blocking; + #[cfg(feature = "tokio")] + use tokio::task::spawn_blocking; + async { - let req = Self::api_call(api, agent_extra, data)?; + let req = spawn_blocking(move || Self::api_call(api, agent_extra, data)) + .await + .map_err(|_| ApiError::JoinError)??; let data = client.request(req).await?.bytes().await?; - let decoded = Self::decode(data)?; + let decoded = spawn_blocking(move || Self::decode(data)) + .await + .map_err(|_| ApiError::JoinError)??; Ok(decoded) } } @@ -79,11 +88,11 @@ pub trait MaiVersionExt: MaiVersion { fn request( client: &AsyncClient, api: APIMethod, - agent_extra: impl Display, + agent_extra: impl Display + Send + 'static, data: D, ) -> impl Future> where - D: Serialize, + D: Serialize + Send + 'static, R: for<'a> Deserialize<'a>, { async { diff --git a/sdgb-cli/Cargo.toml b/sdgb-cli/Cargo.toml index 40655cb..ed9c909 100644 --- a/sdgb-cli/Cargo.toml +++ b/sdgb-cli/Cargo.toml @@ -5,20 +5,25 @@ edition = "2024" authors = ["mokurin000"] description = "CLI tool for SDGB protocol" +[features] +default = ["compio"] +compio = ["dep:compio", "sdgb-api/compio"] +tokio = ["dep:tokio", "sdgb-api/tokio"] + [dependencies] -snafu = { workspace = true } sdgb-api = { workspace = true } + +snafu = { workspace = true } serde_json = { workspace = true } strum = { workspace = true } +tokio = { workspace = true, features = ["macros"], optional = true } +compio = { workspace = true, features = ["macros"], optional = true } nyquest-preset = { version = "0.2.0", features = ["async"] } -compio = { version = "0.15.0", default-features = false, features = [ - "runtime", - "macros", -] } palc = { version = "0.0.1", features = ["derive"] } spdlog-rs = { version = "0.4.3", default-features = false, features = [ "level-info", "release-level-info", ] } +futures-util = "0.3.31" diff --git a/sdgb-cli/src/commands.rs b/sdgb-cli/src/commands.rs index 510d05d..0394874 100644 --- a/sdgb-cli/src/commands.rs +++ b/sdgb-cli/src/commands.rs @@ -36,6 +36,9 @@ pub enum Commands { #[arg(short, long)] user_id: u32, }, + + ListAllUser, + Logout { #[arg(short, long)] user_id: u32, diff --git a/sdgb-cli/src/main.rs b/sdgb-cli/src/main.rs index b8bc4c2..3a3124a 100644 --- a/sdgb-cli/src/main.rs +++ b/sdgb-cli/src/main.rs @@ -1,6 +1,13 @@ +use std::{ + fs::OpenOptions, + io::{BufRead, stdin}, +}; + +use futures_util::StreamExt; use nyquest_preset::nyquest::ClientBuilder; use palc::Parser; use sdgb_api::{ + ApiError, all_net::QRCode, auth_lite::{SDGB, SDHJ, delivery_raw}, title::{ @@ -12,13 +19,14 @@ use sdgb_api::{ }, }, }; -use spdlog::{error, info}; +use spdlog::{error, info, warn}; use crate::commands::Cli; mod commands; -#[compio::main] +#[cfg_attr(feature = "compio", compio::main)] +#[cfg_attr(feature = "tokio", tokio::main)] async fn main() -> Result<(), Box> { nyquest_preset::register(); let cmd = ::parse(); @@ -78,6 +86,55 @@ async fn main() -> Result<(), Box> { }; println!("{}", String::from_utf8_lossy(&resp)); } + + commands::Commands::ListAllUser => { + let mut stdin = stdin().lock(); + let mut buf = String::new(); + let mut user_ids = Vec::new(); + + while stdin.read_line(&mut buf).is_ok_and(|size| size != 0) { + if buf.is_empty() { + continue; + } + + let user_id: u32 = buf.trim().parse()?; + buf.clear(); + user_ids.push(user_id); + } + + let users = futures_util::stream::iter(user_ids) + .map(async |user_id| { + info!("preview: {user_id}"); + let resp = Sdgb1_50::request::<_, GetUserPreviewApiResp>( + &client, + APIMethod::GetUserPreviewApi, + user_id, + GetUserPreviewApi { user_id }, + ) + .await; + + if let Err(e) = &resp { + if matches!(e, ApiError::JSON { .. }) { + warn!("account unregistered: {user_id}"); + } else { + error!("preview failed: {e}"); + } + } + + resp + }) + .buffer_unordered(20) + .filter_map(async |r| r.ok()) + .collect::>() + .await; + + let output = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open("users.json")?; + serde_json::to_writer_pretty(output, &users)?; + } } Ok(())