From 0e118ae31b4245d756821db5d0b03078551a8d8d Mon Sep 17 00:00:00 2001 From: THMonster Date: Fri, 13 Oct 2023 10:52:21 +0800 Subject: [PATCH] refactor --- Cargo.lock | 1572 +++++++++++++++++++------------ Cargo.toml | 19 +- src/config/config.rs | 1 + src/config/mod.rs | 121 +-- src/danmaku/bilibili.rs | 43 +- src/danmaku/bilivideo.rs | 4 +- src/danmaku/douyu.rs | 134 +-- src/danmaku/fudujikiller.rs | 23 +- src/danmaku/huya.rs | 106 +-- src/danmaku/mod.rs | 481 +++++----- src/danmaku/twitch.rs | 81 +- src/danmaku/youtube.rs | 131 ++- src/dmlive.rs | 261 ++--- src/ffmpeg/mod.rs | 188 ++-- src/ipcmanager/mod.rs | 246 ++--- src/main.rs | 41 +- src/mpv/mod.rs | 205 ++-- src/streamer/flv.rs | 95 +- src/streamer/hls.rs | 48 +- src/streamer/mod.rs | 29 +- src/streamer/segment.rs | 30 +- src/streamer/youtube.rs | 249 +++-- src/streamfinder/bilibili.rs | 166 ++-- src/streamfinder/douyu.rs | 65 +- src/streamfinder/huya.rs | 30 +- src/streamfinder/mod.rs | 36 +- src/streamfinder/twitch.rs | 36 +- src/streamfinder/youtube.rs | 18 +- src/utils/cookies.rs | 4 +- src/utils/mod.rs | 57 +- tars-stream/src/tars_decoder.rs | 2 + 31 files changed, 2251 insertions(+), 2271 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7864b94..aa715e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -10,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" dependencies = [ "cfg-if", "cipher", @@ -21,10 +30,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", @@ -32,9 +42,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" dependencies = [ "memchr", ] @@ -54,6 +64,18 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -64,34 +86,68 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.70" +name = "anstream" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] [[package]] -name = "async-channel" -version = "1.8.0" +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", + "utf8parse", ] [[package]] -name = "async-compression" -version = "0.3.15" +name = "anstyle-query" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "brotli", - "flate2", + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", "futures-core", - "memchr", - "pin-project-lite", - "tokio", ] [[package]] @@ -110,9 +166,9 @@ dependencies = [ [[package]] name = "atoi" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] @@ -124,16 +180,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "base64" -version = "0.13.1" +name = "backtrace" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] [[package]] name = "base64" -version = "0.21.0" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" + +[[package]] +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bincode" @@ -165,6 +236,15 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -176,18 +256,18 @@ dependencies = [ [[package]] name = "block-padding" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] [[package]] name = "brotli" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -196,9 +276,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "da74e2b81409b1b743f8f0c62cc6254afefb8b8e50bbfe3735550f7aeefa3448" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -206,9 +286,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" @@ -228,9 +308,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cbc" @@ -243,9 +323,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -255,17 +338,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] @@ -280,59 +362,65 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.13" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c911b090850d79fc64fe9ea01e28e465f65e821e08813ced95bced72f7a8a9b" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", "strsim", - "termcolor", ] [[package]] name = "clap_derive" -version = "4.1.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a932373bab67b984c790ddf2c9ca295d8e3af3b7ef92de5a5bacdccdee4b09b" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.37", ] [[package]] name = "clap_lex" -version = "0.3.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033f6b7a4acb1f358c742aaca805c939ee73b4c6209ae4318ec7aca81c42e646" -dependencies = [ - "os_str_bytes", -] +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "colorchoice" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + [[package]] name = "cookie" version = "0.16.2" @@ -340,7 +428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding", - "time 0.3.23", + "time", "version_check", ] @@ -357,7 +445,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time 0.3.23", + "time", "url", ] @@ -373,15 +461,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] @@ -422,9 +510,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", ] @@ -440,77 +528,59 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.93" +name = "data-encoding" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] -name = "cxx-build" -version = "1.0.93" +name = "der" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.8", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.93" +name = "deranged" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.8", -] +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", ] [[package]] name = "directories" -version = "4.0.1" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", + "option-ext", "redox_users", - "winapi", + "windows-sys", ] [[package]] @@ -520,10 +590,10 @@ dependencies = [ "aes", "anyhow", "async-channel", - "async-compression 0.4.3", - "base64 0.21.0", + "async-compression", + "base64", "bincode", - "bytes 1.4.0", + "bytes 1.5.0", "cbc", "chrono", "clap", @@ -537,7 +607,7 @@ dependencies = [ "rand", "regex", "reqwest", - "ring", + "ring 0.17.0", "roxmltree", "serde", "serde_json", @@ -559,15 +629,18 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -585,15 +658,21 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.2.8" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys", ] [[package]] @@ -606,6 +685,17 @@ dependencies = [ "libc", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -624,18 +714,21 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide", @@ -643,14 +736,13 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "pin-project", - "spin 0.9.6", + "spin 0.9.8", ] [[package]] @@ -676,18 +768,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -700,9 +792,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -710,15 +802,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -727,49 +819,49 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.11.2", + "parking_lot", ] [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -785,9 +877,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -795,28 +887,34 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + [[package]] name = "h2" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "fnv", "futures-core", "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -828,17 +926,24 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown", + "hashbrown 0.14.1", ] [[package]] @@ -852,24 +957,42 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "libc", + "hmac", ] [[package]] -name = "hermit-abi" -version = "0.3.1" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] [[package]] -name = "hex" -version = "0.4.3" +name = "home" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] [[package]] name = "html-escape" @@ -886,7 +1009,7 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "fnv", "itoa", ] @@ -897,7 +1020,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "http", "pin-project-lite", ] @@ -910,9 +1033,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -922,11 +1045,11 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures-channel", "futures-core", "futures-util", @@ -937,7 +1060,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -950,7 +1073,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "hyper", "native-tls", "tokio", @@ -959,9 +1082,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -973,12 +1096,11 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -1003,43 +1125,43 @@ dependencies = [ ] [[package]] -name = "indexmap" -version = "1.9.2" +name = "idna" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "autocfg", - "hashbrown", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "inout" -version = "0.1.3" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "block-padding", - "generic-array", + "autocfg", + "hashbrown 0.12.3", ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ - "cfg-if", + "equivalent", + "hashbrown 0.14.1", ] [[package]] -name = "io-lifetimes" -version = "1.0.9" +name = "inout" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.45.0", + "block-padding", + "generic-array", ] [[package]] @@ -1053,42 +1175,41 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" [[package]] name = "is-terminal" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", + "hermit-abi", "rustix", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -1098,44 +1219,44 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "libc" -version = "0.2.140" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] -name = "libsqlite3-sys" -version = "0.24.2" +name = "libm" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "libsqlite3-sys" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ "cc", + "pkg-config", + "vcpkg", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -1143,12 +1264,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matches" @@ -1156,11 +1274,21 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -1176,23 +1304,22 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "wasi", + "windows-sys", ] [[package]] @@ -1224,7 +1351,24 @@ dependencies = [ ] [[package]] -name = "num-integer" +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" @@ -1233,38 +1377,59 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" -version = "0.10.48" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -1275,13 +1440,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] @@ -1292,20 +1457,19 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.25.2+1.1.1t" +version = "300.1.5+3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320708a054ad9b3bf314688b5db87cf4d6683d64cfc835e2337924ae62bf4431" +checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.83" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ - "autocfg", "cc", "libc", "openssl-src", @@ -1314,21 +1478,10 @@ dependencies = [ ] [[package]] -name = "os_str_bytes" -version = "6.5.0" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" @@ -1337,73 +1490,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", - "instant", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "percent-encoding" -version = "2.2.0" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] -name = "pin-project" -version = "1.0.12" +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "pin-project-internal", + "base64ct", ] [[package]] -name = "pin-project-internal" -version = "1.0.12" +name = "percent-encoding" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1411,11 +1539,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" @@ -1425,9 +1574,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.53" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba466839c78239c09faf015484e5cc04860f88242cff4d03eb038f04b4699b73" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -1456,9 +1605,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1499,7 +1648,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -1509,15 +1667,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", @@ -1526,19 +1696,19 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" [[package]] name = "reqwest" -version = "0.11.15" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba30cc2c0cd02af1222ed216ba659cdb2f879dfe3181852fe7c50b1d0005949" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "async-compression 0.3.15", - "base64 0.21.0", - "bytes 1.4.0", + "async-compression", + "base64", + "bytes 1.5.0", "cookie", "cookie_store", "encoding_rs", @@ -1560,6 +1730,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-util", @@ -1581,81 +1752,125 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb9d44f9bf6b635117787f72416783eb7e4227aaf255e5ce739563d817176a7e" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "roxmltree" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f595a457b6b8c6cda66a48503e92ee8d19342f905948f29c383200ec9eb1d8" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" dependencies = [ "xmlparser", ] +[[package]] +name = "rsa" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +dependencies = [ + "byteorder", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustix" -version = "0.36.11" +version = "0.38.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ - "log", - "ring", + "ring 0.16.20", + "rustls-webpki", "sct", - "webpki", ] [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ - "base64 0.21.0", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" @@ -1663,17 +1878,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] name = "security-framework" -version = "2.8.2" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -1682,9 +1897,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1692,29 +1907,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.158" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", + "syn 2.0.37", ] [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -1723,9 +1938,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] @@ -1744,9 +1959,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1755,9 +1970,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -1773,20 +1988,30 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" @@ -1798,6 +2023,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" @@ -1806,18 +2041,28 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85" dependencies = [ "itertools", "nom", @@ -1826,42 +2071,40 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.6.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] name = "sqlx-core" -version = "0.6.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" dependencies = [ "ahash", "atoi", - "bitflags", "byteorder", - "bytes 1.4.0", + "bytes 1.5.0", "crc", "crossbeam-queue", "dotenvy", "either", "event-listener", - "flume", "futures-channel", "futures-core", - "futures-executor", "futures-intrusive", + "futures-io", "futures-util", "hashlink", "hex", - "indexmap", - "itoa", - "libc", - "libsqlite3-sys", + "indexmap 2.0.2", "log", "memchr", "once_cell", @@ -1869,53 +2112,167 @@ dependencies = [ "percent-encoding", "rustls", "rustls-pemfile", + "serde", + "serde_json", "sha2", "smallvec", "sqlformat", - "sqlx-rt", - "stringprep", "thiserror", + "tokio", "tokio-stream", + "tracing", "url", "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.6.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" dependencies = [ "dotenvy", "either", "heck", + "hex", "once_cell", "proc-macro2", "quote", + "serde", + "serde_json", "sha2", "sqlx-core", - "sqlx-rt", + "sqlx-mysql", + "sqlx-sqlite", "syn 1.0.109", + "tempfile", + "tokio", "url", ] [[package]] -name = "sqlx-rt" -version = "0.6.3" +name = "sqlx-mysql" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "bytes 1.5.0", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", "once_cell", - "tokio", - "tokio-rustls", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", ] [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -1926,6 +2283,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1939,15 +2302,36 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.8" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc02725fd69ab9f26eab07fad303e2497fad6fb9eba4f96c4d1687bdf704ad9" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tars-stream" version = "0.1.0" @@ -1958,63 +2342,53 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.4.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.8", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "syn 2.0.37", ] [[package]] name = "time" -version = "0.3.23" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -2023,15 +2397,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2053,33 +2427,32 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.26.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg", - "bytes 1.4.0", + "backtrace", + "bytes 1.5.0", "libc", - "memchr", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.4", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "1.8.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] @@ -2092,22 +2465,11 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2116,9 +2478,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -2130,11 +2492,11 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ - "bytes 1.4.0", + "bytes 1.5.0", "futures-core", "futures-sink", "pin-project-lite", @@ -2144,9 +2506,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", @@ -2156,20 +2518,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap", + "indexmap 2.0.2", "serde", "serde_spanned", "toml_datetime", @@ -2189,15 +2551,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] @@ -2210,13 +2585,13 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", - "bytes 1.4.0", + "bytes 1.5.0", + "data-encoding", "http", "httparse", "log", @@ -2230,9 +2605,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" @@ -2242,9 +2617,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2261,12 +2636,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - [[package]] name = "unicode_categories" version = "0.1.1" @@ -2279,22 +2648,28 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", ] [[package]] name = "urlencoding" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" @@ -2308,11 +2683,17 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom", ] @@ -2331,20 +2712,13 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2353,9 +2727,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2363,24 +2737,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -2390,9 +2764,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2400,51 +2774,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "webpki-roots" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" dependencies = [ - "ring", - "untrusted", + "rustls-webpki", ] [[package]] -name = "webpki-roots" -version = "0.22.6" +name = "whoami" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" [[package]] name = "winapi" @@ -2464,9 +2834,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -2479,42 +2849,27 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -2527,66 +2882,73 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.4.0" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deac0939bd6e4f24ab5919fbf751c97a8cfc8543bb083a305ed5c0c10bb241d1" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys", ] [[package]] name = "xmlparser" -version = "0.13.5" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "zeroize" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/Cargo.toml b/Cargo.toml index 74fdaeb..73d627e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ tokio = { version = "1", features = ["full"] } # tokio-rustls = "0.22" # webpki-roots = "*" # tokio-tungstenite = { version = "0.18", features = ["rustls-tls"] } -tokio-tungstenite = { version = "0.18", features = ["native-tls-vendored"] } +tokio-tungstenite = { version = "0.20", features = ["native-tls-vendored"] } reqwest = { version = "0.11", default-features = false, features = ["brotli", "deflate", "gzip", "json", "native-tls-vendored", "cookies"] } log = "0.4" env_logger = "0.10" @@ -19,29 +19,28 @@ serde_json = "1.0" serde = { version = "1", features = ["derive"] } bincode = "1.3" rand = "0.8" -regex = "1.7" +regex = "1.10" fancy-regex = "0.11" -uuid = { version = "1.3", features = ["v4"] } +uuid = { version = "1.4", features = ["v4"] } chrono = "0.4" -url = "2.3" +url = "2.4" urlencoding = "2.1" base64 = "0.21" libc = "0.2" -toml = "0.7" +toml = "0.8" html-escape = "0.2" futures = "0.3" roxmltree = "0.18" -async-channel = "1.8" -directories = "4.0" +async-channel = "1.9" +directories = "5.0" anyhow = "1" -bytes = "1.4" +bytes = "1.5" # boa_engine = { features = ["console"], version = "0.15.0" } tars-stream = { path = "tars-stream"} - ring = "*" cbc = "*" aes = "*" -sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] } +sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] } async-compression = { version = "0.4", features = ["tokio", "deflate", "brotli"] } [profile.release] diff --git a/src/config/config.rs b/src/config/config.rs index bd58a1a..f17da16 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -18,6 +18,7 @@ pub enum BVideoType { Video, Bangumi } + pub struct BVideoInfo { pub base_url: String, pub video_type: BVideoType, diff --git a/src/config/mod.rs b/src/config/mod.rs index 7541472..2bc3a6a 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,28 +1,24 @@ pub mod config; -use reqwest::Url; -use std::{ - path::Path, - sync::atomic::AtomicBool, -}; -use tokio::sync::RwLock; -use tokio::{ - fs::OpenOptions, - io::AsyncWriteExt, -}; - +use self::config::{BVideoInfo, BVideoType, Config}; +use crate::utils::is_android; use crate::Args; +use reqwest::Url; +use std::cell::{Cell, RefCell}; +use std::path::Path; +use tokio::{fs::OpenOptions, io::AsyncWriteExt}; -use self::config::{ - BVideoInfo, - BVideoType, - Config, -}; - +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Platform { + Linux, + LinuxTcp, + Android, +} pub enum RunMode { Play, Record, } +#[derive(Clone, Copy)] pub enum StreamType { FLV, HLS, @@ -38,30 +34,31 @@ pub enum Site { } pub struct ConfigManager { - pub plat: u8, + pub plat: Platform, pub bcookie: String, pub cookies_from_browser: String, pub plive: bool, pub quiet: bool, pub wait_interval: u64, - pub font_scale: RwLock, - pub font_alpha: RwLock, - pub danmaku_speed: RwLock, - pub display_fps: RwLock<(u64, u64)>, + pub font_scale: Cell, + pub font_alpha: Cell, + pub danmaku_speed: Cell, + pub display_fps: Cell<(u64, u64)>, pub room_url: String, pub http_address: Option, pub run_mode: RunMode, pub site: Site, - pub stream_type: RwLock, - pub bvideo_info: RwLock, - on_writing: AtomicBool, + pub stream_type: Cell, + pub bvideo_info: RefCell, + pub title: RefCell, + on_writing: Cell, } impl ConfigManager { pub fn new(config_path: impl AsRef, args: &Args) -> Self { - let mut plat = if cfg!(target_os = "linux") { 0 } else { 1 }; + let mut plat = Platform::Linux; if args.tcp { - plat = 1; + plat = Platform::LinuxTcp; } let mut bvinfo = BVideoInfo { base_url: "".into(), @@ -111,42 +108,50 @@ impl ConfigManager { }; Self { room_url: room_url.replace("dmlive://", "https://"), - stream_type: RwLock::new(StreamType::FLV), + stream_type: Cell::new(StreamType::FLV), run_mode, site, - font_scale: RwLock::new(c.font_scale.unwrap_or(1.0)), - font_alpha: RwLock::new(c.font_alpha.unwrap_or(0.0)), - danmaku_speed: RwLock::new(c.danmaku_speed.unwrap_or(8000)), - bvideo_info: RwLock::new(bvinfo), + font_scale: Cell::new(c.font_scale.unwrap_or(1.0)), + font_alpha: Cell::new(c.font_alpha.unwrap_or(0.0)), + danmaku_speed: Cell::new(c.danmaku_speed.unwrap_or(8000)), + bvideo_info: RefCell::new(bvinfo), bcookie: c.bcookie.unwrap_or_else(|| "".into()), http_address: args.http_address.as_ref().map(|it| it.into()), plive: args.plive, quiet: args.quiet, wait_interval: args.wait_interval.unwrap_or(0), - on_writing: AtomicBool::new(false), + on_writing: Cell::new(false), plat, cookies_from_browser: c.cookies_from_browser.unwrap_or_else(|| "".into()), - display_fps: RwLock::new((60, 0)), + display_fps: Cell::new((60, 0)), + title: RefCell::new("".to_string()), } } - pub async fn set_stream_type(&self, url: &str) { + pub async fn init(&mut self) -> anyhow::Result<()> { + if is_android().await { + self.plat = Platform::Android; + } + Ok(()) + } + + pub fn set_stream_type(&self, url: &str) { if url.contains(".m3u8") { - *self.stream_type.write().await = StreamType::HLS; + self.stream_type.set(StreamType::HLS); } else if url.contains(".flv") { - *self.stream_type.write().await = StreamType::FLV; + self.stream_type.set(StreamType::FLV); } else { - *self.stream_type.write().await = StreamType::DASH; + self.stream_type.set(StreamType::DASH); } if matches!(self.site, Site::BiliVideo) { - *self.stream_type.write().await = StreamType::DASH; + self.stream_type.set(StreamType::DASH); } } pub async fn write_config(&self) -> anyhow::Result<()> { - if !self.on_writing.load(std::sync::atomic::Ordering::SeqCst) { - self.on_writing.store(true, std::sync::atomic::Ordering::SeqCst); - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + if !self.on_writing.get() { + self.on_writing.set(true); + // tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; let proj_dirs = directories::ProjectDirs::from("com", "THMonster", "dmlive").unwrap(); let d = proj_dirs.config_dir(); let _ = tokio::fs::create_dir_all(&d).await; @@ -154,23 +159,21 @@ impl ConfigManager { if !config_path.exists() { let _ = tokio::fs::File::create(&config_path).await; } - { - let mut f = OpenOptions::new().write(true).truncate(true).open(config_path).await?; - f.write_all( - toml::to_string_pretty(&Config { - bcookie: Some(self.bcookie.clone()), - cookies_from_browser: Some(self.cookies_from_browser.clone()), - danmaku_speed: Some(*self.danmaku_speed.read().await), - font_alpha: Some(*self.font_alpha.read().await), - font_scale: Some(*self.font_scale.read().await), - }) - .unwrap() - .as_bytes(), - ) - .await?; - f.sync_all().await?; - } - self.on_writing.store(false, std::sync::atomic::Ordering::SeqCst); + let mut f = OpenOptions::new().write(true).truncate(true).open(config_path).await?; + f.write_all( + toml::to_string_pretty(&Config { + bcookie: Some(self.bcookie.clone()), + cookies_from_browser: Some(self.cookies_from_browser.clone()), + danmaku_speed: Some(self.danmaku_speed.get()), + font_alpha: Some(self.font_alpha.get()), + font_scale: Some(self.font_scale.get()), + }) + .unwrap() + .as_bytes(), + ) + .await?; + f.sync_all().await?; + self.on_writing.set(false); } Ok(()) } diff --git a/src/danmaku/bilibili.rs b/src/danmaku/bilibili.rs index 6aefab2..c17f562 100644 --- a/src/danmaku/bilibili.rs +++ b/src/danmaku/bilibili.rs @@ -1,12 +1,7 @@ -use std::{ - cell::RefCell, - collections::{HashMap, VecDeque}, -}; +use std::collections::{HashMap, VecDeque}; use bincode::Options; use futures::{stream::StreamExt, SinkExt}; -use log::info; -use log::warn; use reqwest::Url; use serde::Deserialize; use serde_json::json; @@ -17,6 +12,8 @@ use tokio::{ }; use tokio_tungstenite::{connect_async, tungstenite::Message::Binary}; +use crate::dmlerr; + const API_BUVID: &'static str = "https://data.bilibili.com/v/"; const API_ROOMINIT: &'static str = "https://api.live.bilibili.com/room/v1/Room/room_init"; const API_DMINFO: &'static str = "https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo"; @@ -59,17 +56,13 @@ impl Bilibili { .await? .json::() .await?; - let token = resp - .pointer("/data/token") - .ok_or(anyhow::anyhow!("err gdt a1"))? - .as_str() - .ok_or(anyhow::anyhow!("err gdt a12"))?; + let token = resp.pointer("/data/token").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?; Ok(token.to_string()) } - async fn get_ws_info(&self, url: &str) -> Result<(String, Vec), Box> { + async fn get_ws_info(&self, url: &str) -> anyhow::Result<(String, Vec)> { let rid = - Url::parse(url)?.path_segments().ok_or("rid parse error 1")?.last().ok_or("rid parse error 2")?.to_string(); + Url::parse(url)?.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?.to_string(); let mut reg_data: Vec = Vec::new(); let client = reqwest::Client::builder().user_agent(crate::utils::gen_ua()).build()?; let param1 = vec![("id", rid.as_str())]; @@ -81,7 +74,7 @@ impl Bilibili { .await? .json::() .await?; - let rid = resp.pointer("/data/room_id").ok_or("gwi pje 1")?.as_u64().ok_or("gwi pje 1-2")?; + let rid = resp.pointer("/data/room_id").ok_or_else(|| dmlerr!())?.as_u64().ok_or_else(|| dmlerr!())?; let buvid = self.get_buvid(&client).await?; let token = self.get_dm_token(&client, url, rid.to_string().as_str()).await?; // let rn = rand::random::(); @@ -108,12 +101,7 @@ impl Bilibili { if header.op == 5 { let j: serde_json::Value = serde_json::from_slice(data)?; // warn!("{:?}", &j); - let msg_type = match j - .pointer("/cmd") - .ok_or_else(|| anyhow::anyhow!("cmd parse failed 1"))? - .as_str() - .ok_or_else(|| anyhow::anyhow!("cmd parse failed 12"))? - { + let msg_type = match j.pointer("/cmd").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())? { "SEND_GIFT" => "gift", "SUPER_CHAT_MESSAGE" => "superchat", "WELCOME" => "enter", @@ -205,9 +193,7 @@ impl Bilibili { Ok(ret) } - pub async fn run( - &self, url: &str, dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { + pub async fn run(&self, url: &str, dtx: async_channel::Sender<(String, String, String)>) -> anyhow::Result<()> { let (tx, mut rx) = mpsc::channel::>(10); let (ws, reg_data) = self.get_ws_info(url).await?; let (ws_stream, _) = connect_async(&ws).await?; @@ -228,15 +214,14 @@ impl Bilibili { }; let (dmq_tx, mut dmq_rx) = mpsc::channel(1000); - let dm_cnt = RefCell::new(0u64); + let dm_cnt = std::cell::Cell::new(0u64); let decode_task = async { while let Some(mut it) = rx.recv().await { let mut dm = self.decode_msg(&mut it, &tx).await?; // dm_queue.append(&mut dm); for d in dm.drain(..) { dmq_tx.send(d).await?; - let mut dm_cnt = dm_cnt.borrow_mut(); - *dm_cnt = dm_cnt.saturating_add(1); + dm_cnt.set(dm_cnt.get() + 1); } } anyhow::Ok(()) @@ -244,9 +229,8 @@ impl Bilibili { let balance_task = async { while let Some(d) = dmq_rx.recv().await { let itvl = { - let mut dm_cnt = dm_cnt.borrow_mut(); - let itvl = 2000u64.saturating_div(*dm_cnt); - *dm_cnt = dm_cnt.saturating_sub(1); + let itvl = 2000u64.saturating_div(dm_cnt.get()); + dm_cnt.set(dm_cnt.get().saturating_sub(1)); itvl }; if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { @@ -272,7 +256,6 @@ impl Bilibili { it = decode_task => { it?; }, it = balance_task => { it?; }, } - info!("ws closed!"); Ok(()) } } diff --git a/src/danmaku/bilivideo.rs b/src/danmaku/bilivideo.rs index 6efb9f0..a21d339 100644 --- a/src/danmaku/bilivideo.rs +++ b/src/danmaku/bilivideo.rs @@ -1,4 +1,3 @@ -use anyhow::Result; use bytes::BufMut; use log::info; use tokio::io::AsyncWriteExt; @@ -12,7 +11,7 @@ impl Bilibili { pub async fn run( &self, url: &str, dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { + ) -> anyhow::Result<()> { let client = reqwest::Client::builder() .deflate(false) .user_agent(crate::utils::gen_ua()) @@ -42,6 +41,7 @@ impl Bilibili { .await?; } } + dtx.close(); Ok(()) } } diff --git a/src/danmaku/douyu.rs b/src/danmaku/douyu.rs index 86a0fec..8c504a9 100644 --- a/src/danmaku/douyu.rs +++ b/src/danmaku/douyu.rs @@ -1,45 +1,34 @@ -use crate::utils::gen_ua; +use crate::{dmlerr, utils::gen_ua}; use bincode::Options; -use futures::{ - stream::StreamExt, - SinkExt, -}; +use futures::{stream::StreamExt, SinkExt}; use reqwest::Url; -use std::{ - collections::HashMap, - usize, -}; +use std::{collections::HashMap, time::Duration, usize}; use tokio::time::sleep; +use tokio_tungstenite::tungstenite::Message::Binary; + +const HEARTBEAT: &'static [u8] = + b"\x14\x00\x00\x00\x14\x00\x00\x00\xb1\x02\x00\x00\x74\x79\x70\x65\x40\x3d\x6d\x72\x6b\x6c\x2f\x00"; pub struct Douyu { - color_tab: HashMap, - heartbeat: Vec, + color_tab: HashMap<&'static str, &'static str>, } impl Douyu { pub fn new() -> Self { - let hb = b"\x14\x00\x00\x00\x14\x00\x00\x00\xb1\x02\x00\x00\x74\x79\x70\x65\x40\x3d\x6d\x72\x6b\x6c\x2f\x00" - .to_vec(); - Douyu { - color_tab: [ - ("2".to_owned(), "1e87f0".to_owned()), - ("3".to_owned(), "7ac84b".to_owned()), - ("4".to_owned(), "ff7f00".to_owned()), - ("6".to_owned(), "ff69b4".to_owned()), - ("5".to_owned(), "9b39f4".to_owned()), - ("1".to_owned(), "ff0000".to_owned()), - ] - .iter() - .cloned() - .collect::>(), - heartbeat: hb, - } + let mut ct = HashMap::new(); + ct.insert("1", "ff0000"); + ct.insert("2", "1e87f0"); + ct.insert("3", "7ac84b"); + ct.insert("4", "ff7f00"); + ct.insert("5", "9b39f4"); + ct.insert("6", "ff69b4"); + Douyu { color_tab: ct } } - async fn get_ws_info(&self, url: &str) -> Result<(String, Vec>), Box> { + async fn get_ws_info(&self, url: &str) -> anyhow::Result<(String, Vec>)> { let mut reg_datas = Vec::new(); let rid = - Url::parse(url)?.path_segments().ok_or("rid parse error 1")?.last().ok_or("rid parse error 2")?.to_string(); + Url::parse(url)?.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?.to_string(); let mut pl = format!(r#"type@=loginreq/roomid@={}/"#, rid).as_bytes().to_vec(); let mut data: Vec = Vec::new(); let len = pl.len() as u32 + 9; @@ -61,7 +50,7 @@ impl Douyu { Ok(("wss://danmuproxy.douyu.com:8505".to_string(), reg_datas)) } - fn decode_msg(&self, data: &mut Vec) -> Result>, Box> { + fn decode_msg(&self, data: &mut Vec) -> anyhow::Result>> { let mut ret = Vec::new(); let bc_option = bincode::options().with_little_endian().with_fixint_encoding(); loop { @@ -85,7 +74,7 @@ impl Douyu { } }; - let msg_type = match j.pointer("/type").ok_or("dm pje 1")?.as_str().ok_or("dm pje 1-2")? { + let msg_type = match j.pointer("/type").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())? { "dgb" => "gift", "chatmsg" => "danmaku", "uenter" => "enter", @@ -94,21 +83,16 @@ impl Douyu { let mut d = std::collections::HashMap::new(); d.insert("msg_type".to_owned(), msg_type.to_owned()); if msg_type.eq("danmaku") { - // println!("{:?}", &j); d.insert( "name".to_owned(), - j.pointer("/nn").ok_or("dm pje 2")?.as_str().ok_or("dm pje 2-2")?.to_owned(), + j.pointer("/nn").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?.to_owned(), ); d.insert( "content".to_owned(), - j.pointer("/txt").ok_or("dm pje 3")?.as_str().ok_or("dm pje 3-2")?.to_owned(), + j.pointer("/txt").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?.to_owned(), ); - let col = match j.pointer("/col").ok_or("dm pje 4") { - Ok(it) => { - self.color_tab.get(it.as_str().unwrap_or("-1")).unwrap_or(&"ffffff".to_owned()).to_owned() - } - _ => "ffffff".to_string(), - }; + let col = j.pointer("/col").map(|it| it.as_str().unwrap_or("-1")).unwrap_or("-1"); + let col = self.color_tab.get(col).unwrap_or(&"ffffff"); d.insert("color".to_owned(), col.to_string()); } ret.push(d); @@ -116,11 +100,7 @@ impl Douyu { } Ok(ret) } - pub async fn run( - &self, - url: &str, - dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { + pub async fn run(&self, url: &str, dtx: async_channel::Sender<(String, String, String)>) -> anyhow::Result<()> { let (ws, reg_data) = self.get_ws_info(url).await?; let req = tokio_tungstenite::tungstenite::http::Request::builder() .method("GET") @@ -137,51 +117,35 @@ impl Douyu { .body(())?; let (ws_stream, _) = tokio_tungstenite::connect_async(req).await?; let (mut ws_write, mut ws_read) = ws_stream.split(); - ws_write - .send(tokio_tungstenite::tungstenite::Message::Binary( - reg_data[0].to_vec(), - )) - .await?; - ws_write - .send(tokio_tungstenite::tungstenite::Message::Binary( - reg_data[1].to_vec(), - )) - .await?; - let hb = self.heartbeat.clone(); - tokio::spawn(async move { - loop { - sleep(tokio::time::Duration::from_secs(20)).await; - let hb1 = hb.clone(); - match ws_write.send(tokio_tungstenite::tungstenite::Message::Binary(hb1)).await { - Ok(_) => {} - _ => { - println!("send heartbeat failed!") - } - }; + ws_write.send(Binary(reg_data[0].to_vec())).await?; + ws_write.send(Binary(reg_data[1].to_vec())).await?; + let hb_task = async { + while let Ok(_) = ws_write.send(Binary(HEARTBEAT.to_vec())).await { + sleep(Duration::from_secs(20)).await; } - }); - while let Some(m) = ws_read.next().await { - match m { - Ok(it) => { - if let Ok(mut dm) = self.decode_msg(it.into_data().as_mut()) { - for d in dm.drain(..) { - if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { - dtx.send(( - d.get("color").unwrap_or(&"ffffff".into()).into(), - d.get("name").unwrap_or(&"unknown".into()).into(), - d.get("content").unwrap_or(&" ".into()).into(), - )) - .await?; - } - } + Err(anyhow::anyhow!("send heartbeat failed!")) + }; + let recv_task = async { + while let Some(m) = ws_read.next().await { + let m = m?; + let mut dm = self.decode_msg(m.into_data().as_mut())?; + for mut d in dm.drain(..) { + if d.remove("msg_type").unwrap_or("other".into()).eq("danmaku") { + dtx.send(( + d.remove("color").unwrap_or("ffffff".into()), + d.remove("name").unwrap_or("unknown".into()), + d.remove("content").unwrap_or("".into()), + )) + .await?; } } - Err(e) => { - println!("read ws error: {:?}", e) - } } + anyhow::Ok(()) + }; + tokio::select! { + it = hb_task => { it?; }, + it = recv_task => { it?; }, } - println!("ws closed!"); Ok(()) } } diff --git a/src/danmaku/fudujikiller.rs b/src/danmaku/fudujikiller.rs index be69f5c..c19f20b 100644 --- a/src/danmaku/fudujikiller.rs +++ b/src/danmaku/fudujikiller.rs @@ -1,30 +1,31 @@ use std::{ - collections::HashMap, - rc::Rc, + cell::RefCell, + collections::{hash_map::DefaultHasher, HashMap}, + hash::{Hash, Hasher}, time::Instant, }; - -use tokio::sync::RwLock; - pub struct FudujiKiller { start_time: Instant, - dm_stats: RwLock, (u128, i64)>>, // time and count + dm_stats: RefCell>, // time and count } impl FudujiKiller { pub fn new() -> Self { Self { start_time: Instant::now(), - dm_stats: RwLock::new(HashMap::new()), + dm_stats: RefCell::new(HashMap::new()), } } - pub async fn dm_check(&self, dm: Rc) -> bool { + pub async fn dm_check(&self, dm: &str) -> bool { + let mut s = DefaultHasher::new(); + dm.hash(&mut s); + let dm_hash = s.finish(); let mut ret = true; let now = self.start_time.elapsed().as_millis(); - let mut dmst = self.dm_stats.write().await; - match dmst.get_mut(&dm) { + let mut dmst = self.dm_stats.borrow_mut(); + match dmst.get_mut(&dm_hash) { Some(it) => { if now > it.0 + 3000 { it.0 = now; @@ -37,7 +38,7 @@ impl FudujiKiller { } } None => { - dmst.insert(dm, (now, 1)); + dmst.insert(dm_hash, (now, 1)); } } // warn!("dm_stats len: {}", dmst.len()); diff --git a/src/danmaku/huya.rs b/src/danmaku/huya.rs index e32d75b..6685d38 100644 --- a/src/danmaku/huya.rs +++ b/src/danmaku/huya.rs @@ -1,13 +1,17 @@ -use futures::{ - stream::StreamExt, - SinkExt, -}; +use futures::{stream::StreamExt, SinkExt}; +use log::info; use regex::Regex; use reqwest::Url; -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; use tars_stream::prelude::*; use tokio::time::sleep; use tokio_tungstenite::connect_async; +use tokio_tungstenite::tungstenite::Message::Binary; + +use crate::dmlerr; + +const HEARTBEAT: &'static [u8] = + b"\x00\x03\x1d\x00\x00\x69\x00\x00\x00\x69\x10\x03\x2c\x3c\x4c\x56\x08\x6f\x6e\x6c\x69\x6e\x65\x75\x69\x66\x0f\x4f\x6e\x55\x73\x65\x72\x48\x65\x61\x72\x74\x42\x65\x61\x74\x7d\x00\x00\x3c\x08\x00\x01\x06\x04\x74\x52\x65\x71\x1d\x00\x00\x2f\x0a\x0a\x0c\x16\x00\x26\x00\x36\x07\x61\x64\x72\x5f\x77\x61\x70\x46\x00\x0b\x12\x03\xae\xf0\x0f\x22\x03\xae\xf0\x0f\x3c\x42\x6d\x52\x02\x60\x5c\x60\x01\x7c\x82\x00\x0b\xb0\x1f\x9c\xac\x0b\x8c\x98\x0c\xa8\x0c"; struct HuyaUser { _uid: i64, @@ -41,20 +45,16 @@ impl StructFromTars for HuyaDanmaku { } } -pub struct Huya { - heartbeat: Vec, -} +pub struct Huya {} impl Huya { pub fn new() -> Self { - let heartbeat = - b"\x00\x03\x1d\x00\x00\x69\x00\x00\x00\x69\x10\x03\x2c\x3c\x4c\x56\x08\x6f\x6e\x6c\x69\x6e\x65\x75\x69\x66\x0f\x4f\x6e\x55\x73\x65\x72\x48\x65\x61\x72\x74\x42\x65\x61\x74\x7d\x00\x00\x3c\x08\x00\x01\x06\x04\x74\x52\x65\x71\x1d\x00\x00\x2f\x0a\x0a\x0c\x16\x00\x26\x00\x36\x07\x61\x64\x72\x5f\x77\x61\x70\x46\x00\x0b\x12\x03\xae\xf0\x0f\x22\x03\xae\xf0\x0f\x3c\x42\x6d\x52\x02\x60\x5c\x60\x01\x7c\x82\x00\x0b\xb0\x1f\x9c\xac\x0b\x8c\x98\x0c\xa8\x0c".to_vec(); - Huya { heartbeat } + Huya {} } - async fn get_ws_info(&self, url: &str) -> Result<(String, Vec), Box> { + async fn get_ws_info(&self, url: &str) -> anyhow::Result<(String, Vec)> { let url = Url::parse(url)?; - let rid = url.path_segments().ok_or("gwi err a1")?.last().ok_or("gwi err a12")?; + let rid = url.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?; let client = reqwest::Client::new(); let resp = client .get(format!("https://www.huya.com/{}", &rid)) @@ -65,12 +65,13 @@ impl Huya { .text() .await?; let re = Regex::new(r"var\s+TT_PROFILE_INFO\s+=\s+(.+\});").unwrap(); - let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or("gwi err b1")?[1])?; - let ayyuid = j.pointer("/lp").ok_or("gwi err b2")?.to_string().replace(r#"""#, ""); + let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; + let ayyuid = j.pointer("/lp").ok_or_else(|| dmlerr!())?.to_string().replace(r#"""#, ""); let mut t = Vec::new(); t.push(format!("live:{}", ayyuid)); t.push(format!("chat:{}", ayyuid)); + info!("huya reg data: {:?}", &t); let mut oos = TarsEncoder::new(); oos.write_list(0, &t)?; oos.write_string(1, &"".to_owned())?; @@ -84,7 +85,7 @@ impl Huya { )) } - fn decode_msg(&self, data: &mut Vec) -> Result>, Box> { + fn decode_msg(&self, data: &mut Vec) -> anyhow::Result>> { let mut ret = Vec::new(); // println!("{}", String::from_utf8_lossy(&data)); let mut ios = TarsDecoder::from(data.to_owned()); @@ -123,67 +124,44 @@ impl Huya { dm.insert("msg_type".to_owned(), "danmaku".to_owned()); } } - match dm.get("name") { - Some(it) => { - if !it.trim().is_empty() { - // println!("{:?}", &dm); - ret.push(dm); - } - } - _ => { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - "parse failed", - ))); - } + if dm.get("name").map(|it| !it.trim().is_empty()).unwrap_or(false) { + ret.push(dm); }; Ok(ret) } - pub async fn run( - &self, - url: &str, - dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { + pub async fn run(&self, url: &str, dtx: async_channel::Sender<(String, String, String)>) -> anyhow::Result<()> { let (ws, reg_data) = self.get_ws_info(url).await?; let (ws_stream, _) = connect_async(&ws).await?; let (mut ws_write, mut ws_read) = ws_stream.split(); ws_write.send(tokio_tungstenite::tungstenite::Message::Binary(reg_data)).await?; - let hb = self.heartbeat.clone(); - tokio::spawn(async move { - loop { - sleep(tokio::time::Duration::from_secs(20)).await; - let hb1 = hb.clone(); - match ws_write.send(tokio_tungstenite::tungstenite::Message::Binary(hb1)).await { - Ok(_) => {} - _ => { - println!("send heartbeat failed!") - } - }; + let hb_task = async { + while let Ok(_) = ws_write.send(Binary(HEARTBEAT.to_vec())).await { + sleep(Duration::from_secs(20)).await; } - }); - while let Some(m) = ws_read.next().await { - match m { - Ok(it) => { - if let Ok(mut dm) = self.decode_msg(it.into_data().as_mut()) { - for d in dm.drain(..) { - if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { - dtx.send(( - d.get("color").unwrap_or(&"ffffff".into()).into(), - d.get("name").unwrap_or(&"unknown".into()).into(), - d.get("content").unwrap_or(&" ".into()).into(), - )) - .await?; - } - } + Err(anyhow::anyhow!("send heartbeat failed!")) + }; + let recv_task = async { + while let Some(m) = ws_read.next().await { + let m = m?; + let mut dm = self.decode_msg(m.into_data().as_mut())?; + for mut d in dm.drain(..) { + if d.remove("msg_type").unwrap_or("other".into()).eq("danmaku") { + dtx.send(( + d.remove("color").unwrap_or("ffffff".into()), + d.remove("name").unwrap_or("unknown".into()), + d.remove("content").unwrap_or("".into()), + )) + .await?; } } - Err(e) => { - println!("read ws error: {:?}", e) - } } + anyhow::Ok(()) + }; + tokio::select! { + it = hb_task => { it?; }, + it = recv_task => { it?; }, } - println!("ws closed!"); Ok(()) } } diff --git a/src/danmaku/mod.rs b/src/danmaku/mod.rs index a636d2a..0428a9c 100644 --- a/src/danmaku/mod.rs +++ b/src/danmaku/mod.rs @@ -7,84 +7,128 @@ mod mkv_header; mod twitch; mod youtube; +use crate::ipcmanager::IPCManager; use crate::{config::ConfigManager, dmlive::DMLMessage, ipcmanager::DMLStream}; use anyhow::anyhow; use anyhow::Result; use async_channel::Sender; use chrono::{Duration, NaiveTime}; use log::info; -use log::warn; +use std::cell::Cell; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::collections::VecDeque; +use std::ops::BitXor; use std::rc::Rc; -use std::{collections::BTreeMap, ops::BitXorAssign, sync::Arc}; -use tokio::{io::AsyncWriteExt, sync::RwLock, task::spawn_local}; +use tokio::io::AsyncWriteExt; + +const ASS_HEADER_TEXT: &'static str = r#"[Script Info] +; Script generated by dmlive +; https://github.com/THMonster/Revda +Title: Danmaku file +ScriptType: v4.00+ +WrapStyle: 0 +ScaledBorderAndShadow: yes +YCbCr Matrix: None +PlayResX: 1920 +PlayResY: 1080 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Sans,40,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,7,0,0,0,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +"#; +const EMOJI_RE: &'static str = r#"[\x{1F300}-\x{1F5FF}|\x{1F1E6}-\x{1F1FF}|\x{2700}-\x{27BF}|\x{1F900}-\x{1F9FF}|\x{1F600}-\x{1F64F}|\x{1F680}-\x{1F6FF}|\x{2600}-\x{26FF}]"#; #[derive(Clone, Debug)] struct DanmakuChannel { length: usize, - begin_pts: usize, + begin_pts: u64, } pub struct Danmaku { - ipc_manager: Arc, - cm: Arc, - show_nick: Arc>, - font_size: Arc>, - channel_num: Arc>, - bili_video_cid: RwLock, + ipc_manager: Rc, + cm: Rc, + show_nick: Cell, + font_size: Cell, + channel_num: Cell, + ratio_scale: Cell, + read_order: Cell, + bili_video_cid: RefCell, + dchannels: RefCell>, fk: fudujikiller::FudujiKiller, } impl Danmaku { - pub fn new(cm: Arc, im: Arc, _mtx: Sender) -> Self { + pub fn new(cm: Rc, im: Rc, _mtx: Sender) -> Self { + let font_size = (40.0 * cm.font_scale.get()) as usize; + let ch = vec![ + DanmakuChannel { + length: 0, + begin_pts: 0 + }; + 30 + ]; Self { ipc_manager: im, cm, - show_nick: Arc::new(RwLock::new(false)), - font_size: Arc::new(RwLock::new(40)), - channel_num: Arc::new(RwLock::new(14)), + show_nick: Cell::new(false), + font_size: Cell::new(font_size), + channel_num: Cell::new((540.0 / font_size as f64).ceil() as usize), + ratio_scale: Cell::new(1.0), + read_order: Cell::new(0), fk: fudujikiller::FudujiKiller::new(), - bili_video_cid: RwLock::new("".into()), + bili_video_cid: RefCell::new("".into()), + dchannels: RefCell::new(ch), + } + } + + pub fn reset(&self) { + for it in self.dchannels.borrow_mut().iter_mut() { + it.length = 0; + it.begin_pts = 0; } + self.read_order.set(0) } pub async fn set_speed(&self, speed: u64) { - if speed > 1000 { - *self.cm.danmaku_speed.write().await = speed; + if (1000..30000).contains(&speed) { + self.cm.danmaku_speed.set(speed); let _ = self.cm.write_config().await; } } pub async fn set_font_size(&self, font_scale: f64) { if font_scale > 0.0 { - *self.font_size.write().await = (40_f64 * font_scale) as usize; - *self.channel_num.write().await = (540.0 / *self.font_size.read().await as f64).ceil() as usize; - *self.cm.font_scale.write().await = font_scale; + self.font_size.set((40.0 * font_scale) as usize); + self.channel_num.set((540.0 / self.font_size.get() as f64).ceil() as usize); + self.cm.font_scale.set(font_scale); let _ = self.cm.write_config().await; } } pub async fn set_font_alpha(&self, font_alpha: f64) { if (0.0..=1.0).contains(&font_alpha) { - *self.cm.font_alpha.write().await = font_alpha; + self.cm.font_alpha.set(font_alpha); let _ = self.cm.write_config().await; } } - pub async fn set_bili_video_cid(self: &Arc, cid: &str) { - let mut bvc = self.bili_video_cid.write().await; + pub async fn set_bili_video_cid(&self, cid: &str) { + let mut bvc = self.bili_video_cid.borrow_mut(); bvc.clear(); bvc.push_str(cid); } pub async fn toggle_show_nick(&self) { - self.show_nick.write().await.bitxor_assign(true); + self.show_nick.set(self.show_nick.get().bitxor(true)); } - async fn get_avail_danmaku_channel( - &self, c_pts: usize, len: usize, channels: &mut [DanmakuChannel], - ) -> Option { - let s = (1920.0 + len as f64) / *self.cm.danmaku_speed.read().await as f64; - for (i, c) in channels.iter_mut().enumerate() { - if i >= *self.channel_num.read().await { + fn get_avail_danmaku_channel(&self, c_pts: u64, len: usize) -> Option { + let s = (1920.0 + len as f64) / self.cm.danmaku_speed.get() as f64; + for (i, c) in self.dchannels.borrow_mut().iter_mut().enumerate() { + if i >= self.channel_num.get() { break; } if c.length == 0 { @@ -92,10 +136,10 @@ impl Danmaku { c.begin_pts = c_pts; return Some(i); } - if ((*self.cm.danmaku_speed.read().await as f64 - c_pts as f64 + c.begin_pts as f64) * s) > 1920.0 { + if ((self.cm.danmaku_speed.get() as f64 - c_pts as f64 + c.begin_pts as f64) * s) > 1920.0 { continue; } else if ((c.length + 1920) as f64 * (c_pts as f64 - c.begin_pts as f64) - / *self.cm.danmaku_speed.read().await as f64) + / self.cm.danmaku_speed.get() as f64) < c.length as f64 { continue; @@ -108,7 +152,7 @@ impl Danmaku { None } - async fn get_danmaku_display_length(&self, nick: &str, dm: &str, ratio_scale: f64) -> usize { + fn get_danmaku_display_length(&self, nick: &str, dm: &str) -> usize { let mut ascii_num = 0; let mut non_ascii_num = 0; for c in dm.chars() { @@ -118,7 +162,7 @@ impl Danmaku { non_ascii_num += 1; } } - if *self.show_nick.read().await { + if self.show_nick.get() { for c in nick.chars() { if c.is_ascii() { ascii_num += 1; @@ -128,56 +172,67 @@ impl Danmaku { } non_ascii_num += 1; } - let fs = *self.font_size.read().await; - (((fs as f64 * 0.75 * non_ascii_num as f64) + (fs as f64 * 0.50 * ascii_num as f64)) * ratio_scale).round() - as usize + let fs = self.font_size.get(); + (((fs as f64 * 0.75 * non_ascii_num as f64) + (fs as f64 * 0.50 * ascii_num as f64)) * self.ratio_scale.get()) + .round() as usize } async fn launch_danmaku( - &self, c: &str, n: &str, d: &str, c_pts: u64, ratio_scale: f64, channels: &mut [DanmakuChannel], - read_order: &mut usize, socket: &mut Box, + &self, c: &str, n: &str, d: &str, c_pts: u64, socket: &mut Box, ) -> Result<()> { + let mut out_of_channel = false; let cluster = if n.trim().is_empty() { - let ass = format!(r#"{},0,Default,dmlive-empty,20,20,2,,"#, *read_order,).into_bytes(); - mkv_header::DMKVCluster::new(ass, c_pts, 1) - } else { - let display_length = self.get_danmaku_display_length(n, d, ratio_scale).await; - let avail_dc = self - .get_avail_danmaku_channel(c_pts as usize, display_length, channels) - .await - .ok_or(anyhow!("ld err 1"))?; let ass = format!( - r#"{4},0,Default,{5},0,0,0,,{{\alpha{0}\fs{7}\1c&{6}&\move(1920,{1},{2},{1})}}{8}{9}{3}"#, - format_args!("{:02x}", (*self.cm.font_alpha.read().await * 255_f64) as u8), - avail_dc * *self.font_size.read().await, - 0 - display_length as isize, - &d, - *read_order, - &n, - format!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), - *self.font_size.read().await, - if *self.show_nick.read().await { n } else { "" }, - if *self.show_nick.read().await { ": " } else { "" }, + r#"{},0,Default,dmlive-empty,20,20,2,,"#, + self.read_order.get() ) .into_bytes(); - mkv_header::DMKVCluster::new(ass, c_pts, *self.cm.danmaku_speed.read().await) + mkv_header::DMKVCluster::new(ass, c_pts, 1) + } else { + let display_length = self.get_danmaku_display_length(n, d); + let avail_dc = self.get_avail_danmaku_channel(c_pts, display_length); + match avail_dc { + Some(avail_dc) => { + let ass = format!( + r#"{4},0,Default,{5},0,0,0,,{{\alpha{0}\fs{7}\1c&{6}&\move(1920,{1},{2},{1})}}{8}{9}{3}"#, + format_args!("{:02x}", (self.cm.font_alpha.get() * 255_f64) as u8), + avail_dc * self.font_size.get(), + 0 - display_length as isize, + &d, + self.read_order.get(), + &n, + format!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), + self.font_size.get(), + if self.show_nick.get() { n } else { "" }, + if self.show_nick.get() { ": " } else { "" }, + ) + .into_bytes(); + mkv_header::DMKVCluster::new(ass, c_pts, self.cm.danmaku_speed.get()) + } + None => { + out_of_channel = true; + let ass = format!( + r#"{},0,Default,dmlive-empty,20,20,2,,"#, + self.read_order.get() + ) + .into_bytes(); + mkv_header::DMKVCluster::new(ass, c_pts, 1) + } + } }; - *read_order = read_order.saturating_add(1); + self.read_order.set(self.read_order.get() + 1); match cluster.write_to_socket(socket).await { Ok(_) => {} Err(_) => return Err(anyhow!("socket error")), }; - Ok(()) - } - - async fn init(&self) { - *self.font_size.write().await = (40_f64 * *self.cm.font_scale.read().await) as usize; - *self.channel_num.write().await = (540.0 / *self.font_size.read().await as f64).ceil() as usize; + if out_of_channel { + Err(anyhow!("no available channel")) + } else { + Ok(()) + } } - pub async fn run_danmaku_client( - self: &Arc, dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<()> { + pub async fn danmaku_client_task(&self, dtx: async_channel::Sender<(String, String, String)>) -> Result<()> { loop { match match self.cm.site { crate::config::Site::BiliLive => { @@ -186,7 +241,15 @@ impl Danmaku { } crate::config::Site::BiliVideo => { let b = bilivideo::Bilibili::new(); - b.run(&self.cm.room_url, dtx.clone()).await + b.run( + format!( + "http://api.bilibili.com/x/v1/dm/list.so?oid={}", + self.bili_video_cid.borrow() + ) + .as_str(), + dtx.clone(), + ) + .await } crate::config::Site::DouyuLive => { let b = douyu::Douyu::new(); @@ -207,7 +270,7 @@ impl Danmaku { } { Ok(_) => {} Err(e) => { - warn!("danmaku client error: {:?}", e); + info!("danmaku client error: {:?}", e); } }; if dtx.is_closed() { @@ -215,110 +278,98 @@ impl Danmaku { } tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } - warn!("danmaku client exited."); + info!("danmaku client exited."); Ok(()) } - pub async fn run_bilivideo(self: &Arc, ratio_scale: f64) -> Result<()> { - self.init().await; - // *self.channel_num.write().await = 30; - info!("ratio: {}", &ratio_scale); + async fn fire_danmaku_task(&self, rx: async_channel::Receiver<(String, String, String)>) -> Result<()> { + let now = std::time::Instant::now(); let mut socket = self.ipc_manager.get_danmaku_socket().await?; - let mut dchannels = vec![ - DanmakuChannel { - length: 0, - begin_pts: 0 - }; - 30 - ]; - let (dtx, drx) = async_channel::unbounded(); - let cid = self.bili_video_cid.read().await.clone(); - let _ = spawn_local(async move { - let b = bilivideo::Bilibili::new(); - match b - .run( - // format!("https://comment.bilibili.com/{}.xml", cid).as_str(), - format!("http://api.bilibili.com/x/v1/dm/list.so?oid={}", cid).as_str(), - dtx.clone(), - ) - .await - { - Ok(_) => {} - Err(err) => { - info!("bilivideo danmaku err: {}", err) + let mut dm_queue = VecDeque::new(); + let emoji_re = regex::Regex::new(EMOJI_RE).unwrap(); + let empty_dm = ("".to_string(), "".to_string(), "".to_string()); + let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(200)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + socket.write_all(&mkv_header::get_mkv_header()).await?; + loop { + while let Ok(it) = rx.try_recv() { + dm_queue.push_back(it); + } + let (co, ni, da) = dm_queue.get(0).unwrap_or(&empty_dm); + if !da.is_empty() { + if !self.cm.quiet { + println!("[{}] {}", &ni, &da); + } + if !self.fk.dm_check(da).await { + continue; } } - }); - - socket.write_all(r#"[Script Info] -; Script generated by QLivePlayer -; https://github.com/THMonster/QLivePlayer -Title: Danmaku file -ScriptType: v4.00+ -WrapStyle: 0 -ScaledBorderAndShadow: yes -YCbCr Matrix: None -PlayResX: 1920 -PlayResY: 1080 - -[V4+ Styles] -Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,Sans,40,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,7,0,0,0,1 + let da = emoji_re.replace_all(da, "[em]"); + let c_pts = now.elapsed().as_millis() as u64; + match self.launch_danmaku(co, ni, &da, c_pts, &mut socket).await { + Ok(_) => { + let _ = dm_queue.pop_front(); + } + Err(e) => { + info!("danmaku send error: {}", &e); + if e.to_string().contains("socket error") { + break; + } + } + }; + if self.read_order.get() > 70 { + interval.tick().await; + } + } + Ok(()) + } -[Events] -Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text -"#.as_bytes()).await?; - let mut dm_hashmap: BTreeMap = BTreeMap::new(); - while let Ok((c, n, d)) = drx.recv().await { + async fn fire_bvideo_danmaku_task(&self, rx: async_channel::Receiver<(String, String, String)>) -> Result<()> { + let mut socket = self.ipc_manager.get_danmaku_socket().await?; + let mut dm_map: BTreeMap = BTreeMap::new(); + while let Ok((c, n, d)) = rx.recv().await { let tmps: Vec<&str> = n.split(',').collect(); - dm_hashmap.insert( + dm_map.insert( (tmps[0].parse::().unwrap() * 1000.0) as i64, (c.to_string(), tmps[1].to_string(), d.to_string()), ); } - for (k, (c, t, d)) in dm_hashmap.into_iter() { + socket.write_all(ASS_HEADER_TEXT.as_bytes()).await?; + for (k, (c, t, d)) in dm_map.into_iter() { info!("{}-{}-{}-{}", &k, &c, &t, &d); let t1 = NaiveTime::from_hms_opt(0, 0, 0).unwrap() + Duration::milliseconds(k); - let t2 = t1 + Duration::milliseconds(*self.cm.danmaku_speed.read().await as i64); + let t2 = t1 + Duration::milliseconds(self.cm.danmaku_speed.get() as i64); let mut t1_s = t1.format("%k:%M:%S%.3f").to_string(); let mut t2_s = t2.format("%k:%M:%S%.3f").to_string(); t1_s.remove(t1_s.len() - 1); t2_s.remove(t2_s.len() - 1); if t.trim().eq("4") { - socket - .write_all( - format!( - r#"Dialogue: 0,{4},{5},Default,,0,0,0,,{{\alpha{0}\fs{3}\1c&{2}&\an2}}{1}"#, - format_args!("{:02x}", (*self.cm.font_alpha.read().await * 255_f64) as u8), - &d, - format_args!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), - *self.font_size.read().await, - t1_s, - t2_s, - ) - .as_bytes(), - ) - .await?; + let ass = format!( + r#"Dialogue: 0,{4},{5},Default,,0,0,0,,{{\alpha{0}\fs{3}\1c&{2}&\an2}}{1}"#, + format_args!("{:02x}", (self.cm.font_alpha.get() * 255_f64) as u8), + &d, + format_args!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), + self.font_size.get(), + t1_s, + t2_s, + ); + socket.write_all(ass.as_bytes()).await?; socket.write_all("\n".as_bytes()).await?; } else if t.trim().eq("5") { - socket - .write_all( - format!( - r#"Dialogue: 0,{4},{5},Default,,0,0,0,,{{\alpha{0}\fs{3}\1c&{2}&\an8}}{1}"#, - format_args!("{:02x}", (*self.cm.font_alpha.read().await * 255_f64) as u8), - &d, - format!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), - *self.font_size.read().await, - t1_s, - t2_s, - ) - .as_bytes(), - ) - .await?; + let ass = format!( + r#"Dialogue: 0,{4},{5},Default,,0,0,0,,{{\alpha{0}\fs{3}\1c&{2}&\an8}}{1}"#, + format_args!("{:02x}", (self.cm.font_alpha.get() * 255_f64) as u8), + &d, + format!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), + self.font_size.get(), + t1_s, + t2_s, + ); + socket.write_all(ass.as_bytes()).await?; socket.write_all("\n".as_bytes()).await?; } else { - let display_length = self.get_danmaku_display_length("", &d, ratio_scale).await; - let avail_dc = match self.get_avail_danmaku_channel(k as usize, display_length, &mut dchannels).await { + let display_length = self.get_danmaku_display_length("", &d); + let avail_dc = match self.get_avail_danmaku_channel(k as u64, display_length) { Some(it) => it, None => { continue; @@ -326,14 +377,14 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text }; let ass = format!( r#"Dialogue: 0,{4},{5},Default,,0,0,0,,{{\alpha{0}\fs{7}\1c&{6}&\move(1920,{1},{2},{1})}}{3}"#, - format_args!("{:02x}", (*self.cm.font_alpha.read().await * 255_f64) as u8), - avail_dc * *self.font_size.read().await, + format_args!("{:02x}", (self.cm.font_alpha.get() * 255_f64) as u8), + avail_dc * self.font_size.get(), 0 - display_length as isize, &d, t1_s, t2_s, format!("{}{}{}", &c[4..6], &c[2..4], &c[0..2]), - *self.font_size.read().await, + self.font_size.get(), ); socket.write_all(ass.as_bytes()).await?; socket.write_all("\n".as_bytes()).await?; @@ -342,108 +393,30 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text Ok(()) } - pub async fn run(self: &Arc, ratio_scale: f64, _start_pts: u64) -> Result<()> { - let now = std::time::Instant::now(); - self.init().await; - let mut socket = self.ipc_manager.get_danmaku_socket().await?; - let mut read_order = 0usize; - let emoji_re = regex::Regex::new( - r#"[\x{1F300}-\x{1F5FF}|\x{1F1E6}-\x{1F1FF}|\x{2700}-\x{27BF}|\x{1F900}-\x{1F9FF}|\x{1F600}-\x{1F64F}|\x{1F680}-\x{1F6FF}|\x{2600}-\x{26FF}]"#) - .unwrap(); - let mut dchannels = vec![ - DanmakuChannel { - length: 0, - begin_pts: 0 - }; - 30 - ]; + pub async fn run_bilivideo(&self, ratio_scale: f64) -> Result<()> { + info!("ratio: {}", &ratio_scale); + self.reset(); + self.ratio_scale.set(ratio_scale); let (dtx, drx) = async_channel::unbounded(); - let s1 = self.clone(); - let dc = spawn_local(async move { - let _ = s1.run_danmaku_client(dtx).await; - }); + let (dc_res, fbd_res) = tokio::join!( + self.danmaku_client_task(dtx), + self.fire_bvideo_danmaku_task(drx) + ); + dc_res?; + fbd_res?; + info!("bilibili video danmaku exited"); + Ok(()) + } - socket.write_all(&mkv_header::get_mkv_header()).await?; - // wu~wu~ warm up - for _ in 1..77 { - let _ = self - .launch_danmaku( - "", - "", - "", - now.elapsed().as_millis() as u64, - ratio_scale, - &mut dchannels, - &mut read_order, - &mut socket, - ) - .await; - } - 'l1: loop { - if let Ok((c, n, d)) = drx.try_recv() { - if !self.cm.quiet { - println!("[{}] {}", &n, &d); - } - let d = Rc::new(d); - if !self.fk.dm_check(d.clone()).await { - continue; - } - let d = emoji_re.replace_all(&d, "[em]"); - loop { - match self - .launch_danmaku( - &c, - &n, - &d, - now.elapsed().as_millis() as u64, - ratio_scale, - &mut dchannels, - &mut read_order, - &mut socket, - ) - .await - { - Ok(_) => { - break; - } - Err(e) => { - info!("danmaku send error: {}", &e); - let _ = self - .launch_danmaku( - "", - "", - "", - now.elapsed().as_millis() as u64, - ratio_scale, - &mut dchannels, - &mut read_order, - &mut socket, - ) - .await; - if e.to_string().contains("socket error") { - break 'l1; - } - tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; - } - }; - } - } else { - let _ = self - .launch_danmaku( - "", - "", - "", - now.elapsed().as_millis() as u64, - ratio_scale, - &mut dchannels, - &mut read_order, - &mut socket, - ) - .await; - tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; - } + pub async fn run(&self, ratio_scale: f64, _start_pts: u64) -> Result<()> { + self.reset(); + self.ratio_scale.set(ratio_scale); + let (dtx, drx) = async_channel::unbounded(); + tokio::select! { + it = self.danmaku_client_task(dtx) => { it?; }, + it = self.fire_danmaku_task(drx) => { it?; }, } - dc.abort(); + info!("danmaku exited"); Ok(()) } } diff --git a/src/danmaku/twitch.rs b/src/danmaku/twitch.rs index abac6be..faed1dd 100644 --- a/src/danmaku/twitch.rs +++ b/src/danmaku/twitch.rs @@ -1,24 +1,23 @@ +use crate::dmlerr; use futures::{stream::StreamExt, SinkExt}; use regex::Regex; use reqwest::Url; use std::collections::HashMap; -use tokio::time::sleep; -use tokio_tungstenite::connect_async; +use tokio::time::{sleep, Duration}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; -pub struct Twitch { - heartbeat: String, -} +const HEARTBEAT: &'static str = "PING"; + +pub struct Twitch {} impl Twitch { pub fn new() -> Self { - Twitch { - heartbeat: "PING".to_string(), - } + Self {} } - async fn get_ws_info(&self, url: &str) -> Result<(String, Vec), Box> { + async fn get_ws_info(&self, url: &str) -> anyhow::Result<(String, Vec)> { let rid = - Url::parse(url)?.path_segments().ok_or("rid parse error 1")?.last().ok_or("rid parse error 2")?.to_string(); + Url::parse(url)?.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?.to_string(); let mut reg_datas: Vec = Vec::new(); reg_datas.push("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership".to_owned()); @@ -32,7 +31,7 @@ impl Twitch { Ok(("wss://irc-ws.chat.twitch.tv".to_string(), reg_datas)) } - fn decode_msg(&self, data: &mut [u8]) -> Result>, Box> { + fn decode_msg(&self, data: &mut [u8]) -> anyhow::Result>> { let mut ret = Vec::new(); let msg = String::from_utf8_lossy(data); for m in msg.split('\n') { @@ -61,52 +60,40 @@ impl Twitch { Ok(ret) } - pub async fn run( - &self, - url: &str, - dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { + pub async fn run(&self, url: &str, dtx: async_channel::Sender<(String, String, String)>) -> anyhow::Result<()> { let (ws, mut reg_datas) = self.get_ws_info(url).await?; let (ws_stream, _) = connect_async(&ws).await?; let (mut ws_write, mut ws_read) = ws_stream.split(); for reg_data in reg_datas.drain(..) { - ws_write.send(tokio_tungstenite::tungstenite::Message::text(reg_data)).await?; + ws_write.send(Message::text(reg_data)).await?; } - let hb = self.heartbeat.clone(); - tokio::spawn(async move { - loop { - sleep(tokio::time::Duration::from_secs(20)).await; - let hb1 = hb.clone(); - match ws_write.send(tokio_tungstenite::tungstenite::Message::text(hb1)).await { - Ok(_) => {} - _ => { - println!("send heartbeat failed!") - } - }; + let hb_task = async { + while let Ok(_) = ws_write.send(Message::text(HEARTBEAT)).await { + sleep(Duration::from_secs(20)).await; } - }); - while let Some(m) = ws_read.next().await { - match m { - Ok(it) => { - let mut dm = self.decode_msg(it.into_data().as_mut())?; - for d in dm.drain(..) { - if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { - dtx.send(( - d.get("color").unwrap_or(&"ffffff".into()).into(), - d.get("name").unwrap_or(&"unknown".into()).into(), - d.get("content").unwrap_or(&" ".into()).into(), - )) - .await?; - } + Err(anyhow::anyhow!("send heartbeat failed!")) + }; + let recv_task = async { + while let Some(m) = ws_read.next().await { + let m = m?; + let mut dm = self.decode_msg(m.into_data().as_mut())?; + for mut d in dm.drain(..) { + if d.remove("msg_type").unwrap_or("other".into()).eq("danmaku") { + dtx.send(( + d.remove("color").unwrap_or("ffffff".into()), + d.remove("name").unwrap_or("unknown".into()), + d.remove("content").unwrap_or("".into()), + )) + .await?; } } - Err(e) => { - println!("read ws error: {:?}", e); - break; - } } + anyhow::Ok(()) + }; + tokio::select! { + it = hb_task => { it?; }, + it = recv_task => { it?; }, } - println!("ws closed!"); Ok(()) } } diff --git a/src/danmaku/youtube.rs b/src/danmaku/youtube.rs index 9b8c545..553f606 100644 --- a/src/danmaku/youtube.rs +++ b/src/danmaku/youtube.rs @@ -1,20 +1,15 @@ -use base64::{ - engine::general_purpose, - Engine, -}; +use crate::{dmlerr, utils}; +use base64::{engine::general_purpose, Engine}; use chrono::prelude::*; use log::*; use regex::Regex; use reqwest::Client; -use serde_json::{ - json, - Value, -}; -use std::{ - collections::HashMap, - sync::Arc, -}; -use tokio::time::sleep; +use serde_json::{json, Value}; +use std::collections::HashMap; +use tokio::time::{sleep, Duration}; + +const YTB_KEY: &'static [u8] = + b"eW91dHViZWkvdjEvbGl2ZV9jaGF0L2dldF9saXZlX2NoYXQ/a2V5PUFJemFTeUFPX0ZKMlNscVU4UTRTVEVITEdDaWx3X1k5XzExcWNXOA=="; fn get_param(vid: &str, cid: &str) -> String { let ts = Utc::now().timestamp() as u64 * 1000000; @@ -81,21 +76,15 @@ pub struct Youtube { impl Youtube { pub fn new() -> Self { Youtube { - key: String::from_utf8_lossy( - general_purpose::STANDARD.decode(b"eW91dHViZWkvdjEvbGl2ZV9jaGF0L2dldF9saXZlX2NoYXQ/a2V5PUFJemFTeUFPX0ZKMlNscVU4UTRTVEVITEdDaWx3X1k5XzExcWNXOA==") - .unwrap() - .as_ref(), - ) - .to_string(), - ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36".to_owned(), + key: String::from_utf8_lossy(general_purpose::STANDARD.decode(YTB_KEY).unwrap().as_ref()).to_string(), + ua: utils::gen_ua(), } } - async fn get_room_info(&self, url: &str) -> Result<(String, String), Box> { - let client = reqwest::Client::new(); + async fn get_room_info(&self, url: &str, client: &Client) -> anyhow::Result<(String, String)> { let url = url::Url::parse(url)?; let room_url = if url.as_str().contains("youtube.com/channel/") { - let cid = url.path_segments().ok_or("gri err a1")?.last().ok_or("gri err a12")?; + let cid = url.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?; format!("https://www.youtube.com/channel/{}/live", &cid) } else { for q in url.query_pairs() { @@ -106,7 +95,7 @@ impl Youtube { }; let resp = client .get(&room_url) - .header("User-Agent", crate::utils::gen_ua()) + .header("Connection", "keep-alive") .header("Accept-Language", "en-US") .header("Referer", "https://www.youtube.com/") .send() @@ -114,29 +103,36 @@ impl Youtube { .text() .await?; let re = Regex::new(r"ytInitialPlayerResponse\s*=\s*(\{.+?\});.*?").unwrap(); - let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or("gri err b1")?[1])?; - let vid = j.pointer("/videoDetails/videoId").ok_or("gri err b2")?.as_str().unwrap().to_string(); - let cid = j.pointer("/videoDetails/channelId").ok_or("gri err b3")?.as_str().unwrap().to_string(); + let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; + let vid = j.pointer("/videoDetails/videoId").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let cid = j.pointer("/videoDetails/channelId").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); // println!("{} {}", &vid, &cid); Ok((vid, cid)) } - fn decode_msg(&self, j: &Value) -> Result, Box> { + fn decode_msg(&self, j: &Value) -> anyhow::Result> { let mut d = std::collections::HashMap::new(); - let renderer = j.pointer("/addChatItemAction/item/liveChatTextMessageRenderer").ok_or("dm err 1")?; + let renderer = j.pointer("/addChatItemAction/item/liveChatTextMessageRenderer").ok_or_else(|| dmlerr!())?; d.insert( "name".to_owned(), - renderer.pointer("/authorName/simpleText").ok_or("dm err 2")?.as_str().ok_or("dm err 2-2")?.to_string(), + renderer + .pointer("/authorName/simpleText") + .ok_or_else(|| dmlerr!())? + .as_str() + .ok_or_else(|| dmlerr!())? + .to_string(), ); - let runs = renderer.pointer("/message/runs").ok_or("dm err 3")?.as_array().ok_or("dm err 3-2")?; + let runs = renderer.pointer("/message/runs").ok_or_else(|| dmlerr!())?.as_array().ok_or_else(|| dmlerr!())?; let mut msg = "".to_owned(); for r in runs { match r.pointer("/emoji") { Some(it) => { - msg.push_str(it.pointer("/shortcuts/0").ok_or("dm err 4")?.as_str().ok_or("dm err 4-2")?); + msg.push_str( + it.pointer("/shortcuts/0").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, + ); } None => { - msg.push_str(r.pointer("/text").ok_or("dm err 5")?.as_str().ok_or("dm err 5-2")?); + msg.push_str(r.pointer("/text").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?); } } } @@ -145,17 +141,13 @@ impl Youtube { Ok(d) } - async fn get_single_chat( - &self, - ctn: &mut String, - client: Arc, - ) -> Result>, Box> { + async fn get_single_chat(&self, ctn: &mut String, client: &Client) -> anyhow::Result>> { let mut ret = Vec::new(); let body = json!({ "context": { "client": { "visitorData": "", - "userAgent": &self.ua, + "userAgent": self.ua, "clientName": "WEB", "clientVersion": format!("2.{}.01.00", (Utc::now() - chrono::Duration::days(2)).format("%Y%m%d")), }, @@ -165,11 +157,9 @@ impl Youtube { let body = serde_json::to_vec(&body)?; // println!("{}", String::from_utf8_lossy(&body)); - // let client = reqwest::Client::new(); let resp = client .post(format!("https://www.youtube.com/{}", &self.key)) .header("Connection", "keep-alive") - .header("User-Agent", &self.ua) .body(body) .send() .await? @@ -178,7 +168,8 @@ impl Youtube { ctn.clear(); // println!("{:#?}", &resp); - let con = resp.pointer("/continuationContents/liveChatContinuation/continuations/0").ok_or("gsc err 1")?; + let con = + resp.pointer("/continuationContents/liveChatContinuation/continuations/0").ok_or_else(|| dmlerr!())?; // println!("{:#?}", &con); let metadata = match con.pointer("/invalidationContinuationData") { @@ -187,16 +178,16 @@ impl Youtube { Some(it) => it, None => match con.pointer("/reloadContinuationData") { Some(it) => it, - None => con.pointer("/liveChatReplayContinuationData").ok_or("gsc err 2")?, + None => con.pointer("/liveChatReplayContinuationData").ok_or_else(|| dmlerr!())?, }, }, }; - ctn.push_str(metadata.pointer("/continuation").ok_or("gsc err 3")?.as_str().ok_or("gsc err 3-2")?); + ctn.push_str(metadata.pointer("/continuation").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?); let actions = resp .pointer("/continuationContents/liveChatContinuation/actions") - .ok_or("gsc err 4")? + .ok_or_else(|| dmlerr!())? .as_array() - .ok_or("gsc err 4-2")?; + .ok_or_else(|| dmlerr!())?; for action in actions { if let Ok(it) = self.decode_msg(action) { ret.push(it); @@ -206,43 +197,43 @@ impl Youtube { Ok(ret) } - pub async fn run( - &self, - url: &str, - dtx: async_channel::Sender<(String, String, String)>, - ) -> Result<(), Box> { - let (vid, cid) = self.get_room_info(url).await?; + pub async fn run(&self, url: &str, dtx: async_channel::Sender<(String, String, String)>) -> anyhow::Result<()> { + let client = + reqwest::Client::builder().user_agent(self.ua.clone()).connect_timeout(Duration::from_secs(10)).build()?; + let (vid, cid) = self.get_room_info(url, &client).await?; let mut ctn = get_param(&vid, &cid); - let http_client = Arc::new(reqwest::Client::new()); + let mut interval = tokio::time::interval(Duration::from_millis(2000)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); loop { + interval.tick().await; if ctn.trim().is_empty() { info!("ctn not found, regenerate..."); ctn.push_str(&get_param(&vid, &cid)); } - let interval: u64; - match self.get_single_chat(&mut ctn, http_client.clone()).await { + let itvl: u64; + match self.get_single_chat(&mut ctn, &client).await { Ok(mut dm) => { - if !dm.is_empty() { - interval = 2000 / dm.len() as u64; - for d in dm.drain(..) { - if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { - dtx.send(( - d.get("color").unwrap_or(&"ffffff".into()).into(), - d.get("name").unwrap_or(&"unknown".into()).into(), - d.get("content").unwrap_or(&" ".into()).into(), - )) - .await?; - sleep(tokio::time::Duration::from_millis(interval)).await; + itvl = 2000usize.saturating_div(if dm.len() == 0 { 1 } else { dm.len() }) as u64; + for d in dm.drain(..) { + if d.get("msg_type").unwrap_or(&"other".into()).eq("danmaku") { + dtx.send(( + d.get("color").unwrap_or(&"ffffff".into()).into(), + d.get("name").unwrap_or(&"unknown".into()).into(), + d.get("content").unwrap_or(&" ".into()).into(), + )) + .await?; + if itvl < 50 { + } else if itvl > 500 { + sleep(Duration::from_millis(500)).await; + } else { + sleep(Duration::from_millis(itvl)).await; } } - } else { - sleep(tokio::time::Duration::from_secs(2)).await; } } Err(e) => { - info!("{}", e); - sleep(tokio::time::Duration::from_secs(2)).await; + info!("get single chat error: {}", e); } } } diff --git a/src/dmlive.rs b/src/dmlive.rs index 51f5ccb..aa776f7 100644 --- a/src/dmlive.rs +++ b/src/dmlive.rs @@ -2,179 +2,194 @@ use crate::{ config::ConfigManager, danmaku::Danmaku, ffmpeg::FfmpegControl, ipcmanager::IPCManager, mpv::MpvControl, streamer::Streamer, streamfinder::StreamFinder, }; -use async_channel::Receiver; -use log::{info, warn}; -use std::{ops::Deref, sync::Arc}; -use tokio::sync::RwLock; +use async_channel::{Receiver, Sender}; +use futures::StreamExt; +use log::info; +use std::rc::Rc; +use tokio::time::Duration; +#[allow(unused)] pub enum DMLMessage { SetFontScale(f64), SetFontAlpha(f64), SetDMSpeed(u64), - GoToBVPage(usize), + PlayVideo, SetVideoInfo((u64, u64, u64)), ToggleShowNick, + FfmpegOutputReady, RequestRestart, RequestExit, } -enum DMLState { - Running, - Exiting, -} - +#[allow(unused)] pub struct DMLive { - ipc_manager: Arc, - cm: Arc, - mc: Arc, - fc: Arc, - sf: Arc, - st: Arc, - dm: Arc, + ipc_manager: Rc, + cm: Rc, + mc: Rc, + fc: Rc, + sf: Rc, + st: Rc, + dm: Rc, mrx: Receiver, - state: RwLock, + mtx: Sender, } impl DMLive { - pub async fn new(cm: Arc) -> Self { - let mut im = IPCManager::new(cm.clone()); - im.run().await.unwrap(); - let im = Arc::new(im); + pub async fn new(cm: Rc, im: Rc) -> Self { let (mtx, mrx) = async_channel::unbounded(); - let mc = Arc::new(MpvControl::new(cm.clone(), im.clone(), mtx.clone())); - let fc = Arc::new(FfmpegControl::new(cm.clone(), im.clone(), mtx.clone())); - let sf = Arc::new(StreamFinder::new(cm.clone(), im.clone(), mtx.clone())); - let st = Arc::new(Streamer::new(cm.clone(), im.clone(), mtx.clone())); - let dm = Arc::new(Danmaku::new(cm.clone(), im.clone(), mtx)); + let mc = Rc::new(MpvControl::new(cm.clone(), im.clone(), mtx.clone())); + let fc = Rc::new(FfmpegControl::new(cm.clone(), im.clone(), mtx.clone())); + let sf = Rc::new(StreamFinder::new(cm.clone(), im.clone(), mtx.clone())); + let st = Rc::new(Streamer::new(cm.clone(), im.clone(), mtx.clone())); + let dm = Rc::new(Danmaku::new(cm.clone(), im.clone(), mtx.clone())); DMLive { ipc_manager: im, cm, mrx, + mtx, mc, fc, sf, st, dm, - state: RwLock::new(DMLState::Running), } } - pub async fn run(self: &Arc) { - let s1 = self.clone(); - tokio::task::spawn_local(async move { - s1.dispatch().await; - }); - let s2 = self.clone(); - tokio::task::spawn_local(async move { - s2.restart().await; - }); - let s3 = self.clone(); - tokio::task::spawn_local(async move { + pub async fn run(&self) { + let signal_task = async { let _ = tokio::signal::ctrl_c().await; - s3.quit().await; - }); - let _ = self.mc.run().await; + }; + let play_task = async { + loop { + if self.play().await.map_err(|e| info!("play error: {}", e)).unwrap_or(false) { + break; + } + tokio::time::sleep(Duration::from_millis(1000)).await; + } + }; + tokio::select! { + _ = self.dispatch_task() => {}, + _ = self.mc.run() => {}, + _ = play_task => {}, + _ = signal_task => {}, + } match self.ipc_manager.stop().await { Ok(_) => {} Err(err) => info!("ipc manager stop error: {}", err), }; } - pub async fn dispatch(self: &Arc) { + async fn dispatch_task(&self) { + let mut tasks = futures::stream::FuturesUnordered::new(); + loop { - match self.mrx.recv().await.unwrap() { - DMLMessage::SetFontScale(fs) => { - self.dm.set_font_size(fs).await; - } - DMLMessage::SetFontAlpha(fa) => { - self.dm.set_font_alpha(fa).await; + tokio::select! { + Some(_) = tasks.next() => {}, + msg = self.mrx.recv() => { + match msg { + Ok(it) => { tasks.push(self.dispatch(it)) }, + Err(_) => { return; }, + } } - DMLMessage::SetDMSpeed(sp) => { - self.dm.set_speed(sp).await; - } - DMLMessage::ToggleShowNick => { - self.dm.toggle_show_nick().await; - } - DMLMessage::RequestRestart => { - self.restart().await; - } - DMLMessage::RequestExit => { - self.quit().await; - } - DMLMessage::SetVideoInfo((w, h, pts)) => { - info!("video info: w {} h {} pts {}", w, h, pts); - let s1 = self.clone(); - // danmaku task - tokio::task::spawn_local(async move { - if matches!(s1.cm.site, crate::config::Site::BiliVideo) { - let _ = s1.dm.run_bilivideo(16.0 * h as f64 / w as f64 / 9.0).await; - } else { - let _ = s1.dm.run(16.0 * h as f64 / w as f64 / 9.0, pts).await; - } - }); + } + } + } + + async fn dispatch(&self, msg: DMLMessage) { + match msg { + DMLMessage::SetFontScale(fs) => { + self.dm.set_font_size(fs).await; + } + DMLMessage::SetFontAlpha(fa) => { + self.dm.set_font_alpha(fa).await; + } + DMLMessage::SetDMSpeed(sp) => { + self.dm.set_speed(sp).await; + } + DMLMessage::ToggleShowNick => { + self.dm.toggle_show_nick().await; + } + DMLMessage::RequestRestart => { + let _ = self.fc.quit().await; + } + DMLMessage::RequestExit => { + // self.quit().await; + } + DMLMessage::SetVideoInfo((w, h, pts)) => { + info!("video info: w {} h {} pts {}", w, h, pts); + // danmaku task + if matches!(self.cm.site, crate::config::Site::BiliVideo) { + let _ = self.dm.run_bilivideo(16.0 * h as f64 / w as f64 / 9.0).await; + } else { + let _ = self.dm.run(16.0 * h as f64 / w as f64 / 9.0, pts).await; } - DMLMessage::GoToBVPage(p) => { - match self.sf.run_bilivideo(p).await { - Ok((title, urls)) => { - let u1 = urls[0].to_string(); - self.dm.set_bili_video_cid(&u1).await; - let s2 = self.clone(); - tokio::task::spawn_local(async move { - let _ = s2.mc.reload_edl_video(&urls, &title).await; - }); + } + DMLMessage::PlayVideo => { + let _ = self.play_video().await.map_err(|e| info!("play video error: {}", e)); + } + DMLMessage::FfmpegOutputReady => { + info!("ffmpeg output ready"); + tokio::time::sleep(Duration::from_millis(200)).await; + match self.cm.run_mode { + crate::config::RunMode::Play => { + let _ = self.mc.reload_video().await; + } + crate::config::RunMode::Record => { + if self.cm.http_address.is_none() { + let _ = self.fc.write_record_task().await; } - Err(_) => {} - }; + } } } } } - pub async fn restart(self: &Arc) { - if matches!(self.state.read().await.deref(), DMLState::Exiting) { - return; - } - let (title, urls) = match self.sf.run().await { - Ok(it) => it, - Err(_) => { - self.quit().await; - return; - } - }; - self.cm.set_stream_type(&urls[0]).await; - let u1 = urls[0].to_string(); - self.dm.set_bili_video_cid(&u1).await; - let s2 = self.clone(); - let urls1 = urls.clone(); - // ffmpeg task - tokio::task::spawn_local(async move { - if matches!(s2.cm.site, crate::config::Site::BiliVideo) - && matches!(s2.cm.run_mode, crate::config::RunMode::Play) - { - let _ = s2.mc.reload_edl_video(&urls1, &title).await; - } else { - let _ = s2.fc.run(&title, &urls1).await; - info!("ffmpeg exit"); - if matches!(s2.cm.site, crate::config::Site::BiliVideo) { - // bilibili video download completed, then quit - let _ = s2.mc.quit().await; + pub async fn play(&self) -> anyhow::Result { + match self.cm.run_mode { + crate::config::RunMode::Play => { + if matches!(self.cm.site, crate::config::Site::BiliVideo) { + self.play_video().await?; + tokio::time::sleep(Duration::from_secs(u64::MAX)).await; } else { - s2.restart().await; + self.play_live().await?; } } - }); - let s3 = self.clone(); - // streamer task - tokio::task::spawn_local(async move { - if !matches!(s3.cm.site, crate::config::Site::BiliVideo) { - let _ = s3.st.run(urls).await; - let _ = s3.fc.quit_new().await; + crate::config::RunMode::Record => { + self.play_live().await?; + if matches!(self.cm.site, crate::config::Site::BiliVideo) { + return Ok(true); + } } - }); + } + Ok(false) + } + + pub async fn play_live(&self) -> anyhow::Result<()> { + let (title, urls) = self.sf.run().await?; + self.cm.set_stream_type(&urls[0]); + self.cm.title.borrow_mut().clear(); + self.cm.title.borrow_mut().push_str(&title); + self.dm.set_bili_video_cid(&urls[0]).await; + let ff_task = async { + self.fc.run(&urls).await?; + anyhow::Ok(()) + }; + let streamer_task = async { + self.st.run(&urls).await?; + self.fc.quit().await?; + anyhow::Ok(()) + }; + let (_ff_res, _st_res) = tokio::join!(ff_task, streamer_task); + Ok(()) } - pub async fn quit(self: &Arc) { - *self.state.write().await = DMLState::Exiting; - let _ = self.mc.quit().await; + pub async fn play_video(&self) -> anyhow::Result<()> { + let (title, urls) = self.sf.run().await?; + self.cm.set_stream_type(&urls[0]); + self.cm.title.borrow_mut().clear(); + self.cm.title.borrow_mut().push_str(&title); + self.dm.set_bili_video_cid(&urls[0]).await; + self.mc.reload_edl_video(&urls).await?; + Ok(()) } } diff --git a/src/ffmpeg/mod.rs b/src/ffmpeg/mod.rs index 600f98e..ff46d1d 100644 --- a/src/ffmpeg/mod.rs +++ b/src/ffmpeg/mod.rs @@ -1,99 +1,77 @@ +use crate::ipcmanager::IPCManager; +use crate::{config::ConfigManager, dmlive::DMLMessage}; use anyhow::anyhow; use anyhow::Result; use log::info; -use log::warn; use std::cell::RefCell; -use std::sync::Arc; +use std::rc::Rc; use tokio::io::AsyncRead; use tokio::process::ChildStdin; -use tokio::sync::oneshot; -use tokio::task::spawn_local; -use tokio::time::timeout; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt}, process::Command, - sync::RwLock, }; -use crate::utils; -use crate::{config::ConfigManager, dmlive::DMLMessage}; - pub struct FfmpegControl { - ipc_manager: Arc, - cm: Arc, - ff_command_tx: RwLock>>, + ipc_manager: Rc, + cm: Rc, ff_stdin: RefCell>, mtx: async_channel::Sender, } impl FfmpegControl { - pub fn new( - cm: Arc, im: Arc, mtx: async_channel::Sender, - ) -> Self { + pub fn new(cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { Self { ipc_manager: im, cm, - ff_command_tx: RwLock::new(None), mtx, ff_stdin: RefCell::new(None), } } - async fn run_write_record_task(&self, title: String) -> Result<()> { + pub async fn write_record_task(&self) -> Result<()> { let in_stream = self.ipc_manager.get_f2m_socket_path(); - let max_len = match title.char_indices().nth(70) { + let max_len = match self.cm.title.borrow().char_indices().nth(70) { Some(it) => it.0, - None => title.len(), + None => self.cm.title.borrow().len(), }; - let _ = spawn_local(async move { - let now = chrono::Local::now(); - let filename = format!( - "{} - {}.mkv", - title[..max_len].replace('/', "-"), - now.format("%F %T") - ); - loop { - let mut cmd = Command::new("ffmpeg"); - cmd.args(&["-y", "-xerror", "-hide_banner", "-nostats", "-nostdin"]); - cmd.arg("-i"); - cmd.arg(&in_stream); - cmd.args(&["-c", "copy", "-f", "matroska"]); - cmd.arg(&filename); - let mut ff = cmd - .stdin(std::process::Stdio::null()) - .stderr(std::process::Stdio::piped()) - .kill_on_drop(false) - .spawn() - .unwrap(); - let mut reader = tokio::io::BufReader::new(ff.stderr.take().unwrap()).lines(); - let mut retry = false; - while let Some(line) = reader.next_line().await.unwrap_or(None) { - info!("{}", &line); - if line.contains("Connection refused") { - retry = true; - } - } - let _ = ff.wait().await; - if retry == true { - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - continue; - } else { - return; - } - } - }) - .await; + let now = chrono::Local::now(); + let filename = format!( + "{} - {}.mkv", + self.cm.title.borrow()[..max_len].replace('/', "-"), + now.format("%F %T") + ); + let mut cmd = Command::new("ffmpeg"); + cmd.args(&["-y", "-xerror", "-hide_banner", "-nostats", "-nostdin"]); + cmd.arg("-i"); + cmd.arg(&in_stream); + cmd.args(&["-c", "copy", "-f", "matroska"]); + cmd.arg(&filename); + let mut ff = cmd + .stdin(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .kill_on_drop(false) + .spawn() + .unwrap(); + // let mut reader = tokio::io::BufReader::new(ff.stderr.take().unwrap()).lines(); + // let mut retry = false; + // while let Some(line) = reader.next_line().await.unwrap_or(None) { + // info!("{}", &line); + // if line.contains("Connection refused") { + // retry = true; + // } + // } + let _ = ff.wait().await; Ok(()) } - pub async fn create_ff_command(&self, title: &str, rurl: &Vec) -> Result { - let stream_type = &*self.cm.stream_type.read().await; + pub async fn create_ff_command(&self, rurl: &Vec) -> Result { let mut ret = Command::new("ffmpeg"); ret.args(&["-y", "-xerror"]); ret.arg("-hide_banner"); ret.arg("-nostats"); // ret.arg("-report"); // ret.arg("-loglevel").arg("quiet"); - match stream_type { + match self.cm.stream_type.get() { crate::config::StreamType::DASH => { if matches!(self.cm.site, crate::config::Site::BiliVideo) { ret.args(&[ @@ -118,7 +96,7 @@ impl FfmpegControl { ret.args(&["-map", "0:v:0", "-map", "1:a:0", "-map", "2:s:0"]); } _ => { - ret.arg("-i").arg(self.ipc_manager.get_stream_socket_path()); + ret.arg("-i").arg(self.ipc_manager.get_video_socket_path()); ret.arg("-i").arg(self.ipc_manager.get_danmaku_socket_path()); ret.args(&["-map", "0:v:0", "-map", "0:a:0", "-map", "1:s:0"]); } @@ -127,7 +105,12 @@ impl FfmpegControl { if matches!(self.cm.site, crate::config::Site::TwitchLive) { ret.args(&["-c:a", "pcm_s16le"]); } - ret.args(&["-metadata", format!("title={}", &title).as_str(), "-f", "matroska"]); + ret.args(&[ + "-metadata", + format!("title={}", self.cm.title.borrow()).as_str(), + "-f", + "matroska", + ]); // ret.args(&["-reserve_index_space", " 1024000"]); match self.cm.run_mode { crate::config::RunMode::Play => { @@ -148,14 +131,6 @@ impl FfmpegControl { } pub async fn quit(&self) -> Result<()> { - match self.ff_command_tx.write().await.take().ok_or(anyhow!("ffmpeg quit err 1"))?.send(true) { - Ok(_) => {} - Err(_) => {} - }; - Ok(()) - } - - pub async fn quit_new(&self) -> Result<()> { info!("close ffmpeg"); let _ = self .ff_stdin @@ -168,54 +143,45 @@ impl FfmpegControl { } pub async fn get_video_info(&self, ffstderr: T) -> Result<()> { - let mut duration = 0u64; - let mut start = 0u64; let mut reader = tokio::io::BufReader::new(ffstderr).lines(); let res_re = regex::Regex::new(r#"Stream #[0-9].+? Video:.*?\D(\d{3,5})x(\d{2,5})\D.*"#).unwrap(); let pts_re = regex::Regex::new(r#"Duration: ([^,\s]+),\s+(start: ([0-9.]+))*.+"#).unwrap(); - let mut has_sent = false; + let dm_re = regex::Regex::new(r#"Stream #[0-9:]+\s*Subtitle:\s*ass"#).unwrap(); + let mut vinfo_sent = false; + let mut ffready_sent = false; while let Some(line) = reader.next_line().await.unwrap_or(None) { info!("{}", &line); let line = line.trim(); - match pts_re.captures(&line) { - Some(it) => { - duration = utils::str_to_ms(&it[1]); - let st: f64 = it.get(3).map_or("0", |it| it.as_str()).parse().unwrap_or(0.0); - start = (st * 1000.0) as u64; - continue; + if let Some(_it) = pts_re.captures(&line) { + // duration = utils::str_to_ms(&it[1]); + // let st: f64 = it.get(3).map_or("0", |it| it.as_str()).parse().unwrap_or(0.0); + // start = (st * 1000.0) as u64; + // continue; + } else if let Some(_it) = dm_re.captures(&line) { + if ffready_sent == false { + let _ = self.mtx.send(DMLMessage::FfmpegOutputReady).await; + ffready_sent = true; } - None => {} - } - match res_re.captures(&line) { - Some(it) => { - let w = it[1].parse().unwrap(); - let h = it[2].parse().unwrap(); - if w < 100 || h < 100 { - let _ = self.quit_new().await; - } - if has_sent == false { - let _ = self - .mtx - .send(DMLMessage::SetVideoInfo(( - w, - h, - if start != 0 { start } else { duration }, - ))) - .await; - has_sent = true; - } + } else if let Some(it) = res_re.captures(&line) { + let w = it[1].parse().unwrap(); + let h = it[2].parse().unwrap(); + if w < 100 || h < 100 { + let _ = self.quit().await; + } + if vinfo_sent == false { + let _ = self.mtx.send(DMLMessage::SetVideoInfo((w, h, 0))).await; + vinfo_sent = true; } - None => {} } } // warn!("get video info failed!"); - let _ = self.quit_new().await; + let _ = self.quit().await; Ok(()) } - pub async fn run(self: &Arc, title: &str, rurl: &Vec) -> Result<()> { + pub async fn run(&self, rurl: &Vec) -> Result<()> { let mut ff = self - .create_ff_command(title, rurl) + .create_ff_command(rurl) .await? .stdin(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) @@ -228,14 +194,14 @@ impl FfmpegControl { *self.ff_stdin.borrow_mut() = Some(ffstdin); let write_record_task = async { - match self.cm.run_mode { - crate::config::RunMode::Play => {} - crate::config::RunMode::Record => { - if self.cm.http_address.is_none() { - let _ = self.run_write_record_task(title.into()).await; - } - } - } + // match self.cm.run_mode { + // crate::config::RunMode::Play => {} + // crate::config::RunMode::Record => { + // if self.cm.http_address.is_none() { + // let _ = self.run_write_record_task(title.into()).await; + // } + // } + // } }; let _ = tokio::join!(ff.wait(), self.get_video_info(ffstderr), write_record_task); Ok(()) diff --git a/src/ipcmanager/mod.rs b/src/ipcmanager/mod.rs index 3c7e237..10d34c2 100644 --- a/src/ipcmanager/mod.rs +++ b/src/ipcmanager/mod.rs @@ -1,218 +1,100 @@ -use std::sync::Arc; - -use anyhow::anyhow; +use crate::config::{ConfigManager, Platform}; +use crate::dmlerr; use anyhow::Result; -use async_channel::Receiver; -use futures::{ - future::{select_ok, AbortHandle, Abortable}, - Future, -}; +use std::rc::Rc; use tokio::{ io::{AsyncRead, AsyncWrite}, net::{TcpListener, UnixListener}, }; use uuid::Uuid; -use crate::config::ConfigManager; - pub trait DMLStream: AsyncRead + AsyncWrite + Send + Sync + Unpin {} impl DMLStream for T where T: AsyncRead + AsyncWrite + Send + Sync + Unpin {} pub struct IPCManager { - pub is_dash: bool, - plat: u8, base_uuid: String, base_socket_dir: String, f2m_port: u16, - stream_port: u16, danmaku_port: u16, video_port: u16, audio_port: u16, - abort_handle: Option, - danmaku_socket_rx: Option>>, - // danmaku_unix_rx: Option>, - stream_socket_rx: Option>>, - dashv_socket_rx: Option>>, - dasha_socket_rx: Option>>, + danmaku_unix_listener: Option, + danmaku_tcp_listener: Option, + video_tcp_listener: Option, + audio_tcp_listener: Option, + cm: Rc, } impl IPCManager { - pub fn new(cm: Arc) -> Self { - let is_dash = cm.room_url.contains("youtube.com"); + pub fn new(cm: Rc) -> Self { let base_uuid = Uuid::new_v4().as_hyphenated().to_string(); IPCManager { - is_dash, - plat: cm.plat, base_uuid, base_socket_dir: "/tmp".into(), f2m_port: 0, - stream_port: 0, danmaku_port: 0, video_port: 0, audio_port: 0, - abort_handle: None, - danmaku_socket_rx: None, - stream_socket_rx: None, - dashv_socket_rx: None, - dasha_socket_rx: None, + danmaku_unix_listener: None, + danmaku_tcp_listener: None, + video_tcp_listener: None, + audio_tcp_listener: None, + cm, } } pub async fn stop(&self) -> Result<()> { - self.abort_handle.as_ref().unwrap().abort(); tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - if self.plat == 0 { - tokio::fs::remove_file(format!( + if self.cm.plat == Platform::Linux { + let _ = tokio::fs::remove_file(format!( "{}/dml-{}-dm", &self.base_socket_dir, &self.base_uuid )) - .await?; - if false { - tokio::fs::remove_file(format!( - "{}/dml-{}-s", - &self.base_socket_dir, &self.base_uuid - )) - .await?; - } - tokio::fs::remove_file(format!( + .await; + let _ = tokio::fs::remove_file(format!( "{}/dml-{}-mpv", &self.base_socket_dir, &self.base_uuid )) - .await?; + .await; } Ok(()) } - pub async fn run_normal(&mut self) -> Result<()> { - let mut tasks: Vec>>>> = Vec::new(); - let dml = UnixListener::bind(format!( - "{}/dml-{}-dm", - &self.base_socket_dir, &self.base_uuid - ))?; - let (tx, rx) = async_channel::bounded(1); - self.danmaku_socket_rx = Some(rx); - let danmaku_socket_task = async move { - while let Ok((s, _)) = dml.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok(()) - // Ok::<(), _>(()) - }; - tasks.push(Box::pin(danmaku_socket_task)); - if self.is_dash { - let (vl, p) = Self::get_tcp_listener().await; - self.video_port = p; - let (tx, rx) = async_channel::bounded(1); - self.dashv_socket_rx = Some(rx); - let dashv_socket_task = async move { - while let Ok((s, _)) = vl.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok(()) - }; - tasks.push(Box::pin(dashv_socket_task)); - let (al, p) = Self::get_tcp_listener().await; - self.audio_port = p; - let (tx, rx) = async_channel::bounded(1); - self.dasha_socket_rx = Some(rx); - let dasha_socket_task = async move { - while let Ok((s, _)) = al.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok(()) - }; - tasks.push(Box::pin(dasha_socket_task)); + async fn init_danmaku(&mut self) -> Result<()> { + if self.cm.plat == Platform::Linux { + let dml = UnixListener::bind(format!( + "{}/dml-{}-dm", + &self.base_socket_dir, &self.base_uuid + ))?; + self.danmaku_unix_listener = Some(dml); } else { - let (sl, p) = Self::get_tcp_listener().await; - self.stream_port = p; - let (tx, rx) = async_channel::bounded(1); - self.stream_socket_rx = Some(rx); - let stream_socket_task = async move { - while let Ok((s, _)) = sl.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok(()) - }; - tasks.push(Box::pin(stream_socket_task)); + let (dml, p) = Self::get_tcp_listener().await; + self.danmaku_port = p; + self.danmaku_tcp_listener = Some(dml); } - let (_, p) = Self::get_tcp_listener().await; - self.f2m_port = p; + Ok(()) + } - let (abort_handle, abort_registration) = AbortHandle::new_pair(); - let tasks = Abortable::new(select_ok(tasks.into_iter()), abort_registration); - self.abort_handle = Some(abort_handle); - tokio::task::spawn_local(async move { - let _ = tasks.await; - }); + async fn init_stream(&mut self) -> Result<()> { + let (vl, p) = Self::get_tcp_listener().await; + self.video_port = p; + self.video_tcp_listener = Some(vl); + let (al, p) = Self::get_tcp_listener().await; + self.audio_port = p; + self.audio_tcp_listener = Some(al); Ok(()) } - pub async fn run_tcp_only(&mut self) -> Result<()> { - let mut tasks: Vec>>>> = Vec::new(); - let (dml, p) = Self::get_tcp_listener().await; - self.danmaku_port = p; - let (tx, rx) = async_channel::bounded(1); - self.danmaku_socket_rx = Some(rx); - let danmaku_socket_task = async move { - while let Ok((s, _)) = dml.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok::<()>(()) - }; - tasks.push(Box::pin(danmaku_socket_task)); - if self.is_dash { - let (vl, p) = Self::get_tcp_listener().await; - self.video_port = p; - let (tx, rx) = async_channel::bounded(1); - self.dashv_socket_rx = Some(rx); - let dashv_socket_task = async move { - while let Ok((s, _)) = vl.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok::<()>(()) - }; - tasks.push(Box::pin(dashv_socket_task)); - let (al, p) = Self::get_tcp_listener().await; - self.audio_port = p; - let (tx, rx) = async_channel::bounded(1); - self.dasha_socket_rx = Some(rx); - let dasha_socket_task = async move { - while let Ok((s, _)) = al.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok::<()>(()) - }; - tasks.push(Box::pin(dasha_socket_task)); - } else { - let (sl, p) = Self::get_tcp_listener().await; - self.stream_port = p; - let (tx, rx) = async_channel::bounded(1); - self.stream_socket_rx = Some(rx); - let stream_socket_task = async move { - while let Ok((s, _)) = sl.accept().await { - tx.send(Box::new(s)).await?; - } - anyhow::Ok::<()>(()) - }; - tasks.push(Box::pin(stream_socket_task)); - } + async fn init_f2m(&mut self) -> Result<()> { let (_, p) = Self::get_tcp_listener().await; self.f2m_port = p; - - let (abort_handle, abort_registration) = AbortHandle::new_pair(); - let tasks = Abortable::new(select_ok(tasks.into_iter()), abort_registration); - self.abort_handle = Some(abort_handle); - tokio::task::spawn_local(async move { - let _ = tasks.await; - }); Ok(()) } + pub async fn run(&mut self) -> Result<()> { - if self.plat == 0 { - self.run_normal().await?; - } else { - self.run_tcp_only().await?; - } + self.init_danmaku().await?; + self.init_stream().await?; + self.init_f2m().await?; Ok(()) } @@ -246,14 +128,10 @@ impl IPCManager { // } } - pub fn get_stream_socket_path(&self) -> String { - if self.plat == 0 { - // format!("unix://{}/dml-{}-s", &self.base_socket_dir, &self.base_uuid) - format!("tcp://127.0.0.1:{}", &self.stream_port) - } else { - format!("tcp://127.0.0.1:{}", &self.stream_port) - } - } + // pub fn get_stream_socket_path(&self) -> String { + // // format!("unix://{}/dml-{}-s", &self.base_socket_dir, &self.base_uuid) + // format!("tcp://127.0.0.1:{}", &self.stream_port) + // } pub fn get_video_socket_path(&self) -> String { format!("tcp://127.0.0.1:{}", &self.video_port) @@ -264,7 +142,7 @@ impl IPCManager { } pub fn get_danmaku_socket_path(&self) -> String { - if self.plat == 0 { + if self.cm.plat == Platform::Linux { format!( "unix://{}/dml-{}-dm", &self.base_socket_dir, &self.base_uuid @@ -275,28 +153,22 @@ impl IPCManager { } pub async fn get_danmaku_socket(&self) -> Result> { - let rx = self.danmaku_socket_rx.as_ref().ok_or(anyhow!("gds err 1"))?; - let s = loop { - let s = rx.recv().await?; - if rx.is_empty() { - break s; - } - }; - Ok(s) - } - - pub async fn get_stream_socket(&self) -> Result> { - let s = self.stream_socket_rx.as_ref().ok_or(anyhow!("gss err 1"))?.recv().await?; - Ok(s) + if self.cm.plat == Platform::Linux { + let (s, _) = self.danmaku_unix_listener.as_ref().ok_or_else(|| dmlerr!())?.accept().await?; + Ok(Box::new(s)) + } else { + let (s, _) = self.danmaku_tcp_listener.as_ref().ok_or_else(|| dmlerr!())?.accept().await?; + Ok(Box::new(s)) + } } pub async fn get_video_socket(&self) -> Result> { - let s = self.dashv_socket_rx.as_ref().ok_or(anyhow!("gvs err 1"))?.recv().await?; - Ok(s) + let (s, _) = self.video_tcp_listener.as_ref().ok_or_else(|| dmlerr!())?.accept().await?; + Ok(Box::new(s)) } pub async fn get_audio_socket(&self) -> Result> { - let s = self.dasha_socket_rx.as_ref().ok_or(anyhow!("gas err 1"))?.recv().await?; - Ok(s) + let (s, _) = self.audio_tcp_listener.as_ref().ok_or_else(|| dmlerr!())?.accept().await?; + Ok(Box::new(s)) } } diff --git a/src/main.rs b/src/main.rs index 720876d..779f89f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,14 +8,11 @@ mod streamer; mod streamfinder; mod utils; -use std::sync::Arc; - +use crate::config::ConfigManager; use clap::Parser; use log::*; -use tokio::{ - runtime::Builder, - task, -}; +use std::rc::Rc; +use tokio::runtime::Builder; #[derive(Parser)] #[clap(author, version, about, long_about = None)] @@ -46,7 +43,6 @@ pub struct Args { #[clap(long, action)] plive: bool, - // /// Use the Cookies that extracted from browser, could be "chrome" "chromium" or "firefox" // #[clap(long = "cookies-from-browser", value_parser)] // cookies_from_browser: Option, @@ -64,21 +60,20 @@ fn main() { env_logger::Builder::new().filter(None, log_level).init(); Builder::new_current_thread().enable_all().build().unwrap().block_on(async move { - let local = task::LocalSet::new(); - local - .run_until(async move { - let proj_dirs = directories::ProjectDirs::from("com", "THMonster", "dmlive").unwrap(); - let d = proj_dirs.config_dir(); - let _ = tokio::fs::create_dir_all(&d).await; - let config_path = d.join("config.toml"); - if !config_path.exists() { - let _ = tokio::fs::File::create(&config_path).await; - } - let cm = Arc::new(crate::config::ConfigManager::new(config_path, &args)); - let dml = dmlive::DMLive::new(cm).await; - let dml = Arc::new(dml); - dml.run().await; - }) - .await; + let proj_dirs = directories::ProjectDirs::from("com", "THMonster", "dmlive").unwrap(); + let d = proj_dirs.config_dir(); + let _ = tokio::fs::create_dir_all(&d).await; + let config_path = d.join("config.toml"); + if !config_path.exists() { + let _ = tokio::fs::File::create(&config_path).await; + } + let mut cm = ConfigManager::new(config_path, &args); + cm.init().await.unwrap(); + let cm = Rc::new(cm); + let mut im = ipcmanager::IPCManager::new(cm.clone()); + im.run().await.unwrap(); + let im = Rc::new(im); + let dml = dmlive::DMLive::new(cm, im).await; + dml.run().await; }) } diff --git a/src/mpv/mod.rs b/src/mpv/mod.rs index 23d729d..0b334c2 100644 --- a/src/mpv/mod.rs +++ b/src/mpv/mod.rs @@ -1,36 +1,29 @@ pub mod cmdparser; - -use crate::{ - config::ConfigManager, - dmlive::DMLMessage, - utils::gen_ua, -}; -use anyhow::anyhow; +use crate::config::Platform; +use crate::dmlerr; +use crate::ipcmanager::IPCManager; +use crate::{config::ConfigManager, dmlive::DMLMessage, utils::gen_ua}; use anyhow::Result; +use futures::StreamExt; use log::info; -use std::sync::Arc; +use std::cell::Cell; +use std::rc::Rc; use tokio::{ - io::{ - AsyncBufReadExt, - AsyncWriteExt, - }, + io::{AsyncBufReadExt, AsyncWriteExt}, net::UnixStream, process::Command, }; pub struct MpvControl { - ipc_manager: Arc, - cm: Arc, + last_rpc_ts: Cell, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, mpv_command_tx: async_channel::Sender, mpv_command_rx: async_channel::Receiver, } impl MpvControl { - pub fn new( - cm: Arc, - im: Arc, - mtx: async_channel::Sender, - ) -> Self { + pub fn new(cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { let (tx, rx) = async_channel::unbounded(); Self { ipc_manager: im, @@ -38,6 +31,7 @@ impl MpvControl { mtx, mpv_command_tx: tx, mpv_command_rx: rx, + last_rpc_ts: Cell::new(0), } } @@ -54,6 +48,7 @@ impl MpvControl { "--keep-open=no", "--idle=yes", "--player-operation-mode=pseudo-gui", + "--sub=1", ]) .arg(format!( "--input-ipc-server={}", @@ -62,7 +57,7 @@ impl MpvControl { Ok(ret) } - pub async fn reload_edl_video(&self, urls: &Vec, title: &str) -> Result<()> { + pub async fn reload_edl_video(&self, urls: &Vec) -> Result<()> { let edl = format!( "edl://!no_clip;!no_chapters;%{0}%{1};!new_stream;!no_clip;!no_chapters;%{2}%{3}", urls[2].chars().count(), @@ -70,7 +65,7 @@ impl MpvControl { urls[1].chars().count(), urls[1] ); - info!("{}--{}", &edl, title); + info!("load video: {}--{}", &edl, self.cm.title.borrow()); self.mpv_command_tx .send(format!( "{{ \"command\": [\"loadfile\", \"{}\"], \"async\": true }}\n", @@ -80,26 +75,30 @@ impl MpvControl { self.mpv_command_tx .send(format!( "{{ \"command\": [\"set_property\", \"force-media-title\", \"{}\"] }}\n", - title.replace(r#"""#, r#"\""#) + self.cm.title.borrow().replace(r#"""#, r#"\""#) )) .await?; Ok(()) } pub async fn reload_video(&self) -> Result<()> { - self.mpv_command_tx - .send(format!( - "{{ \"command\": [\"loadfile\", \"{}\"] }}\n ", - self.ipc_manager.get_f2m_socket_path() - )) - .await?; + if self.cm.plat == Platform::Android { + Command::new("termux-open").arg(self.ipc_manager.get_f2m_socket_path()).spawn()?; + } else { + self.mpv_command_tx + .send(format!( + "{{ \"command\": [\"loadfile\", \"{}\"] }}\n", + self.ipc_manager.get_f2m_socket_path() + )) + .await?; + } Ok(()) } - pub async fn quit(&self) -> Result<()> { - self.mpv_command_tx.send("{ \"command\": [\"quit\"] }\n".into()).await?; - Ok(()) - } + // pub async fn quit(&self) -> Result<()> { + // self.mpv_command_tx.send("{ \"command\": [\"quit\"] }\n".into()).await?; + // Ok(()) + // } pub async fn stop(&self) -> Result<()> { self.mpv_command_tx.send("{ \"command\": [\"stop\"] }\n".into()).await?; @@ -124,12 +123,12 @@ impl MpvControl { Ok(()) } - async fn handle_mpv_event(self: &Arc, line: &str, last_time: &mut i64) -> Result<()> { - let j: serde_json::Value = serde_json::from_str(line)?; + async fn handle_mpv_event(&self, line: String) -> Result<()> { + let j: serde_json::Value = serde_json::from_str(&line)?; if let Some(rid) = j.pointer("/request_id") { if rid.as_u64().eq(&Some(114)) { - let w = j.pointer("/data/w").ok_or(anyhow!("hme err a1"))?.as_u64().unwrap(); - let h = j.pointer("/data/h").ok_or(anyhow!("hme err a2"))?.as_u64().unwrap(); + let w = j.pointer("/data/w").ok_or_else(|| dmlerr!())?.as_u64().unwrap(); + let h = j.pointer("/data/h").ok_or_else(|| dmlerr!())?.as_u64().unwrap(); if matches!(self.cm.site, crate::config::Site::BiliVideo) { let _ = self.mtx.send(DMLMessage::SetVideoInfo((w, h, 0))).await; self.mpv_command_tx @@ -150,7 +149,7 @@ impl MpvControl { match j.pointer("/data") { Some(it) => match it.as_f64() { Some(it) => { - self.cm.display_fps.write().await.0 = it.round() as u64; + self.cm.display_fps.set((it.round() as u64, self.cm.display_fps.get().1)); } None => {} }, @@ -160,7 +159,7 @@ impl MpvControl { match j.pointer("/data") { Some(it) => match it.as_f64() { Some(it) => { - if self.cm.display_fps.read().await.1 == 0 && it < 59.0 { + if self.cm.display_fps.get().1 == 0 && it < 59.0 { self.mpv_command_tx .send( r#"{ "command": ["set_property", "vf", "fps=fps=60:round=near"] } @@ -176,23 +175,16 @@ impl MpvControl { } } } - let event = j.pointer("/event").ok_or(anyhow!("hme err 1"))?.as_str().ok_or(anyhow!("hme err 2"))?; + let event = j.pointer("/event").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?; if event.eq("end-file") { if matches!(self.cm.site, crate::config::Site::BiliVideo) { - if j.pointer("/reason").ok_or(anyhow!("hme err 9"))?.as_str().unwrap().eq("eof") { - let _ = self - .mtx - .send(DMLMessage::GoToBVPage( - self.cm.bvideo_info.read().await.current_page + 1, - )) - .await; + if j.pointer("/reason").ok_or_else(|| dmlerr!())?.as_str().unwrap().eq("eof") { + self.cm.bvideo_info.borrow_mut().current_page += 1; + let _ = self.mtx.send(DMLMessage::PlayVideo).await; } } else { - let s1 = self.clone(); - tokio::task::spawn_local(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - let _ = s1.reload_video().await; - }); + // tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + // let _ = self.reload_video().await; } } else if event.eq("file-loaded") { tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; @@ -206,15 +198,18 @@ impl MpvControl { .into(), ) .await; - } else if event.eq("client-message") && (chrono::Utc::now().timestamp_millis() - *last_time > 1000) { - *last_time = chrono::Utc::now().timestamp_millis(); + } else if event.eq("client-message") { + let now = chrono::Utc::now().timestamp_millis(); + if now - self.last_rpc_ts.get() < 1000 { + return Ok(()); + } + self.last_rpc_ts.set(now); let cmds = cmdparser::CmdParser::new( - j.pointer("/args/0").ok_or(anyhow!("hme err 3"))?.as_str().ok_or(anyhow!("hme err 4"))?, + j.pointer("/args/0").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, ); if cmds.restart { if matches!(self.cm.site, crate::config::Site::BiliVideo) { - let p = self.cm.bvideo_info.read().await.current_page; - let _ = self.mtx.send(DMLMessage::GoToBVPage(p)).await; + let _ = self.mtx.send(DMLMessage::PlayVideo).await; } else { self.stop().await?; } @@ -222,19 +217,9 @@ impl MpvControl { if cmds.fs.is_some() { let _ = self.mtx.send(DMLMessage::SetFontScale(cmds.fs.unwrap())).await; } else if cmds.fsup { - let _ = self - .mtx - .send(DMLMessage::SetFontScale( - *self.cm.font_scale.read().await + 0.15, - )) - .await; + let _ = self.mtx.send(DMLMessage::SetFontScale(self.cm.font_scale.get() + 0.15)).await; } else if cmds.fsdown { - let _ = self - .mtx - .send(DMLMessage::SetFontScale( - *self.cm.font_scale.read().await - 0.15, - )) - .await; + let _ = self.mtx.send(DMLMessage::SetFontScale(self.cm.font_scale.get() - 0.15)).await; } if cmds.fa.is_some() { let _ = self.mtx.send(DMLMessage::SetFontAlpha(cmds.fa.unwrap())).await; @@ -243,23 +228,24 @@ impl MpvControl { let _ = self.mtx.send(DMLMessage::SetDMSpeed(cmds.speed.unwrap())).await; } if cmds.page.is_some() { - let _ = self.mtx.send(DMLMessage::GoToBVPage(cmds.page.unwrap() as usize)).await; + self.cm.bvideo_info.borrow_mut().current_page = cmds.page.unwrap() as usize; + let _ = self.mtx.send(DMLMessage::PlayVideo).await; } if cmds.nick { let _ = self.mtx.send(DMLMessage::ToggleShowNick).await; } if cmds.back { - let p = self.cm.bvideo_info.read().await.current_page.saturating_sub(1); - let p = if p == 0 { 1 } else { p }; - let _ = self.mtx.send(DMLMessage::GoToBVPage(p)).await; + let p = self.cm.bvideo_info.borrow().current_page.saturating_sub(1); + self.cm.bvideo_info.borrow_mut().current_page = if p == 0 { 1 } else { p }; + let _ = self.mtx.send(DMLMessage::PlayVideo).await; } if cmds.next { - let p = self.cm.bvideo_info.read().await.current_page.saturating_add(1); - let _ = self.mtx.send(DMLMessage::GoToBVPage(p)).await; + self.cm.bvideo_info.borrow_mut().current_page += 1; + let _ = self.mtx.send(DMLMessage::PlayVideo).await; } if cmds.fps { let fps: u64 = { - let df = self.cm.display_fps.read().await; + let df = self.cm.display_fps.get(); let i = df.1 as usize % 3; [df.0, 0u64, 60u64][i] }; @@ -280,63 +266,50 @@ impl MpvControl { )) .await?; } - self.cm.display_fps.write().await.1 += 1; + let df = self.cm.display_fps.get(); + self.cm.display_fps.set((df.0, df.1.saturating_add(1))); } } Ok(()) } - pub async fn run_normal(self: &Arc) -> Result<()> { + pub async fn run_normal(&self) -> Result<()> { let mut mpv = self.create_mpv_command().await?.kill_on_drop(true).spawn().unwrap(); tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; let s = UnixStream::connect(self.ipc_manager.get_mpv_socket_path()).await?; let (usocket_read, mut usocket_write) = tokio::io::split(s); - let s1 = self.clone(); - tokio::task::spawn_local(async move { - while let Ok(s) = s1.mpv_command_rx.recv().await { + let mpv_rpc_write_task = async { + while let Ok(s) = self.mpv_command_rx.recv().await { let _ = usocket_write.write_all(s.as_bytes()).await; } - }); - let s2 = self.clone(); - tokio::task::spawn_local(async move { - let mut last_time = chrono::Utc::now().timestamp_millis(); + }; + let mpv_rpc_read_task = async { + let mut tasks = futures::stream::FuturesUnordered::new(); let mut reader = tokio::io::BufReader::new(usocket_read).lines(); - while let Some(line) = reader.next_line().await? { - info!("mpv rpc: {}", &line); - let _ = s2.handle_mpv_event(&line, &mut last_time).await; + loop { + tokio::select! { + Some(_) = tasks.next() => {}, + msg = reader.next_line() => { + match msg { + Ok(it) => { tasks.push(self.handle_mpv_event(it.unwrap_or("".to_string()))); }, + Err(_) => { return; }, + } + } + } } - Ok::<(), Box>(()) - }); + }; let _ = self.init_mpv_rpc().await; - if !matches!(self.cm.site, crate::config::Site::BiliVideo) { - let _ = self.reload_video().await; + // let _ = self.reload_video().await; + tokio::select! { + _ = mpv_rpc_write_task => {}, + _ = mpv_rpc_read_task => {}, + _ = mpv.wait() => {}, } - mpv.wait().await?; Ok(()) } - pub async fn run_android(self: &Arc) -> Result<()> { - 'l1: loop { - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - let mut ns = Command::new("sh") - .arg("-c") - .arg(format!( - "netstat -apn | grep ffmpeg | grep {}", - self.ipc_manager.get_f2m_socket_path().trim_start_matches("tcp://") - )) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let o = ns.stdout.take().unwrap(); - let mut reader = tokio::io::BufReader::new(o).lines(); - while let Some(line) = reader.next_line().await.unwrap() { - if line.contains(self.ipc_manager.get_f2m_socket_path().trim_start_matches("tcp://")) { - break 'l1; - } - } - } - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; - Command::new("termux-open").arg(self.ipc_manager.get_f2m_socket_path()).spawn().unwrap(); + pub async fn run_android(&self) -> Result<()> { + // Command::new("termux-open").arg(self.ipc_manager.get_f2m_socket_path()).spawn().unwrap(); while let Ok(s) = self.mpv_command_rx.recv().await { if s.contains("quit") { break; @@ -345,10 +318,10 @@ impl MpvControl { Ok(()) } - pub async fn run(self: &Arc) -> Result<()> { + pub async fn run(&self) -> Result<()> { match self.cm.run_mode { crate::config::RunMode::Play => { - if cfg!(target_os = "android") { + if self.cm.plat == Platform::Android { self.run_android().await?; } else { self.run_normal().await?; diff --git a/src/streamer/flv.rs b/src/streamer/flv.rs index 3719e7a..9e11728 100644 --- a/src/streamer/flv.rs +++ b/src/streamer/flv.rs @@ -1,35 +1,22 @@ use crate::{ - config::{ - ConfigManager, - Site, - }, + config::{ConfigManager, Site}, dmlive::DMLMessage, - ipcmanager::DMLStream, -}; -use log::{ - info, - warn, -}; -use std::sync::{ - atomic::AtomicBool, - Arc, + ipcmanager::IPCManager, }; +use log::{info, warn}; +use std::{cell::Cell, rc::Rc}; use tokio::io::AsyncWriteExt; +#[allow(unused)] pub struct FLV { url: String, - ipc_manager: Arc, - cm: Arc, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, } impl FLV { - pub fn new( - url: String, - cm: Arc, - im: Arc, - mtx: async_channel::Sender, - ) -> Self { + pub fn new(url: String, cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { FLV { url, ipc_manager: im, @@ -38,67 +25,47 @@ impl FLV { } } - async fn download(&self, mut stream: Box) -> Result<(), Box> { + async fn download(&self) -> anyhow::Result<()> { + let mut stream = self.ipc_manager.get_video_socket().await?; let client = reqwest::Client::builder() .user_agent(crate::utils::gen_ua()) .connect_timeout(tokio::time::Duration::from_secs(10)) .build()?; let url = self.url.clone(); let room_url = self.cm.room_url.clone(); - let feed_dog = Arc::new(AtomicBool::new(false)); - let fd1 = feed_dog.clone(); - let watchdog_task = async move { - let mut cnt = 0u8; + let watch_dog = Cell::new(0); + let watchdog_task = async { loop { - if !feed_dog.load(std::sync::atomic::Ordering::SeqCst) { - cnt += 1; - } else { - cnt = 0; - feed_dog.store(false, std::sync::atomic::Ordering::SeqCst); - } - if cnt > 10 { + watch_dog.set(watch_dog.get() + 1); + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + if watch_dog.get() > 10 { warn!("connection too slow"); break; } - tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; } }; - let ts_task = async move { - let mut resp = if self.cm.plive && matches!(self.cm.site, Site::BiliLive) { - client.get(url).header("Referer", room_url).header("Cookie", self.cm.bcookie.as_str()).send().await? - } else { - client.get(url).header("Referer", room_url).send().await? - }; - while let Some(chunk) = match resp.chunk().await { - Ok(it) => it, - Err(err) => { - info!("flv download error: {}", err); - return Ok(()); - } - } { - match stream.write_all(&chunk).await { - Ok(it) => it, - Err(err) => { - info!("flv write error: {}", err); - return Ok(()); - } - }; - fd1.store(true, std::sync::atomic::Ordering::SeqCst); + let dl_task = async { + let mut resp = client.get(url).header("Referer", room_url); + if self.cm.plive && matches!(self.cm.site, Site::BiliLive) { + resp = resp.header("Cookie", self.cm.bcookie.as_str()); + } + let mut resp = resp.send().await?; + while let Some(chunk) = resp.chunk().await? { + stream.write_all(&chunk).await?; + watch_dog.set(0); } info!("flv downloader exit normally"); - Ok::<(), Box>(()) + anyhow::Ok(()) }; - let _ = futures::future::select(Box::pin(watchdog_task), Box::pin(ts_task)).await; + tokio::select! { + it = dl_task => { it?; } + _ = watchdog_task => {} + } Ok(()) } - pub async fn run(self: &Arc) -> Result<(), Box> { - match self.download(self.ipc_manager.get_stream_socket().await?).await { - Ok(it) => it, - Err(err) => { - info!("flv download error: {:?}", err); - } - }; + pub async fn run(&self) -> anyhow::Result<()> { + self.download().await?; info!("flv streamer exit"); Ok(()) } diff --git a/src/streamer/hls.rs b/src/streamer/hls.rs index 5cd02e3..7aafb8c 100644 --- a/src/streamer/hls.rs +++ b/src/streamer/hls.rs @@ -1,14 +1,15 @@ -use crate::{config::ConfigManager, dmlive::DMLMessage, ipcmanager::DMLStream, streamer::segment::SegmentStream}; +use crate::{config::ConfigManager, dmlive::DMLMessage, ipcmanager::IPCManager, streamer::segment::SegmentStream}; use log::info; use reqwest::Client; use std::{ cell::RefCell, collections::{HashMap, VecDeque}, ops::Not, - sync::Arc, + rc::Rc, }; -use tokio::{io::AsyncWriteExt, sync::mpsc::Receiver}; +use tokio::io::AsyncWriteExt; +#[allow(unused)] #[derive(Debug)] pub struct M3U8 { sequence: u64, @@ -17,19 +18,17 @@ pub struct M3U8 { clips: VecDeque, } +#[allow(unused)] pub struct HLS { url: String, header: RefCell>, - ipc_manager: Arc, - cm: Arc, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, } impl HLS { - pub fn new( - url: String, cm: Arc, im: Arc, - mtx: async_channel::Sender, - ) -> Self { + pub fn new(url: String, cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { HLS { url, ipc_manager: im, @@ -106,11 +105,11 @@ impl HLS { Ok(()) } - async fn download_task( - &self, client: &Client, mut stream: Box, mut clip_rx: Receiver, - ) -> anyhow::Result<()> { + async fn download_task(&self, client: &Client, ss: &SegmentStream) -> anyhow::Result<()> { + let mut stream = self.ipc_manager.get_video_socket().await?; let mut header_done = false; - while let Some(clip) = clip_rx.recv().await { + let mut rx = ss.clip_rx.borrow_mut(); + while let Some(clip) = rx.recv().await { let url = self.parse_clip_url(&clip)?; // info!("hls: clip url: {}, m3u8 url: {}", &url, &self.url); info!("hls: clip: {}", &clip); @@ -126,14 +125,14 @@ impl HLS { Ok(()) } - async fn refresh_m3u8_task( - &self, mut refresh_rx: Receiver, client: &Client, ss: &SegmentStream, - ) -> anyhow::Result<()> { + async fn refresh_m3u8_task(&self, client: &Client, ss: &SegmentStream) -> anyhow::Result<()> { let mut header_done = false; - while let Some(_) = refresh_rx.recv().await { + let mut rx = ss.refresh_rx.borrow_mut(); + while let Some(_) = rx.recv().await { let resp = client .get(&self.url) .header("Connection", "keep-alive") + // maybe bypass twitch ad .header("X-Forwarded-For", "::1") .send() .await?; @@ -148,21 +147,16 @@ impl HLS { Ok(()) } - pub async fn run(self: &Arc) -> Result<(), Box> { + pub async fn run(&self) -> anyhow::Result<()> { let client = reqwest::Client::builder() .user_agent(crate::utils::gen_ua()) .timeout(tokio::time::Duration::from_secs(15)) .build()?; - let (tx, rx) = tokio::sync::mpsc::channel(10); - let (tx1, rx1) = tokio::sync::mpsc::channel(5); - let seg_stream = SegmentStream::new(tx); - // let file = tokio::fs::File::create("/tmp/aaa.m4s").await?; - // let file = Box::new(file); + let seg_stream = SegmentStream::new(); tokio::select! { - it = self.refresh_m3u8_task(rx1, &client, &seg_stream) => { it?; }, - it = self.download_task(&client, self.ipc_manager.get_stream_socket().await?, rx) => { it?; }, - // it = self.download_task(&client, file, rx) => { it?; }, - it = seg_stream.run(tx1) => { it?; }, + it = self.refresh_m3u8_task(&client, &seg_stream) => { it?; }, + it = self.download_task(&client, &seg_stream) => { it?; }, + it = seg_stream.run() => { it?; }, } info!("hls streamer exit"); Ok(()) diff --git a/src/streamer/mod.rs b/src/streamer/mod.rs index aff34a1..9424bda 100644 --- a/src/streamer/mod.rs +++ b/src/streamer/mod.rs @@ -1,27 +1,23 @@ pub mod flv; pub mod hls; -pub mod youtube; pub mod segment; +pub mod youtube; use crate::{ config::{ConfigManager, StreamType}, dmlive::DMLMessage, + ipcmanager::IPCManager, }; -use anyhow::*; -use std::{ops::Deref, sync::Arc}; +use std::rc::Rc; pub struct Streamer { - ipc_manager: Arc, - cm: Arc, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, } impl Streamer { - pub fn new( - cm: Arc, - im: Arc, - mtx: async_channel::Sender, - ) -> Self { + pub fn new(cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { Self { ipc_manager: im, cm, @@ -29,8 +25,8 @@ impl Streamer { } } - pub async fn run(self: &Arc, rurl: Vec) -> Result<()> { - match self.cm.stream_type.read().await.deref() { + pub async fn run(&self, rurl: &Vec) -> anyhow::Result<()> { + match self.cm.stream_type.get() { StreamType::FLV => { let s = flv::FLV::new( rurl[0].to_string(), @@ -38,8 +34,7 @@ impl Streamer { self.ipc_manager.clone(), self.mtx.clone(), ); - let s = Arc::new(s); - let _ = s.run().await; + s.run().await?; } StreamType::HLS => { let s = hls::HLS::new( @@ -48,8 +43,7 @@ impl Streamer { self.ipc_manager.clone(), self.mtx.clone(), ); - let s = Arc::new(s); - let _ = s.run().await; + s.run().await?; } StreamType::DASH => { let s = youtube::Youtube::new( @@ -60,8 +54,7 @@ impl Streamer { self.ipc_manager.clone(), self.mtx.clone(), ); - let s = Arc::new(s); - s.run().await; + s.run().await?; } } Ok(()) diff --git a/src/streamer/segment.rs b/src/streamer/segment.rs index 84cf27b..74335dc 100644 --- a/src/streamer/segment.rs +++ b/src/streamer/segment.rs @@ -1,22 +1,29 @@ -use log::info; use std::{ cell::{Cell, RefCell}, collections::VecDeque, }; -use tokio::sync::mpsc::Sender; +use tokio::sync::mpsc::{Receiver, Sender}; pub struct SegmentStream { sequence: Cell, clips: RefCell>, clip_tx: Sender, + pub clip_rx: RefCell>, + refresh_tx: Sender, + pub refresh_rx: RefCell>, } impl SegmentStream { - pub fn new(clip_tx: Sender) -> Self { + pub fn new() -> Self { + let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx1, rx1) = tokio::sync::mpsc::channel(10); Self { sequence: Cell::new(0), clips: RefCell::new(VecDeque::new()), - clip_tx, + clip_tx: tx, + clip_rx: RefCell::new(rx), + refresh_tx: tx1, + refresh_rx: RefCell::new(rx1), } } @@ -54,10 +61,15 @@ impl SegmentStream { Ok(()) } - async fn refresh_task(&self, refresh_tx: Sender) -> anyhow::Result<()> { + async fn refresh_task(&self) -> anyhow::Result<()> { let mut itvl = 1000; let mut last_sq = 0u64; loop { + if itvl > 3000 { + itvl = 3000; + } else if itvl < 100 { + itvl = 100 + } let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(itvl)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); interval.tick().await; @@ -68,14 +80,14 @@ impl SegmentStream { // last_sq, // self.sequence.get() // ); - refresh_tx.send(true).await?; + self.refresh_tx.send(true).await?; interval.tick().await; if last_sq == self.sequence.get() { itvl = itvl.saturating_add(100); break; } else if last_sq + 1 < self.sequence.get() { last_sq = self.sequence.get(); - itvl = itvl.saturating_sub(200); + itvl = itvl.saturating_sub(100); break; } else { last_sq = self.sequence.get(); @@ -84,7 +96,7 @@ impl SegmentStream { } } - pub async fn run(&self, refresh_tx: Sender) -> anyhow::Result<()> { - self.refresh_task(refresh_tx).await + pub async fn run(&self) -> anyhow::Result<()> { + self.refresh_task().await } } diff --git a/src/streamer/youtube.rs b/src/streamer/youtube.rs index 0228fa9..fa1a877 100644 --- a/src/streamer/youtube.rs +++ b/src/streamer/youtube.rs @@ -1,32 +1,33 @@ -use std::{convert::TryInto, sync::Arc}; - -use log::info; -use reqwest::Response; +use crate::{ + config::ConfigManager, + dmlerr, + dmlive::DMLMessage, + ipcmanager::{DMLStream, IPCManager}, + streamer::segment::SegmentStream, +}; +use log::{info, warn}; +use reqwest::{Client, Response}; +use std::{collections::VecDeque, rc::Rc}; use tokio::io::AsyncWriteExt; -use crate::{config::ConfigManager, dmlive::DMLMessage, utils::gen_ua}; - -async fn get_head_sq(resp: &Response) -> Result> { - let sq: usize = resp.headers().get("X-Head-Seqnum").ok_or("no x-head-seqnum")?.to_str()?.parse()?; +fn get_head_sq(resp: &Response) -> anyhow::Result { + let sq: u64 = resp.headers().get("X-Head-Seqnum").ok_or_else(|| dmlerr!())?.to_str()?.parse()?; Ok(sq) } +#[allow(unused)] pub struct Youtube { url_v: String, url_a: String, sq: usize, - ipc_manager: Arc, - cm: Arc, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, } impl Youtube { pub fn new( - url_v: String, - url_a: String, - sq: usize, - cm: Arc, - im: Arc, + url_v: String, url_a: String, sq: usize, cm: Rc, im: Rc, mtx: async_channel::Sender, ) -> Self { Youtube { @@ -39,150 +40,110 @@ impl Youtube { } } - pub async fn download_audio(&self, mut sq: usize) -> Result<(), Box> { - let mut interval: u64 = 1000; - let client = reqwest::Client::builder() - .user_agent(gen_ua()) - .timeout(tokio::time::Duration::from_secs(15)) - .build()?; - let mut tcp_stream = self.ipc_manager.get_audio_socket().await?; - loop { - let u = format!("{}sq/{}", &self.url_a, &sq); - // println!("a: {}", &sq); - let now = std::time::Instant::now(); - let mut resp = client - .get(u) - .header("Connection", "keep-alive") - .header("Referer", "https://www.youtube.com/") - .send() - .await?; - let head_sq = get_head_sq(&resp).await?; + pub async fn download_audio( + &self, client: &Client, stream: &mut Box, sq: &str, + ) -> anyhow::Result<()> { + let u = format!("{}sq/{}", &self.url_a, &sq); + // println!("a: {}", &sq); + let mut resp = client + .get(u) + .header("Connection", "keep-alive") + .header("Referer", "https://www.youtube.com/") + .send() + .await?; + if resp.status() != 200 { + warn!("audio stream error: {:?}", &resp.status()); + // return Ok(()); + } + while let Some(chunk) = resp.chunk().await? { + stream.write_all(&chunk).await?; + } + Ok(()) + } - if resp.status() != 200 { - println!("audio stream error: {:?}", &resp.status()); - return Ok(()); - } - if (head_sq - sq) > 1 { - interval = interval.saturating_sub(100); - } else if (head_sq - sq) < 1 { - info!("a: {}, {}", &sq, &interval); - info!("a: {:?}", resp.headers()); - tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; - interval += 100; - continue; - } - while let Some(chunk) = resp.chunk().await? { - tcp_stream.write_all(&chunk).await?; - } - if (head_sq - sq) <= 1 { - let elapsed: u64 = now.elapsed().as_millis().try_into()?; - if elapsed < interval { - let sleep_time = interval - elapsed; - // info!("a sleep: {}", &sleep_time); - tokio::time::sleep(tokio::time::Duration::from_millis(sleep_time)).await; - } - } - sq += 1; + pub async fn download_video( + &self, client: &Client, stream: &mut Box, sq: &str, + ) -> anyhow::Result<()> { + let u = format!("{}sq/{}", &self.url_v, &sq); + // println!("a: {}", &sq); + let mut resp = client + .get(u) + .header("Connection", "keep-alive") + .header("Referer", "https://www.youtube.com/") + .send() + .await?; + if resp.status() != 200 { + warn!("video stream error: {:?}", &resp.status()); + // return Ok(()); + } + while let Some(chunk) = resp.chunk().await? { + stream.write_all(&chunk).await?; } + Ok(()) } - pub async fn download_video(&self, mut sq: usize) -> Result<(), Box> { - let mut interval: u64 = 1000; - let mut tcp_stream = self.ipc_manager.get_video_socket().await?; - let client = reqwest::Client::builder() - .user_agent(gen_ua()) - .timeout(tokio::time::Duration::from_secs(15)) - .build()?; - loop { - let u = format!("{}sq/{}", &self.url_v, &sq); - // println!("v: {}", &sq); - let now = std::time::Instant::now(); - let mut resp = client + + pub async fn refresh_seq_task(&self, client: &Client, ss: &SegmentStream) -> anyhow::Result<()> { + let mut rx = ss.refresh_rx.borrow_mut(); + let mut sq = self.sq as u64; + while let Some(_) = rx.recv().await { + let u = format!("{}sq/{}", &self.url_v, sq.saturating_sub(2)); + let resp = client .get(&u) .header("Connection", "keep-alive") .header("Referer", "https://www.youtube.com/") .send() .await?; - let head_sq = get_head_sq(&resp).await?; - - if resp.status() != 200 { - info!("video stream error: {:?}", &resp.status()); - return Ok(()); - } - if (head_sq - sq) > 1 { - interval = interval.saturating_sub(100); - } else if (head_sq - sq) < 1 { - info!("v: {}, {}", &sq, &interval); - info!("v: {:?}", resp.headers()); - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - interval += 100; - continue; - } - while let Some(chunk) = resp.chunk().await? { - tcp_stream.write_all(&chunk).await?; + info!("get sq: {:?}", resp.headers()); + sq = get_head_sq(&resp)?; + let mut clips = VecDeque::new(); + for i in sq.saturating_sub(5)..sq { + clips.push_back(i.to_string()); } - if (head_sq - sq) <= 1 { - let elapsed: u64 = now.elapsed().as_millis().try_into()?; - if elapsed < interval { - let sleep_time = interval - elapsed; - // info!("v sleep: {}", &sleep_time); - tokio::time::sleep(tokio::time::Duration::from_millis(sleep_time)).await; - } - } - sq += 1; + ss.update_sequence(sq, clips).await?; } + Ok(()) } - async fn get_dash_sq(&self) -> Option { - let client = reqwest::Client::builder() - .user_agent(gen_ua()) - .timeout(tokio::time::Duration::from_secs(7)) - .build() - .unwrap(); - let u = format!("{}sq/{}", &self.url_v, &self.sq); - let resp = match client - .get(&u) - .header("Connection", "keep-alive") - .header("Referer", "https://www.youtube.com/") - .send() - .await - { - Ok(it) => it, - Err(_) => return None, - }; - info!("get sq: {:?}", resp.headers()); - match get_head_sq(&resp).await { - Ok(it) => Some(it), - Err(_) => None, + pub async fn download_task(&self, client: &Client, ss: &SegmentStream) -> anyhow::Result<()> { + let mut rx = ss.clip_rx.borrow_mut(); + let mut video_stream = None; + let mut audio_stream = None; + while let Some(clip) = rx.recv().await { + info!("youtube: clip {}", &clip); + let (r1, r2) = tokio::join!( + async { + if video_stream.is_none() { + video_stream = Some(self.ipc_manager.get_video_socket().await?) + } + self.download_video(&client, video_stream.as_mut().unwrap(), &clip).await?; + anyhow::Ok(()) + }, + async { + if audio_stream.is_none() { + audio_stream = Some(self.ipc_manager.get_audio_socket().await?) + } + self.download_audio(&client, audio_stream.as_mut().unwrap(), &clip).await?; + anyhow::Ok(()) + }, + ); + r1?; + r2?; } + Ok(()) } - pub async fn run(self: &Arc) { - let sq = match self.get_dash_sq().await { - Some(it) => it, - None => { - println!("youtube streamer get sq error"); - return; - } - }; - let s1 = self.clone(); - let vtask = async move { - match s1.download_video(sq).await { - Ok(_) => {} - Err(err) => { - info!("youtube download video: {:?}", err); - } - } - }; - let s2 = self.clone(); - let atask = async move { - match s2.download_audio(sq).await { - Ok(_) => {} - Err(err) => { - info!("youtube download audio: {:?}", err); - } - } - }; - let _ = futures::future::select(Box::pin(vtask), Box::pin(atask)).await; + pub async fn run(&self) -> anyhow::Result<()> { + let client = reqwest::Client::builder() + .user_agent(crate::utils::gen_ua()) + .timeout(tokio::time::Duration::from_secs(15)) + .build()?; + let seg_stream = SegmentStream::new(); + tokio::select! { + it = self.refresh_seq_task(&client, &seg_stream) => { it?; }, + it = self.download_task(&client, &seg_stream) => { it?; }, + it = seg_stream.run() => { it?; }, + } info!("youtube streamer exit"); + Ok(()) } } diff --git a/src/streamfinder/bilibili.rs b/src/streamfinder/bilibili.rs index fede547..54098c1 100644 --- a/src/streamfinder/bilibili.rs +++ b/src/streamfinder/bilibili.rs @@ -1,36 +1,30 @@ use crate::config::ConfigManager; -use anyhow::anyhow; +use crate::dmlerr; use anyhow::Result; -use log::warn; use regex::Regex; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, rc::Rc}; use url::Url; +const BILI_API1: &'static str = "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo"; +const BILI_API2: &'static str = "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom"; +const BILI_APIV: &'static str = "https://api.bilibili.com/x/player/playurl"; +const BILI_APIV_EP: &'static str = "https://api.bilibili.com/pgc/player/web/playurl"; + pub struct Bilibili { - api1: String, - api2: String, - apiv: String, - apiv_ep: String, - cm: Arc, + cm: Rc, } impl Bilibili { - pub fn new(cm: Arc) -> Self { - Bilibili { - api1: String::from("https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo"), - api2: String::from("https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom"), - apiv: String::from("https://api.bilibili.com/x/player/playurl"), - apiv_ep: String::from("https://api.bilibili.com/pgc/player/web/playurl"), - cm, - } + pub fn new(cm: Rc) -> Self { + Bilibili { cm } } pub async fn get_live(&self, room_url: &str) -> Result> { let rid = Url::parse(room_url)? .path_segments() - .ok_or(anyhow!("rid parse error 1"))? + .ok_or_else(|| dmlerr!())? .last() - .ok_or(anyhow!("rid parse error 2"))? + .ok_or_else(|| dmlerr!())? .to_string(); let client = reqwest::Client::builder() .user_agent(crate::utils::gen_ua()) @@ -50,7 +44,7 @@ impl Bilibili { let cookie = if self.cm.plive { self.cm.bcookie.as_str() } else { "" }; let resp = client - .get(&self.api1) + .get(BILI_API1) .header("Referer", room_url) .header("Cookie", cookie) .query(¶m1) @@ -59,49 +53,34 @@ impl Bilibili { .json::() .await?; // warn!("{:?}", &resp); - let j = - resp.pointer("/data/playurl_info/playurl/stream/0/format/0/codec/0").ok_or(anyhow!("cannot parse json"))?; + let j = resp.pointer("/data/playurl_info/playurl/stream/0/format/0/codec/0").ok_or_else(|| dmlerr!())?; ret.insert( String::from("url"), format!( "{}{}{}", - j.pointer("/url_info/0/host") - .ok_or(anyhow!("json err"))? - .as_str() - .ok_or(anyhow!("cannot convert to string"))?, - j.pointer("/base_url") - .ok_or(anyhow!("json err"))? - .as_str() - .ok_or(anyhow!("cannot convert to string"))?, - j.pointer("/url_info/0/extra") - .ok_or(anyhow!("json err"))? - .as_str() - .ok_or(anyhow!("cannot convert to string"))? + j.pointer("/url_info/0/host").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, + j.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, + j.pointer("/url_info/0/extra").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, ), ); param1.clear(); param1.push(("room_id", rid.as_str())); - let resp = client.get(&self.api2).query(¶m1).send().await?.json::().await?; + let resp = client.get(BILI_API2).query(¶m1).send().await?.json::().await?; ret.insert( String::from("title"), format!( "{} - {}", - resp.pointer("/data/room_info/title") - .ok_or(anyhow!("json err"))? - .as_str() - .ok_or(anyhow!("cannot convert to string"))?, + resp.pointer("/data/room_info/title").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, resp.pointer("/data/anchor_info/base_info/uname") - .ok_or(anyhow!("json err"))? + .ok_or_else(|| dmlerr!())? .as_str() - .ok_or(anyhow!("cannot convert to string"))? + .ok_or_else(|| dmlerr!())? ), ); Ok(ret) } pub async fn get_page_info_ep( - &self, - video_url: &str, - mut page: usize, + &self, video_url: &str, mut page: usize, ) -> Result<(String, String, String, String, String)> { let client = reqwest::Client::builder() .user_agent(crate::utils::gen_ua()) @@ -109,24 +88,23 @@ impl Bilibili { .build()?; let resp = client.get(video_url).header("Referer", video_url).send().await?.text().await?; let re = Regex::new(r"_NEXT_DATA_.+>\s*(\{.+\})\s*<").unwrap(); - let j: serde_json::Value = serde_json::from_str(re.captures(&resp).ok_or(anyhow!("gpie err a1"))?[1].as_ref())?; + let j: serde_json::Value = serde_json::from_str(re.captures(&resp).ok_or_else(|| dmlerr!())?[1].as_ref())?; // println!("{:?}", &j); - let j = j - .pointer("/props/pageProps/dehydratedState/queries/0/state/data/mediaInfo") - .ok_or(anyhow!("gpie err a2"))?; - let season_type = j.pointer("/season_type").ok_or(anyhow!("gpie err b1"))?.as_i64().unwrap().to_string(); - let eplist = j.pointer("/episodes").ok_or(anyhow!("gpie err b2"))?.as_array().unwrap(); + let j = + j.pointer("/props/pageProps/dehydratedState/queries/0/state/data/mediaInfo").ok_or_else(|| dmlerr!())?; + let season_type = j.pointer("/season_type").ok_or_else(|| dmlerr!())?.as_i64().unwrap().to_string(); + let eplist = j.pointer("/episodes").ok_or_else(|| dmlerr!())?.as_array().unwrap(); let epid = url::Url::parse(video_url)? .path_segments() - .ok_or(anyhow!("gpie err b3"))? + .ok_or_else(|| dmlerr!())? .last() - .ok_or(anyhow!("gpie err b4"))? + .ok_or_else(|| dmlerr!())? .to_string(); if page == 0 { page = 1; if epid.starts_with("ep") { for (i, e) in eplist.iter().enumerate() { - if e.pointer("/link").ok_or(anyhow!("gpie err c1"))?.as_str().unwrap().contains(&epid) { + if e.pointer("/link").ok_or_else(|| dmlerr!())?.as_str().unwrap().contains(&epid) { page = i + 1; break; } @@ -137,17 +115,17 @@ impl Bilibili { Some(ep) => ep, None => { page = eplist.len(); - eplist.last().ok_or(anyhow!("gpie err d1"))? + eplist.last().ok_or_else(|| dmlerr!())? } }; - self.cm.bvideo_info.write().await.current_page = page; + self.cm.bvideo_info.borrow_mut().current_page = page; - let bvid = ep.pointer("/bvid").ok_or(anyhow!("gpie err d2"))?.as_str().unwrap().to_string(); - let cid = ep.pointer("/cid").ok_or(anyhow!("gpie err d3"))?.as_i64().unwrap().to_string(); - let subtitle = ep.pointer("/long_title").ok_or(anyhow!("gpie err d4"))?.as_str().unwrap().to_string(); - let title = j.pointer("/title").ok_or(anyhow!("gpie err d5"))?.as_str().unwrap().to_string(); - let title_number = ep.pointer("/titleFormat").ok_or(anyhow!("gpie err d6"))?.as_str().unwrap().to_string(); - let referer = ep.pointer("/link").ok_or(anyhow!("gpie err d7"))?.as_str().unwrap().to_string(); + let bvid = ep.pointer("/bvid").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let cid = ep.pointer("/cid").ok_or_else(|| dmlerr!())?.as_i64().unwrap().to_string(); + let subtitle = ep.pointer("/long_title").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let title = j.pointer("/title").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let title_number = ep.pointer("/titleFormat").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let referer = ep.pointer("/link").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); Ok(( bvid, @@ -167,11 +145,11 @@ impl Bilibili { let resp = resp.text().await?; let re = Regex::new(r"__INITIAL_STATE__=(\{.+?\});").unwrap(); let j: serde_json::Value = - serde_json::from_str(re.captures(&resp).ok_or(anyhow!("gpi err a1"))?[1].to_string().as_ref())?; - let bvid = j.pointer("/videoData/bvid").ok_or(anyhow!("json err"))?.as_str().unwrap().to_string(); - let title = j.pointer("/videoData/title").ok_or(anyhow!("json err"))?.as_str().unwrap(); - let artist = j.pointer("/videoData/owner/name").ok_or(anyhow!("json err"))?.as_str().unwrap().to_string(); - let j = j.pointer("/videoData/pages").ok_or(anyhow!("json err"))?.as_array().unwrap(); + serde_json::from_str(re.captures(&resp).ok_or_else(|| dmlerr!())?[1].to_string().as_ref())?; + let bvid = j.pointer("/videoData/bvid").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let title = j.pointer("/videoData/title").ok_or_else(|| dmlerr!())?.as_str().unwrap(); + let artist = j.pointer("/videoData/owner/name").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + let j = j.pointer("/videoData/pages").ok_or_else(|| dmlerr!())?.as_array().unwrap(); if page == 0 { page = 1; } @@ -179,16 +157,16 @@ impl Bilibili { Some(p) => p, None => { page = j.len(); - j.last().ok_or(anyhow!("gpi err c1"))? + j.last().ok_or_else(|| dmlerr!())? } }; - self.cm.bvideo_info.write().await.current_page = page; + self.cm.bvideo_info.borrow_mut().current_page = page; - let cid = p.pointer("/cid").ok_or(anyhow!("gpi err c2"))?.as_u64().unwrap().to_string(); + let cid = p.pointer("/cid").ok_or_else(|| dmlerr!())?.as_u64().unwrap().to_string(); let final_title = if j.len() == 1 { format!("{} - {}", &title, &artist) } else { - let subtitle = p.pointer("/part").ok_or(anyhow!("gpi err c3"))?.as_str().unwrap(); + let subtitle = p.pointer("/part").ok_or_else(|| dmlerr!())?.as_str().unwrap(); format!("{} - {} - {} - {}", &title, page, &subtitle, &artist) }; @@ -203,10 +181,10 @@ impl Bilibili { }; let mut ret: Vec = Vec::new(); if matches!( - self.cm.bvideo_info.read().await.video_type, + self.cm.bvideo_info.borrow().video_type, crate::config::config::BVideoType::Bangumi ) { - let u = self.cm.bvideo_info.read().await.base_url.clone(); + let u = self.cm.bvideo_info.borrow().base_url.clone(); let (bvid, cid, title, referer, _season_type) = self.get_page_info_ep(&u, page).await?; ret.push(title); ret.push(cid.clone()); @@ -222,7 +200,7 @@ impl Bilibili { .connect_timeout(tokio::time::Duration::from_secs(10)) .build()?; let resp = client - .get(&self.apiv_ep) + .get(BILI_APIV_EP) .header("Referer", &referer) .header("Cookie", cookies) .query(¶m1) @@ -231,35 +209,35 @@ impl Bilibili { .json::() .await?; // println!("{:?}", &resp); - let j = resp.pointer("/result").ok_or(anyhow!("gv err b1"))?; + let j = resp.pointer("/result").ok_or_else(|| dmlerr!())?; let mut videos = HashMap::new(); let mut audios = HashMap::new(); - for ele in j.pointer("/dash/video").ok_or(anyhow!("gv err b2"))?.as_array().unwrap() { - let mut id = ele.pointer("/id").ok_or(anyhow!("gv err k3"))?.as_u64().unwrap() * 10; - if ele.pointer("/codecid").ok_or(anyhow!("gv err k31"))?.as_u64().eq(&Some(7)) { + for ele in j.pointer("/dash/video").ok_or_else(|| dmlerr!())?.as_array().unwrap() { + let mut id = ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap() * 10; + if ele.pointer("/codecid").ok_or_else(|| dmlerr!())?.as_u64().eq(&Some(7)) { id += 1; } videos.insert( id, - ele.pointer("/base_url").ok_or(anyhow!("gv err b4"))?.as_str().unwrap(), + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } - for ele in j.pointer("/dash/audio").ok_or(anyhow!("gv err b5"))?.as_array().unwrap() { + for ele in j.pointer("/dash/audio").ok_or_else(|| dmlerr!())?.as_array().unwrap() { audios.insert( - ele.pointer("/id").ok_or(anyhow!("gv err b6"))?.as_u64().unwrap(), - ele.pointer("/base_url").ok_or(anyhow!("gv err b7"))?.as_str().unwrap(), + ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap(), + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } if let Some(ele) = j.pointer("/dash/flac/audio") { audios.insert( - ele.pointer("/id").ok_or(anyhow!("gv err c1"))?.as_u64().unwrap() + 100, - ele.pointer("/base_url").ok_or(anyhow!("gv err c2"))?.as_str().unwrap(), + ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap() + 100, + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } ret.push(videos.iter().max_by_key(|x| x.0).unwrap().1.to_string()); ret.push(audios.iter().max_by_key(|x| x.0).unwrap().1.to_string()); } else { - let u = self.cm.bvideo_info.read().await.base_url.clone(); + let u = self.cm.bvideo_info.borrow().base_url.clone(); let (bvid, cid, title, _artist) = self.get_page_info(&u, page).await?; // println!("{} {} {} {}", &bvid, &cid, &title, &artist); ret.push(title); @@ -273,36 +251,36 @@ impl Bilibili { .connect_timeout(tokio::time::Duration::from_secs(10)) .build()?; let resp = client - .get(&self.apiv) + .get(BILI_APIV) .header("Cookie", cookies) .query(¶m1) .send() .await? .json::() .await?; - let j = resp.pointer("/data").ok_or(anyhow!("gv err k1"))?; + let j = resp.pointer("/data").ok_or_else(|| dmlerr!())?; let mut videos = HashMap::new(); let mut audios = HashMap::new(); - for ele in j.pointer("/dash/video").ok_or(anyhow!("gv err k2"))?.as_array().unwrap() { - let mut id = ele.pointer("/id").ok_or(anyhow!("gv err k3"))?.as_u64().unwrap() * 10; - if ele.pointer("/codecid").ok_or(anyhow!("gv err k31"))?.as_u64().eq(&Some(7)) { + for ele in j.pointer("/dash/video").ok_or_else(|| dmlerr!())?.as_array().unwrap() { + let mut id = ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap() * 10; + if ele.pointer("/codecid").ok_or_else(|| dmlerr!())?.as_u64().eq(&Some(7)) { id += 1; } videos.insert( id, - ele.pointer("/base_url").ok_or(anyhow!("gv err k4"))?.as_str().unwrap(), + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } - for ele in j.pointer("/dash/audio").ok_or(anyhow!("gv err k5"))?.as_array().unwrap() { + for ele in j.pointer("/dash/audio").ok_or_else(|| dmlerr!())?.as_array().unwrap() { audios.insert( - ele.pointer("/id").ok_or(anyhow!("gv err k6"))?.as_u64().unwrap(), - ele.pointer("/base_url").ok_or(anyhow!("gv err k7"))?.as_str().unwrap(), + ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap(), + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } if let Some(ele) = j.pointer("/dash/flac/audio") { audios.insert( - ele.pointer("/id").ok_or(anyhow!("gv err l1"))?.as_u64().unwrap() + 100, - ele.pointer("/base_url").ok_or(anyhow!("gv err l2"))?.as_str().unwrap(), + ele.pointer("/id").ok_or_else(|| dmlerr!())?.as_u64().unwrap() + 100, + ele.pointer("/base_url").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ); } ret.push(videos.iter().max_by_key(|x| x.0).unwrap().1.to_string()); diff --git a/src/streamfinder/douyu.rs b/src/streamfinder/douyu.rs index aed8eb3..986aec7 100644 --- a/src/streamfinder/douyu.rs +++ b/src/streamfinder/douyu.rs @@ -4,6 +4,12 @@ use regex::Regex; use std::collections::HashMap; use uuid::Uuid; +use crate::dmlerr; + +const DOUYU_API1: &'static str = "https://www.douyu.com/betard/"; +// const DOUYU_API2: &'static str = "https://open.douyucdn.cn/api/RoomApi/room/"; +const DOUYU_API3: &'static str = "https://www.douyu.com/lapi/live/getH5Play/"; + fn get_random_name(l: u8) -> String { let mut ret = String::new(); for _ in 0..l { @@ -21,29 +27,19 @@ fn get_js_md5() -> String { ret } -pub struct Douyu { - api1: String, - // api2: String, - api3: String, -} +pub struct Douyu {} impl Douyu { pub fn new() -> Self { - Douyu { - api1: String::from("https://www.douyu.com/betard/"), - // api2: String::from("https://open.douyucdn.cn/api/RoomApi/room/"), - api3: String::from("https://www.douyu.com/lapi/live/getH5Play/"), - } + Self {} } - pub async fn get_live( - &self, - room_url: &str, - ) -> Result, Box> { + pub async fn get_live(&self, room_url: &str) -> anyhow::Result> { let mut ret = HashMap::new(); let rid = url::Url::parse(room_url)? .path_segments() - .ok_or("rid parse error 1")? + .ok_or_else(|| dmlerr!())? .last() - .ok_or("rid parse error 2")?.to_string(); + .ok_or_else(|| dmlerr!())? + .to_string(); let debug_messages = get_random_name(8); let decrypted_codes = get_random_name(8); let resoult = get_random_name(8); @@ -59,7 +55,7 @@ impl Douyu { .text() .await?; let re = Regex::new(r"(var vdwdae325w_64we =[\s\S]+?)\s*").unwrap(); - let js_enc = re.captures(&resp).ok_or("regex err 1")?[1].to_string(); + let js_enc = re.captures(&resp).ok_or_else(|| dmlerr!())?[1].to_string(); // let re = Regex::new(r"function ub98484234\(.+?\Weval\((\w+)\);").unwrap(); // let workflow = re.captures(&js_enc).ok_or("regex err 2")?[1].to_string(); let js_dom = format!( @@ -93,10 +89,7 @@ impl Douyu { &debug_messages, &decrypted_codes, &workflow ); - let did = Uuid::new_v4() - .as_simple() - .encode_lower(&mut Uuid::encode_buffer()) - .to_string(); + let did = Uuid::new_v4().as_simple().encode_lower(&mut Uuid::encode_buffer()).to_string(); let tsec = format!("{}", Local::now().timestamp()); let js_debug = format!( @@ -119,17 +112,17 @@ impl Douyu { let js_all = format!("{}{}{}{}", &get_js_md5(), &js_dom, &js_enc, &js_debug); let rest1 = crate::utils::js_call(&js_all).await?; - let rest1 = rest1.get(0).ok_or("param error")?; + let rest1 = rest1.get(0).ok_or_else(|| dmlerr!())?; let mut param1 = Vec::new(); let re = Regex::new(r"v=(\d+)").unwrap(); param1.push(( "v", - re.captures(rest1).ok_or("regex err 3")?[1].to_string(), + re.captures(rest1).ok_or_else(|| dmlerr!())?[1].to_string(), )); let re = Regex::new(r"sign=(\w{32})").unwrap(); param1.push(( "sign", - re.captures(rest1).ok_or("regex err 4")?[1].to_string(), + re.captures(rest1).ok_or_else(|| dmlerr!())?[1].to_string(), )); param1.push(("did", did)); param1.push(("tt", tsec)); @@ -140,7 +133,7 @@ impl Douyu { // println!("{:?}", ¶m1); let resp = client - .post(format!("{}{}", self.api3, &rid)) + .post(format!("{}{}", DOUYU_API3, &rid)) .header("User-Agent", crate::utils::gen_ua()) .header("Referer", "https://www.douyu.com/") .form(¶m1) @@ -153,18 +146,12 @@ impl Douyu { String::from("url"), format!( "{}/{}", - resp.pointer("/data/rtmp_url") - .ok_or("json err")? - .as_str() - .ok_or("cannot convert to string")?, - resp.pointer("/data/rtmp_live") - .ok_or("json err")? - .as_str() - .ok_or("cannot convert to string")? + resp.pointer("/data/rtmp_url").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, + resp.pointer("/data/rtmp_live").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())? ), ); let resp = client - .get(format!("{}{}", self.api1, &rid)) + .get(format!("{}{}", DOUYU_API1, &rid)) .header("User-Agent", crate::utils::gen_ua()) .header("Referer", "https://www.douyu.com/") .send() @@ -175,14 +162,8 @@ impl Douyu { String::from("title"), format!( "{} - {}", - resp.pointer("/room/room_name") - .ok_or("json err")? - .as_str() - .ok_or("cannot convert to string")?, - resp.pointer("/room/nickname") - .ok_or("json err")? - .as_str() - .ok_or("cannot convert to string")? + resp.pointer("/room/room_name").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())?, + resp.pointer("/room/nickname").ok_or_else(|| dmlerr!())?.as_str().ok_or_else(|| dmlerr!())? ), ); diff --git a/src/streamfinder/huya.rs b/src/streamfinder/huya.rs index ff75657..ad26008 100644 --- a/src/streamfinder/huya.rs +++ b/src/streamfinder/huya.rs @@ -1,18 +1,16 @@ use regex::Regex; -use std::{ - collections::HashMap, - str, -}; +use std::{collections::HashMap, str}; + +use crate::dmlerr; pub struct Huya {} impl Huya { pub fn new() -> Self { - Huya {} + Self {} } - pub async fn get_live(&self, room_url: &str) -> Result, Box> { - // let rid = Url::parse(room_url)?.path_segments().ok_or("rid parse error 1")?.last().ok_or("rid parse error 2")?.to_string(); + pub async fn get_live(&self, room_url: &str) -> anyhow::Result> { let client = reqwest::Client::new(); let mut ret = HashMap::new(); let resp = client @@ -26,16 +24,16 @@ impl Huya { let re = Regex::new(r#"(?m)(?s)hyPlayerConfig.*?stream:(.*?)\s*};"#).unwrap(); let re1 = Regex::new(r"var\s+TT_PROFILE_INFO\s+=\s+(.+\});").unwrap(); let re2 = Regex::new(r"var\s+TT_ROOM_DATA\s+=\s+(.+\});").unwrap(); - let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or("gl err a1")?[1])?; - let j1: serde_json::Value = serde_json::from_str(&re1.captures(&resp).ok_or("gl err a2")?[1])?; - let j2: serde_json::Value = serde_json::from_str(&re2.captures(&resp).ok_or("gl err a3")?[1])?; + let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; + let j1: serde_json::Value = serde_json::from_str(&re1.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; + let j2: serde_json::Value = serde_json::from_str(&re2.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; // println!("{:?}", &j); ret.insert( String::from("title"), format!( "{} - {}", - j2.pointer("/introduction").ok_or("gl err b1")?.as_str().unwrap(), - j1.pointer("/nick").ok_or("gl err b2")?.as_str().unwrap() + j2.pointer("/introduction").ok_or_else(|| dmlerr!())?.as_str().unwrap(), + j1.pointer("/nick").ok_or_else(|| dmlerr!())?.as_str().unwrap() ), ); ret.insert( @@ -43,10 +41,10 @@ impl Huya { html_escape::decode_html_entities( format!( "{}/{}.{}?{}", - j.pointer("/data/0/gameStreamInfoList/0/sFlvUrl").ok_or("gl err b3")?.as_str().unwrap(), - j.pointer("/data/0/gameStreamInfoList/0/sStreamName").ok_or("gl err b4")?.as_str().unwrap(), - j.pointer("/data/0/gameStreamInfoList/0/sFlvUrlSuffix").ok_or("gl err b5")?.as_str().unwrap(), - j.pointer("/data/0/gameStreamInfoList/0/sFlvAntiCode").ok_or("gl err b6")?.as_str().unwrap(), + j.pointer("/data/0/gameStreamInfoList/0/sFlvUrl").ok_or_else(|| dmlerr!())?.as_str().unwrap(), + j.pointer("/data/0/gameStreamInfoList/0/sStreamName").ok_or_else(|| dmlerr!())?.as_str().unwrap(), + j.pointer("/data/0/gameStreamInfoList/0/sFlvUrlSuffix").ok_or_else(|| dmlerr!())?.as_str().unwrap(), + j.pointer("/data/0/gameStreamInfoList/0/sFlvAntiCode").ok_or_else(|| dmlerr!())?.as_str().unwrap(), ) .as_str(), ) diff --git a/src/streamfinder/mod.rs b/src/streamfinder/mod.rs index 644e295..d73f57b 100644 --- a/src/streamfinder/mod.rs +++ b/src/streamfinder/mod.rs @@ -4,43 +4,37 @@ pub mod huya; pub mod twitch; pub mod youtube; -use crate::{ - config::ConfigManager, - dmlive::DMLMessage, -}; +use crate::ipcmanager::IPCManager; +use crate::{config::ConfigManager, dmlive::DMLMessage}; use anyhow::anyhow; use anyhow::Result; use log::info; use log::warn; -use std::sync::Arc; +use std::rc::Rc; +#[allow(unused)] pub struct StreamFinder { - ipc_manager: Arc, - cm: Arc, + ipc_manager: Rc, + cm: Rc, mtx: async_channel::Sender, } impl StreamFinder { - pub fn new( - cm: Arc, - im: Arc, - mtx: async_channel::Sender, - ) -> Self { + pub fn new(cm: Rc, im: Rc, mtx: async_channel::Sender) -> Self { Self { ipc_manager: im, cm, mtx, } } - pub async fn run_bilivideo(self: &Arc, page: usize) -> Result<(String, Vec)> { - let b = bilibili::Bilibili::new(self.cm.clone()); - match b.get_video(page).await { - Ok(mut u) => Ok((u.remove(0), u)), - Err(e) => Err(e), - } - } - pub async fn run(self: &Arc) -> Result<(String, Vec)> { + // pub async fn run_bilivideo(&self, page: usize) -> Result<(String, Vec)> { + // let b = bilibili::Bilibili::new(self.cm.clone()); + // let mut u = b.get_video(page).await?; + // Ok((u.remove(0), u)) + // } + + pub async fn run(&self) -> Result<(String, Vec)> { loop { for _ in 0..20 { match self.cm.site { @@ -57,7 +51,7 @@ impl StreamFinder { } crate::config::Site::BiliVideo => { let b = bilibili::Bilibili::new(self.cm.clone()); - let p = self.cm.bvideo_info.read().await.current_page; + let p = self.cm.bvideo_info.borrow().current_page; match b.get_video(p).await { Ok(mut u) => { return Ok((u.remove(0), u)); diff --git a/src/streamfinder/twitch.rs b/src/streamfinder/twitch.rs index ad1dd3e..41163c7 100644 --- a/src/streamfinder/twitch.rs +++ b/src/streamfinder/twitch.rs @@ -1,26 +1,26 @@ +use crate::dmlerr; use regex::Regex; use std::collections::HashMap; use url::Url; +const TTV_API1: &'static str = "https://gql.twitch.tv/gql"; +const TTV_API2: &'static str = "https://usher.ttvnw.net/api/channel/hls/{channel}.m3u8"; + pub struct Twitch { - api1: String, - api2: String, } impl Twitch { pub fn new() -> Self { - Twitch { - api1: "https://gql.twitch.tv/gql".to_owned(), - api2: "https://usher.ttvnw.net/api/channel/hls/{channel}.m3u8".to_owned(), + Self { } } pub async fn get_live(&self, room_url: &str) -> anyhow::Result> { let rid = Url::parse(room_url)? .path_segments() - .ok_or(anyhow::anyhow!("rid parse error 1"))? + .ok_or_else(|| dmlerr!())? .last() - .ok_or(anyhow::anyhow!("rid parse error 2"))? + .ok_or_else(|| dmlerr!())? .to_string(); let client = reqwest::Client::new(); let mut ret = HashMap::new(); @@ -36,11 +36,7 @@ impl Twitch { let re = Regex::new(r#""BroadcastSettings\}\|\{[^"]+":.+?"title":"(.+?)""#).unwrap(); ret.insert( String::from("title"), - re.captures(&resp) - .ok_or(anyhow::anyhow!("regex err 1"))? - .get(1) - .ok_or(anyhow::anyhow!("regex err 1-1"))? - .as_str().to_string(), + re.captures(&resp).ok_or_else(|| dmlerr!())?.get(1).ok_or_else(|| dmlerr!())?.as_str().to_string(), ); let mut param1 = Vec::new(); let qu = format!( @@ -48,7 +44,7 @@ impl Twitch { &rid, ); let resp = client - .post(&self.api1) + .post(TTV_API1) .header("User-Agent", crate::utils::gen_ua()) .header("Referer", "https://m.twitch.tv/") .header("Client-Id", "jzkbprff40iqj646a697cyrvl0zt2m6") @@ -60,20 +56,20 @@ impl Twitch { // println!("{:?}", &resp); let sign = resp .pointer("/data/streamPlaybackAccessToken/signature") - .ok_or(anyhow::anyhow!("gl err 1"))? + .ok_or_else(|| dmlerr!())? .as_str() - .ok_or(anyhow::anyhow!("gl err 1-2"))?; + .ok_or_else(|| dmlerr!())?; let token = resp .pointer("/data/streamPlaybackAccessToken/value") - .ok_or(anyhow::anyhow!("gl err 2"))? + .ok_or_else(|| dmlerr!())? .as_str() - .ok_or(anyhow::anyhow!("gl err 2-2"))?; + .ok_or_else(|| dmlerr!())?; param1.clear(); param1.push(("allow_source", "true")); param1.push(("fast_bread", "true")); param1.push(("sig", sign)); param1.push(("token", token)); - let api2 = self.api2.replace("{channel}", &rid); + let api2 = TTV_API2.replace("{channel}", &rid); let resp = client .get(api2) .header("User-Agent", crate::utils::gen_ua()) @@ -88,8 +84,8 @@ impl Twitch { // println!("{}", &resp); let re = Regex::new(r#"[\s\S]+?\n(http[^\n]+)"#).unwrap(); ret.insert( - String::from("url"), - re.captures(&resp).ok_or(anyhow::anyhow!("gl err 3"))?.get(1).ok_or(anyhow::anyhow!("gl err 3-1"))?.as_str().to_string(), + "url".to_string(), + re.captures(&resp).ok_or_else(|| dmlerr!())?.get(1).ok_or_else(|| dmlerr!())?.as_str().to_string(), ); Ok(ret) } diff --git a/src/streamfinder/youtube.rs b/src/streamfinder/youtube.rs index 702080f..2bb5aca 100644 --- a/src/streamfinder/youtube.rs +++ b/src/streamfinder/youtube.rs @@ -2,7 +2,7 @@ use log::debug; use regex::Regex; use std::collections::HashMap; -use crate::utils; +use crate::{dmlerr, utils}; pub struct Youtube {} @@ -11,14 +11,14 @@ impl Youtube { Youtube {} } - pub async fn get_live(&self, room_url: &str) -> Result, Box> { + pub async fn get_live(&self, room_url: &str) -> anyhow::Result> { let client = reqwest::Client::builder() .user_agent(utils::gen_ua()) .timeout(tokio::time::Duration::from_secs(10)) .build()?; let url = url::Url::parse(room_url)?; let room_url = if url.as_str().contains("youtube.com/channel/") { - let cid = url.path_segments().ok_or("gl err a1")?.last().ok_or("gl err a12")?; + let cid = url.path_segments().ok_or_else(|| dmlerr!())?.last().ok_or_else(|| dmlerr!())?; format!("https://www.youtube.com/channel/{}/live", &cid) } else { for q in url.query_pairs() { @@ -36,12 +36,12 @@ impl Youtube { .text() .await?; let re = Regex::new(r"ytInitialPlayerResponse\s*=\s*(\{.+?\});.*?").unwrap(); - let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or("gl err b1")?[1])?; + let j: serde_json::Value = serde_json::from_str(&re.captures(&resp).ok_or_else(|| dmlerr!())?[1])?; // let vid = j.pointer("/videoDetails/videoId").ok_or("gl err b2")?.as_str().unwrap().to_string(); // let cid = j.pointer("/videoDetails/channelId").ok_or("gl err b3")?.as_str().unwrap().to_string(); - let title = j.pointer("/videoDetails/title").ok_or("gl err b4")?.as_str().unwrap().to_string(); - if !(j.pointer("/videoDetails/isLive").ok_or("gl err b5")?.as_bool().unwrap()) { - return Err("gl err b6".into()); + let title = j.pointer("/videoDetails/title").ok_or_else(|| dmlerr!())?.as_str().unwrap().to_string(); + if !(j.pointer("/videoDetails/isLive").ok_or_else(|| dmlerr!())?.as_bool().unwrap()) { + return Err(anyhow::anyhow!("not on air!")); } let mut ret = HashMap::new(); @@ -49,7 +49,7 @@ impl Youtube { let mut video_base_url = Vec::new(); let mut audio_base_url = None; let mut sq = 0; - let mpd_url = j.pointer("/streamingData/dashManifestUrl").ok_or("gl err c1")?.as_str().unwrap(); + let mpd_url = j.pointer("/streamingData/dashManifestUrl").ok_or_else(|| dmlerr!())?.as_str().unwrap(); let resp = client .get(mpd_url) .header("User-Agent", crate::utils::gen_ua()) @@ -114,7 +114,7 @@ impl Youtube { dash_urls.push_str(format!("{}", &sq).as_str()); } if dash_urls.is_empty() { - return Err("gl err d1".into()); + return Err(anyhow::anyhow!("no dash url found")); } ret.insert(String::from("url"), dash_urls); ret.insert(String::from("title"), title); diff --git a/src/utils/cookies.rs b/src/utils/cookies.rs index 128366b..47d1489 100644 --- a/src/utils/cookies.rs +++ b/src/utils/cookies.rs @@ -86,7 +86,7 @@ fn decrypt_chrome_cookie(data: &mut [u8], key: &[u8; 16]) -> anyhow::Result anyhow::Result { +async fn get_chrome_cookies(_host: &str, is_chromium: bool) -> anyhow::Result { // TODO: detect de // let v10_key = b"peanuts"; @@ -140,7 +140,7 @@ WHERE host_key LIKE '.bilibili.com' Ok(ret) } -async fn get_firefox_cookies(host: &str) -> anyhow::Result { +async fn get_firefox_cookies(_host: &str) -> anyhow::Result { let user_dirs = directories::UserDirs::new().ok_or_else(|| anyhow::anyhow!("User dir not found"))?; let ff_dir = user_dirs.home_dir().join(".mozilla").join("firefox"); let dir = std::fs::read_dir(ff_dir)? diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 5853c9c..2bbdbe2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -6,6 +6,18 @@ use tokio::{ process::Command, }; +#[macro_export] +macro_rules! dmlerr { + ($($args: expr),*) => { + anyhow::anyhow!( + "file: {}, line: {}, column: {}", + file!(), + line!(), + column!() + ) + }; +} + pub fn gen_ua() -> String { // let rn = rand::random::(); // let n1 = 50 + (rn % 30); @@ -29,21 +41,37 @@ pub fn gen_ua_safari() -> String { // pub async fn js_call(js: &str, func: &str, args: &Vec<(u8, String)>) -> anyhow::Result> { pub async fn js_call(js: &str) -> anyhow::Result> { - let mut rt = - Command::new("node").stdin(std::process::Stdio::piped()).stdout(std::process::Stdio::piped()).spawn()?; + let mut rt = Command::new("node") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .kill_on_drop(true) + .spawn()?; let mut rtin = rt.stdin.take().unwrap(); let rtout = rt.stdout.take().unwrap(); + let mut reader = tokio::io::BufReader::new(rtout).lines(); let js = js.to_string(); - tokio::task::spawn_local(async move { + tokio::task::spawn(async move { rtin.write_all(js.as_bytes()).await.unwrap(); rtin.shutdown().await.unwrap(); }); - // let rtout = rt.wait_with_output().await.unwrap(); - let mut reader = tokio::io::BufReader::new(rtout).lines(); + // let js_task = async { + // rtin.write_all(js.as_bytes()).await?; + // // rtin.flush().await?; + // rtin.shutdown().await.unwrap(); + // rt.wait().await?; + // anyhow::Ok(()) + // }; let mut ret = Vec::new(); - while let Some(line) = reader.next_line().await.unwrap() { - ret.push(line) + let out_task = async { + while let Some(line) = reader.next_line().await.unwrap() { + ret.push(line); + break; + } + }; + tokio::select! { + // _ = js_task => {}, + _ = out_task => {}, } info!("{:?}", &ret); Ok(ret) @@ -94,7 +122,7 @@ pub fn nm(a: u64, ary: u64) -> Vec { tp(0, a, &vn(ary)) } -pub fn str_to_ms(time_str: &str) -> u64 { +pub fn _str_to_ms(time_str: &str) -> u64 { let mut t = time_str.trim().rsplit(':'); let mut ret = 0f64; let mut i = 0usize; @@ -115,3 +143,16 @@ pub fn str_to_ms(time_str: &str) -> u64 { } (ret * 1000.0) as u64 } + +pub async fn is_android() -> bool { + let output = Command::new("getprop").arg("ro.build.version.release").output(); + match output.await { + Ok(it) => { + if it.status.success() { + return true; + } + } + Err(_) => {} + }; + false +} diff --git a/tars-stream/src/tars_decoder.rs b/tars-stream/src/tars_decoder.rs index 189cc81..50d34e0 100644 --- a/tars-stream/src/tars_decoder.rs +++ b/tars-stream/src/tars_decoder.rs @@ -12,6 +12,8 @@ pub struct TarsDecoder { buf: Bytes, pos: usize, } + +#[allow(unused)] #[derive(Debug)] pub struct Head { tag: u8,