From 6079b7b8847491ad962b6d8e2e4810b7fbce5ce8 Mon Sep 17 00:00:00 2001 From: Brad Culwell Date: Sun, 2 Jun 2024 07:32:05 -0500 Subject: [PATCH] feat: imdb column and unstable captcha solver --- Cargo.lock | 710 ++++++++++++++++++++++++++++++++++- Cargo.toml | 7 + src/app.rs | 55 ++- src/source.rs | 74 ++-- src/source/nyaa_html.rs | 35 +- src/source/nyaa_rss.rs | 8 +- src/source/sukebei_nyaa.rs | 34 +- src/source/torrent_galaxy.rs | 158 ++++++-- src/sync.rs | 16 +- src/util/html.rs | 10 + src/widget.rs | 1 + src/widget/captcha.rs | 96 +++++ src/widget/notify_box.rs | 35 +- src/widget/results.rs | 2 +- tests/common/mod.rs | 8 +- tests/popups.rs | 14 +- 16 files changed, 1125 insertions(+), 138 deletions(-) create mode 100644 src/widget/captcha.rs diff --git a/Cargo.lock b/Cargo.lock index c84b1ca..fb72b25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.16" @@ -60,6 +66,35 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-compression" version = "0.4.6" @@ -92,6 +127,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -113,6 +171,18 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -125,24 +195,48 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitstream-io" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c12d1856e42f0d817a835fe55853957c85c8c8a470114029143d3f12671446e" + [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" + [[package]] name = "bumpalo" version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +[[package]] +name = "bytemuck" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.5.0" @@ -169,6 +263,19 @@ name = "cc" version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] [[package]] name = "cfg-if" @@ -231,6 +338,12 @@ dependencies = [ "serde", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "compact_str" version = "0.7.1" @@ -244,6 +357,34 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "cookie" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" +dependencies = [ + "cookie", + "idna 0.3.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -269,6 +410,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.27.0" @@ -294,6 +460,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "cssparser" version = "0.31.2" @@ -352,6 +524,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive-new" version = "0.5.9" @@ -465,6 +646,12 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ego-tree" version = "0.6.2" @@ -532,12 +719,37 @@ dependencies = [ "str-buf", ] +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -554,6 +766,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -657,6 +878,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.1" @@ -682,6 +913,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -698,6 +939,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -828,12 +1075,28 @@ dependencies = [ "cc", ] +[[package]] +name = "icy_sixel" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86858ae800284d596cfdefcb0ad435c3493c12f35367431bbe9b2b3858c1155b" + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -844,6 +1107,45 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "image-webp", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", +] + +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.6" @@ -854,6 +1156,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + [[package]] name = "ipnet" version = "2.9.0" @@ -894,6 +1207,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.68" @@ -909,12 +1228,29 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libredox" version = "0.0.1" @@ -948,6 +1284,15 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lru" version = "0.12.3" @@ -986,6 +1331,16 @@ dependencies = [ "tendril", ] +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.1" @@ -1020,6 +1375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1080,6 +1436,59 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1110,10 +1519,12 @@ dependencies = [ "directories", "dirs", "human_bytes", + "image", "indexmap", "nix 0.28.0", "open", "ratatui", + "ratatui-image", "regex", "reqwest", "rss", @@ -1351,6 +1762,25 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1372,6 +1802,56 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.52", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.30.0" @@ -1441,6 +1921,91 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "ratatui-image" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d1ed81eabd67488a23fd502b770dc09106918d32ec1746cd20ccc7dd46937f" +dependencies = [ + "base64 0.22.1", + "dyn-clone", + "icy_sixel", + "image", + "rand", + "ratatui", + "rustix", +] + +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1497,8 +2062,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "async-compression", - "base64", + "base64 0.21.7", "bytes", + "cookie", + "cookie_store", "encoding_rs", "futures-core", "futures-util", @@ -1534,6 +2101,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -1598,7 +2174,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64", + "base64 0.21.7", ] [[package]] @@ -1795,6 +2371,21 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -1837,6 +2428,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "stability" @@ -1913,7 +2507,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -1969,6 +2563,25 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "tempfile" version = "3.10.1" @@ -2023,6 +2636,48 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2255,7 +2910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", ] @@ -2271,6 +2926,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -2433,6 +3105,12 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "winapi" version = "0.3.9" @@ -2698,3 +3376,27 @@ dependencies = [ "quote", "syn 2.0.52", ] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 86d0bc9..33df516 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,16 +14,23 @@ license = "GPL-3.0-or-later" strip = true lto = true +[features] +default = [] +unstable-captcha = [] + [dependencies] reqwest = { version = "0.11.27", features = [ "gzip", "rustls-tls", "socks", + "cookies", ], default-features = false } tokio = { version = "1.36.0", features = ["rt", "macros", "rt-multi-thread"] } tokio-util = "0.7.11" urlencoding = "2.1.0" ratatui = "0.26.3" +ratatui-image = "1.0.1" +image = "0.25.1" textwrap = "0.16.1" crossterm = "0.27.0" unicode-width = "0.1.5" diff --git a/src/app.rs b/src/app.rs index 349ecc3..e7e25ea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use std::{error::Error, fmt::Display, time::Instant}; +use std::{error::Error, fmt::Display, sync::Arc, time::Instant}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; use indexmap::IndexMap; @@ -7,6 +7,8 @@ use ratatui::{ layout::{Constraint, Direction, Layout}, Frame, Terminal, }; +use reqwest::cookie::Jar; +use reqwest::{cookie::CookieStore, Url}; use tokio::{sync::mpsc, task::AbortHandle}; use crate::{ @@ -14,12 +16,15 @@ use crate::{ clip, config::{Config, ConfigManager}, results::Results, - source::{nyaa_html::NyaaHtmlSource, request_client, Item, Source, SourceInfo, Sources}, + source::{ + nyaa_html::NyaaHtmlSource, request_client, Item, Source, SourceInfo, SourceResults, Sources, + }, sync::{EventSync, SearchQuery}, theme::{self, Theme}, util::conv::key_to_string, widget::{ batch::BatchWidget, + captcha::CaptchaPopup, category::CategoryPopup, clients::ClientsPopup, filter::FilterPopup, @@ -46,10 +51,11 @@ use crate::util::term; pub static APP_NAME: &str = "nyaa"; -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone)] pub enum LoadType { Sourcing, Searching, + SolvingCaptcha(String), Sorting, Filtering, Categorizing, @@ -62,6 +68,7 @@ impl Display for LoadType { let s = match self { LoadType::Sourcing => "Sourcing", LoadType::Searching => "Searching", + LoadType::SolvingCaptcha(_) => "Solving", LoadType::Sorting => "Sorting", LoadType::Filtering => "Filtering", LoadType::Categorizing => "Categorizing", @@ -88,6 +95,7 @@ pub enum Mode { Page, User, Help, + Captcha, } impl Display for Mode { @@ -106,6 +114,7 @@ impl Display for Mode { Mode::Page => "Page", Mode::User => "User", Mode::Help => "Help", + Mode::Captcha => "Captcha", } .to_owned(); write!(f, "{}", s) @@ -206,6 +215,7 @@ pub struct Widgets { pub page: PagePopup, pub user: UserPopup, pub help: HelpPopup, + pub captcha: CaptchaPopup, } impl App { @@ -217,7 +227,7 @@ impl App { let ctx = &mut Context::default(); let (tx_res, mut rx_res) = - mpsc::channel::>>(32); + mpsc::channel::>>(32); let (tx_evt, mut rx_evt) = mpsc::channel::(100); let (tx_dl, mut rx_dl) = mpsc::channel::(100); @@ -240,10 +250,18 @@ impl App { } } - let client = request_client(ctx)?; + let jar = Arc::new(Jar::default()); + let client = request_client(&jar, ctx)?; let mut last_load_abort: Option = None; let mut last_time: Option = None; + // let bytes = client.get("https://torrentgalaxy.to/captcha/cpt_show.pnp?v=txlight&62fd4c746843c74b53ca60277192fb48").send().await?.bytes().await?; + // let mut picker = Picker::new((1, 2)); + // picker.protocol_type = ProtocolType::Halfblocks; + // let dyn_image = image::load_from_memory(&bytes[..])?; + // let image = picker.new_resize_protocol(dyn_image); + // self.widgets.captcha.image = Some(image); + while !ctx.should_quit { if ctx.should_save_config { if let Err(e) = C::store(&ctx.config) { @@ -274,7 +292,7 @@ impl App { self.get_help(ctx); terminal.draw(|f| self.draw(ctx, f))?; - if let Mode::Loading(load_type) = ctx.mode { + if let Mode::Loading(load_type) = ctx.mode.clone() { ctx.mode = Mode::Normal; match load_type { LoadType::Downloading => { @@ -320,7 +338,7 @@ impl App { _ => {} } - ctx.load_type = Some(load_type); + ctx.load_type = Some(load_type.clone()); if let Some(handle) = last_load_abort.as_ref() { handle.abort(); @@ -337,7 +355,7 @@ impl App { let task = tokio::spawn(sync.clone().load_results( tx_res.clone(), - load_type, + load_type.clone(), ctx.src, client.clone(), search, @@ -363,7 +381,17 @@ impl App { }, Some(rt) = rx_res.recv() => { match rt { - Ok(rt) => ctx.results = rt, + Ok(SourceResults::Results(rt)) => ctx.results = rt, + Ok(SourceResults::Captcha(c)) => { + ctx.mode = Mode::Captcha; + // self.widgets.captcha.ses_id = Some(ses_id); + self.widgets.captcha.image = Some(c); + let cookies = jar.cookies(&Url::parse("https://torrentgalaxy.to/")?); + let x = cookies.map(|c| c.to_str().unwrap_or("").to_owned()).unwrap_or_default(); + ctx.notify(format!("Cookies:\n{}", x)); + // jar.add_cookie_str("", &Url::parse("https://torrentgalaxy.to/")?) + // jar.add_cookies_str(ses_id, Url::parse("")); + } Err(e) => { // Clear results on error ctx.results = Results::default(); @@ -447,6 +475,7 @@ impl App { Mode::User => self.widgets.user.draw(f, ctx, f.size()), Mode::Sources => self.widgets.sources.draw(f, ctx, f.size()), Mode::Clients => self.widgets.clients.draw(f, ctx, f.size()), + Mode::Captcha => self.widgets.captcha.draw(f, ctx, f.size()), Mode::KeyCombo(_) | Mode::Normal | Mode::Search | Mode::Loading(_) | Mode::Batch => {} } self.widgets.notification.draw(f, ctx, f.size()); @@ -458,10 +487,8 @@ impl App { ctx: &mut Context, #[cfg(unix)] terminal: &mut Terminal, ) { - if TEST { - if let Event::FocusLost = evt { - ctx.quit(); - } + if TEST && Event::FocusLost == *evt { + ctx.quit(); } if let Event::Key(KeyEvent { @@ -502,6 +529,7 @@ impl App { Mode::Help => self.widgets.help.handle_event(ctx, evt), Mode::Sources => self.widgets.sources.handle_event(ctx, evt), Mode::Clients => self.widgets.clients.handle_event(ctx, evt), + Mode::Captcha => self.widgets.captcha.handle_event(ctx, evt), Mode::KeyCombo(keys) => self.on_combo(ctx, keys, evt), Mode::Loading(_) => {} } @@ -542,6 +570,7 @@ impl App { Mode::User => UserPopup::get_help(), Mode::Sources => SourcesPopup::get_help(), Mode::Clients => ClientsPopup::get_help(), + Mode::Captcha => CaptchaPopup::get_help(), Mode::Help => None, Mode::KeyCombo(_) => None, Mode::Loading(_) => None, diff --git a/src/source.rs b/src/source.rs index 3cfea05..f492bef 100644 --- a/src/source.rs +++ b/src/source.rs @@ -1,12 +1,13 @@ -use std::{collections::HashMap, error::Error, time::Duration}; +use std::{collections::HashMap, error::Error, sync::Arc, time::Duration}; -use reqwest::Proxy; +use ratatui_image::protocol::StatefulProtocol; +use reqwest::{cookie::Jar, Proxy}; use serde::{Deserialize, Serialize}; use crate::{ app::{Context, LoadType, Widgets}, popup_enum, - results::{ResultResponse, ResultTable}, + results::{ResultResponse, ResultTable, Results}, sync::SearchQuery, theme::Theme, util::conv::add_protocol, @@ -15,7 +16,7 @@ use crate::{ use self::{ nyaa_html::{NyaaConfig, NyaaHtmlSource}, - sukebei_nyaa::{SubekiHtmlSource, SukebeiNyaaConfig}, + sukebei_nyaa::{SukebeiHtmlSource, SukebeiNyaaConfig}, torrent_galaxy::{TgxConfig, TorrentGalaxyHtmlSource}, }; @@ -24,6 +25,18 @@ pub mod nyaa_rss; pub mod sukebei_nyaa; pub mod torrent_galaxy; +#[derive(Clone)] +pub enum SourceResults { + Results(Results), + Captcha(Box), +} + +#[derive(Clone)] +pub enum SourceResponse { + Results(ResultResponse), + Captcha(Box), +} + #[derive(Serialize, Deserialize, Clone, Default)] #[serde(default)] pub struct SourceConfig { @@ -78,9 +91,11 @@ impl SourceInfo { } } -pub fn request_client(ctx: &Context) -> Result { +pub fn request_client(jar: &Arc, ctx: &Context) -> Result { let mut client = reqwest::Client::builder() .gzip(true) + .cookie_provider(jar.clone()) + // .cookie_store(true) .timeout(Duration::from_secs(ctx.config.timeout)); if let Some(proxy_url) = ctx.config.request_proxy.to_owned() { client = client.proxy(Proxy::all(add_protocol(proxy_url, false))?); @@ -129,25 +144,32 @@ pub trait Source { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> impl std::future::Future>> + Send; + ) -> impl std::future::Future>> + Send; fn sort( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> impl std::future::Future>> + Send; + ) -> impl std::future::Future>> + Send; fn filter( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> impl std::future::Future>> + Send; + ) -> impl std::future::Future>> + Send; fn categorize( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> impl std::future::Future>> + Send; + ) -> impl std::future::Future>> + Send; + fn solve( + solution: String, + client: &reqwest::Client, + search: &SearchQuery, + config: &SourceConfig, + date_format: Option, + ) -> impl std::future::Future>> + Send; fn info() -> SourceInfo; fn load_config(config: &mut SourceConfig); @@ -172,7 +194,7 @@ impl Sources { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { match self { Sources::Nyaa => match load_type { LoadType::Searching | LoadType::Sourcing => { @@ -187,20 +209,26 @@ impl Sources { LoadType::Categorizing => { NyaaHtmlSource::categorize(client, search, config, date_format).await } + LoadType::SolvingCaptcha(solution) => { + NyaaHtmlSource::solve(solution, client, search, config, date_format).await + } LoadType::Downloading | LoadType::Batching => unreachable!(), }, Sources::SubekiNyaa => match load_type { LoadType::Searching | LoadType::Sourcing => { - SubekiHtmlSource::search(client, search, config, date_format).await + SukebeiHtmlSource::search(client, search, config, date_format).await } LoadType::Sorting => { - SubekiHtmlSource::sort(client, search, config, date_format).await + SukebeiHtmlSource::sort(client, search, config, date_format).await } LoadType::Filtering => { - SubekiHtmlSource::filter(client, search, config, date_format).await + SukebeiHtmlSource::filter(client, search, config, date_format).await } LoadType::Categorizing => { - SubekiHtmlSource::categorize(client, search, config, date_format).await + SukebeiHtmlSource::categorize(client, search, config, date_format).await + } + LoadType::SolvingCaptcha(solution) => { + SukebeiHtmlSource::solve(solution, client, search, config, date_format).await } LoadType::Downloading | LoadType::Batching => unreachable!(), }, @@ -217,6 +245,10 @@ impl Sources { LoadType::Categorizing => { TorrentGalaxyHtmlSource::categorize(client, search, config, date_format).await } + LoadType::SolvingCaptcha(solution) => { + TorrentGalaxyHtmlSource::solve(solution, client, search, config, date_format) + .await + } LoadType::Downloading | LoadType::Batching => unreachable!(), }, } @@ -246,7 +278,7 @@ impl Sources { pub fn info(self) -> SourceInfo { match self { Sources::Nyaa => NyaaHtmlSource::info(), - Sources::SubekiNyaa => SubekiHtmlSource::info(), + Sources::SubekiNyaa => SukebeiHtmlSource::info(), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::info(), } } @@ -254,7 +286,7 @@ impl Sources { pub fn load_config(self, config: &mut SourceConfig) { match self { Sources::Nyaa => NyaaHtmlSource::load_config(config), - Sources::SubekiNyaa => SubekiHtmlSource::load_config(config), + Sources::SubekiNyaa => SukebeiHtmlSource::load_config(config), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::load_config(config), }; } @@ -262,7 +294,7 @@ impl Sources { pub fn default_category(self, config: &SourceConfig) -> usize { match self { Sources::Nyaa => NyaaHtmlSource::default_category(config), - Sources::SubekiNyaa => SubekiHtmlSource::default_category(config), + Sources::SubekiNyaa => SukebeiHtmlSource::default_category(config), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::default_category(config), } } @@ -270,7 +302,7 @@ impl Sources { pub fn default_sort(self, config: &SourceConfig) -> usize { match self { Sources::Nyaa => NyaaHtmlSource::default_sort(config), - Sources::SubekiNyaa => SubekiHtmlSource::default_sort(config), + Sources::SubekiNyaa => SukebeiHtmlSource::default_sort(config), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::default_sort(config), } } @@ -278,7 +310,7 @@ impl Sources { pub fn default_filter(self, config: &SourceConfig) -> usize { match self { Sources::Nyaa => NyaaHtmlSource::default_filter(config), - Sources::SubekiNyaa => SubekiHtmlSource::default_filter(config), + Sources::SubekiNyaa => SukebeiHtmlSource::default_filter(config), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::default_filter(config), } } @@ -286,7 +318,7 @@ impl Sources { pub fn default_search(self, config: &SourceConfig) -> String { match self { Sources::Nyaa => NyaaHtmlSource::default_search(config), - Sources::SubekiNyaa => SubekiHtmlSource::default_search(config), + Sources::SubekiNyaa => SukebeiHtmlSource::default_search(config), Sources::TorrentGalaxy => TorrentGalaxyHtmlSource::default_search(config), } } @@ -300,7 +332,7 @@ impl Sources { ) -> ResultTable { match self { Sources::Nyaa => NyaaHtmlSource::format_table(items, search, config, theme), - Sources::SubekiNyaa => SubekiHtmlSource::format_table(items, search, config, theme), + Sources::SubekiNyaa => SukebeiHtmlSource::format_table(items, search, config, theme), Sources::TorrentGalaxy => { TorrentGalaxyHtmlSource::format_table(items, search, config, theme) } diff --git a/src/source/nyaa_html.rs b/src/source/nyaa_html.rs index ccf0f96..3a6a2c3 100644 --- a/src/source/nyaa_html.rs +++ b/src/source/nyaa_html.rs @@ -18,12 +18,14 @@ use crate::{ theme::Theme, util::{ conv::{shorten_number, to_bytes}, - html::{attr, inner}, + html::{as_type, attr, inner}, }, widget::{sort::SelectedSort, EnumIter as _}, }; -use super::{add_protocol, nyaa_rss, Item, ItemType, Source, SourceConfig, SourceInfo}; +use super::{ + add_protocol, nyaa_rss, Item, ItemType, Source, SourceConfig, SourceInfo, SourceResponse, +}; #[derive(Serialize, Deserialize, Clone)] #[serde(default)] @@ -187,7 +189,7 @@ impl Source for NyaaHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { let nyaa = config.nyaa.to_owned().unwrap_or_default(); if nyaa.rss { return nyaa_rss::search_rss(client, search, config, date_format).await; @@ -285,9 +287,9 @@ impl Source for NyaaHtmlSource { date = date_time.format(&date_format).to_string(); } - let seeders = inner(e, seed_sel, "0").parse().unwrap_or(0); - let leechers = inner(e, leech_sel, "0").parse().unwrap_or(0); - let downloads = inner(e, dl_sel, "0").parse().unwrap_or(0); + let seeders = as_type(inner(e, seed_sel, "0")).unwrap_or_default(); + let leechers = as_type(inner(e, leech_sel, "0")).unwrap_or_default(); + let downloads = as_type(inner(e, dl_sel, "0")).unwrap_or_default(); let torrent_link = url .join(&torrent) .map(|u| u.to_string()) @@ -326,24 +328,24 @@ impl Source for NyaaHtmlSource { }) .collect(); - Ok(ResultResponse { + Ok(SourceResponse::Results(ResultResponse { items, total_results, last_page, - }) + })) } async fn sort( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { let nyaa = config.nyaa.to_owned().unwrap_or_default(); let sort = search.sort; let mut res = NyaaHtmlSource::search(client, search, config, date_format).await; if nyaa.rss { - if let Ok(res) = &mut res { + if let Ok(SourceResponse::Results(res)) = &mut res { nyaa_rss::sort_items(&mut res.items, sort); } } @@ -354,7 +356,7 @@ impl Source for NyaaHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { NyaaHtmlSource::search(client, search, config, date_format).await } async fn categorize( @@ -362,7 +364,16 @@ impl Source for NyaaHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { + NyaaHtmlSource::search(client, search, config, date_format).await + } + async fn solve( + _solution: String, + client: &reqwest::Client, + search: &SearchQuery, + config: &SourceConfig, + date_format: Option, + ) -> Result> { NyaaHtmlSource::search(client, search, config, date_format).await } diff --git a/src/source/nyaa_rss.rs b/src/source/nyaa_rss.rs index ba2f27e..7a89975 100644 --- a/src/source/nyaa_rss.rs +++ b/src/source/nyaa_rss.rs @@ -15,7 +15,7 @@ use crate::{ use super::{ add_protocol, nyaa_html::{NyaaHtmlSource, NyaaSort}, - Item, ItemType, Source, SourceConfig, + Item, ItemType, Source, SourceConfig, SourceResponse, }; type ExtensionMap = BTreeMap>; @@ -60,7 +60,7 @@ pub async fn search_rss( search: &SearchQuery, config: &SourceConfig, date_format: Option, -) -> Result> { +) -> Result> { let nyaa = config.nyaa.to_owned().unwrap_or_default(); let query = search.query.to_owned(); let cat = search.category; @@ -152,11 +152,11 @@ pub async fn search_rss( .collect(); let total_results = items.len(); sort_items(&mut items, search.sort); - Ok(ResultResponse { + Ok(SourceResponse::Results(ResultResponse { items, last_page, total_results, - }) + })) // Ok(items) // Ok(nyaa_table( // items, diff --git a/src/source/sukebei_nyaa.rs b/src/source/sukebei_nyaa.rs index 8e09a96..6cfb623 100644 --- a/src/source/sukebei_nyaa.rs +++ b/src/source/sukebei_nyaa.rs @@ -22,7 +22,7 @@ use crate::{ use super::{ add_protocol, nyaa_html::{nyaa_table, NyaaColumns, NyaaFilter, NyaaSort}, - Item, ItemType, ResultTable, Source, SourceConfig, SourceInfo, + Item, ItemType, ResultTable, Source, SourceConfig, SourceInfo, SourceResponse, }; #[derive(Serialize, Deserialize, Clone)] @@ -51,39 +51,39 @@ impl Default for SukebeiNyaaConfig { } } -pub struct SubekiHtmlSource; +pub struct SukebeiHtmlSource; -impl Source for SubekiHtmlSource { +impl Source for SukebeiHtmlSource { async fn filter( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { - SubekiHtmlSource::search(client, search, config, date_format).await + ) -> Result> { + SukebeiHtmlSource::search(client, search, config, date_format).await } async fn categorize( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { - SubekiHtmlSource::search(client, search, config, date_format).await + ) -> Result> { + SukebeiHtmlSource::search(client, search, config, date_format).await } async fn sort( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { - SubekiHtmlSource::search(client, search, config, date_format).await + ) -> Result> { + SukebeiHtmlSource::search(client, search, config, date_format).await } async fn search( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { let sukebei = config.sukebei.to_owned().unwrap_or_default(); let cat = search.category; let filter = search.filter; @@ -209,11 +209,11 @@ impl Source for SubekiHtmlSource { }) }) .collect(); - Ok(ResultResponse { + Ok(SourceResponse::Results(ResultResponse { items, last_page, total_results, - }) + })) // Ok(nyaa_table( // items, // &theme, @@ -224,6 +224,16 @@ impl Source for SubekiHtmlSource { // )) } + async fn solve( + _solution: String, + client: &reqwest::Client, + search: &SearchQuery, + config: &SourceConfig, + date_format: Option, + ) -> Result> { + SukebeiHtmlSource::search(client, search, config, date_format).await + } + fn info() -> SourceInfo { let cats = cats! { "All Categories" => { diff --git a/src/source/torrent_galaxy.rs b/src/source/torrent_galaxy.rs index cce2913..9448803 100644 --- a/src/source/torrent_galaxy.rs +++ b/src/source/torrent_galaxy.rs @@ -17,12 +17,12 @@ use crate::{ theme::Theme, util::{ conv::{shorten_number, to_bytes}, - html::{attr, inner}, + html::{as_type, attr, inner}, }, widget::EnumIter as _, }; -use super::{add_protocol, Item, ItemType, Source, SourceConfig, SourceInfo}; +use super::{add_protocol, Item, ItemType, Source, SourceConfig, SourceInfo, SourceResponse}; #[derive(Serialize, Deserialize, Clone)] #[serde(default)] @@ -55,6 +55,7 @@ pub struct TgxColumns { category: Option, language: Option, title: Option, + imdb: Option, uploader: Option, size: Option, date: Option, @@ -64,11 +65,12 @@ pub struct TgxColumns { } impl TgxColumns { - fn array(self) -> [bool; 9] { + fn array(self) -> [bool; 10] { [ self.category.unwrap_or(true), self.language.unwrap_or(true), self.title.unwrap_or(true), + self.imdb.unwrap_or(true), self.uploader.unwrap_or(true), self.size.unwrap_or(true), self.date.unwrap_or(true), @@ -151,7 +153,7 @@ impl Source for TorrentGalaxyHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { TorrentGalaxyHtmlSource::search(client, search, config, date_format).await } async fn categorize( @@ -159,7 +161,7 @@ impl Source for TorrentGalaxyHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { TorrentGalaxyHtmlSource::search(client, search, config, date_format).await } async fn sort( @@ -167,15 +169,16 @@ impl Source for TorrentGalaxyHtmlSource { search: &SearchQuery, config: &SourceConfig, date_format: Option, - ) -> Result> { + ) -> Result> { TorrentGalaxyHtmlSource::search(client, search, config, date_format).await } + async fn search( client: &reqwest::Client, search: &SearchQuery, config: &SourceConfig, _date_format: Option, - ) -> Result> { + ) -> Result> { let tgx = config.tgx.to_owned().unwrap_or_default(); let base_url = Url::parse(&add_protocol(tgx.base_url, true))?.join("torrents.php")?; let query = encode(&search.query); @@ -223,9 +226,24 @@ impl Source for TorrentGalaxyHtmlSource { return Err(format!("{}\nInvalid response code: {}", url, code).into()); } let content = response.text().await?; - let doc = Html::parse_document(&content); let table_sel = &sel!(".tgxtable")?; + #[cfg(feature = "unstable-captcha")] + if Html::parse_document(&content).select(table_sel).count() == 0 { + let mut request = client.get("https://torrentgalaxy.to/captcha/cpt_show.pnp?v=txlight&63fd4c746843c74b53ca60277192fb48"); + if let Some(timeout) = tgx.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + let response = request.send().await?; + let bytes = response.bytes().await?; + let mut picker = ratatui_image::picker::Picker::new((1, 2)); + picker.protocol_type = ratatui_image::picker::ProtocolType::Halfblocks; + let dyn_image = image::load_from_memory(&bytes[..])?; + let image = picker.new_resize_protocol(dyn_image); + + return Ok(SourceResponse::Captcha(image)); + } + let doc = Html::parse_document(&content); if doc.select(table_sel).count() == 0 { return Err(format!( "{}\nNo results table found:\nMost likely due to captcha or rate limit\n\nWait a bit before searching again...", @@ -234,7 +252,8 @@ impl Source for TorrentGalaxyHtmlSource { .into()); } let item_sel = &sel!("div.tgxtablerow")?; - let title_sel = &sel!("div.tgxtablecell:nth-of-type(4) > div > a.txlight")?; + let title_sel = &sel!("div.tgxtablecell:nth-of-type(4) > div > a.txlight:first-of-type")?; + let imdb_sel = &sel!("div.tgxtablecell:nth-of-type(4) > div > a:last-of-type")?; let cat_sel = &sel!("div.tgxtablecell:nth-of-type(1) > a")?; let date_sel = &sel!("div.tgxtablecell:nth-of-type(12)")?; let seed_sel = &sel!("div.tgxtablecell:nth-of-type(11) > span > font:first-of-type > b")?; @@ -264,24 +283,9 @@ impl Source for TorrentGalaxyHtmlSource { .nth(0) .map(|e| e.text().collect()) .unwrap_or_default(); - let seeders = inner(e, seed_sel, "0") - .chars() - .filter(char::is_ascii_digit) - .collect::() - .parse::() - .unwrap_or_default(); - let leechers = inner(e, leech_sel, "0") - .chars() - .filter(char::is_ascii_digit) - .collect::() - .parse::() - .unwrap_or_default(); - let views = inner(e, views_sel, "0") - .chars() - .filter(char::is_ascii_digit) - .collect::() - .parse::() - .unwrap_or_default(); + let seeders = as_type(inner(e, seed_sel, "0")).unwrap_or_default(); + let leechers = as_type(inner(e, leech_sel, "0")).unwrap_or_default(); + let views = as_type(inner(e, views_sel, "0")).unwrap_or_default(); let mut size = inner(e, size_sel, "0 MB"); // Convert numbers like 1,015 KB => 1.01 MB @@ -328,10 +332,17 @@ impl Source for TorrentGalaxyHtmlSource { let hash = torrent_link.split('/').nth(4).unwrap_or("unknown"); let file_name = format!("{}.torrent", hash); + let imdb = attr(e, imdb_sel, "href"); + let imdb = match imdb.rsplit_once('=').map(|r| r.1).unwrap_or("") { + "tt2000000" => "", // For some reason, most XXX titles use this ID + i => i, + }; + let extra: HashMap = collection![ "uploader".to_owned() => inner(e, uploader_sel, "???"), "uploader_status".to_owned() => attr(e, uploader_status_sel, "title"), "lang".to_owned() => attr(e, lang_sel, "title"), + "imdb".to_owned() => imdb.to_owned(), ]; Some(Item { @@ -372,11 +383,83 @@ impl Source for TorrentGalaxyHtmlSource { } } - Ok(ResultResponse { + Ok(SourceResponse::Results(ResultResponse { items, total_results, last_page, - }) + })) + } + + async fn solve( + solution: String, + client: &reqwest::Client, + search: &SearchQuery, + config: &SourceConfig, + date_format: Option, + ) -> Result> { + let tgx = config.tgx.to_owned().unwrap_or_default(); + // let jar = Jar::default(); + // jar.add_cookie_str(cookie, url) + // let client = ClientBuilder::new() + // .cookie_provider(true) + // .timeout(Duration::from_secs(60)) + // .build()?; + // + // let mut headers = HeaderMap::new(); + // headers.insert( + // "Cookie", + // HeaderValue::from_str(&format!("PHPSESSID={}", ses_id))?, + // ); + + let base_url = Url::parse(&add_protocol(tgx.base_url, true))?.join("torrents.php")?; + let query = encode(&search.query); + + let sort = match TgxSort::try_from(search.sort.sort) { + Ok(TgxSort::Date) => "&sort=id", + Ok(TgxSort::Seeders) => "&sort=seeders", + Ok(TgxSort::Leechers) => "&sort=leechers", + Ok(TgxSort::Size) => "&sort=size", + Ok(TgxSort::Name) => "&sort=name", + _ => "", + }; + let ord = format!("&order={}", search.sort.dir.to_url()); + let filter = match TgxFilter::try_from(search.filter) { + Ok(TgxFilter::OnlineStreams) => "&filterstream=1", + Ok(TgxFilter::ExcludeXXX) => "&nox=2&nox=1", + Ok(TgxFilter::NoWildcard) => "&nowildcard=1", + _ => "", + }; + let cat = match search.category { + 0 => "".to_owned(), + x => format!("&c{}=1", x), + }; + + let q = format!( + "search={}&page={}{}{}{}{}", + query, + search.page - 1, + filter, + cat, + sort, + ord + ); + let mut url = base_url.clone(); + url.set_query(Some(&q)); + + let mut request = client.post(format!( + "https://torrentgalaxy.to/galaxyfence.php?captcha={}&dropoff={}", + solution, + encode(&format!( + "{}?{}", + url.path(), + url.query().unwrap_or_default() + )), + )); + if let Some(timeout) = tgx.timeout { + request = request.timeout(Duration::from_secs(timeout)); + } + request.send().await?.text().await?; + TorrentGalaxyHtmlSource::search(client, search, config, date_format).await } fn info() -> SourceInfo { @@ -500,11 +583,18 @@ impl Source for TorrentGalaxyHtmlSource { .max() .unwrap_or_default() as u16; let uploader_width = max(raw_uploader_width, 8); + let raw_imdb_width = items + .iter() + .map(|i| i.extra.get("imdb").map(|u| u.len()).unwrap_or(0)) + .max() + .unwrap_or_default() as u16; + let imdb_width = max(raw_imdb_width, 4); let header = ResultHeader::new([ ResultColumn::Normal("Cat".to_owned(), Constraint::Length(3)), ResultColumn::Normal("".to_owned(), Constraint::Length(2)), ResultColumn::Normal("Name".to_owned(), Constraint::Min(3)), + ResultColumn::Normal("imdb".to_owned(), Constraint::Length(imdb_width)), ResultColumn::Normal("Uploader".to_owned(), Constraint::Length(uploader_width)), ResultColumn::Sorted("Size".to_owned(), 9, TgxSort::Size as u32), ResultColumn::Sorted("Date".to_owned(), date_width, TgxSort::Date as u32), @@ -518,6 +608,7 @@ impl Source for TorrentGalaxyHtmlSource { Alignment::Left, Alignment::Left, Alignment::Left, + Alignment::Left, Alignment::Right, Alignment::Left, Alignment::Right, @@ -539,10 +630,15 @@ impl Source for TorrentGalaxyHtmlSource { ItemType::Remake => theme.remake, ItemType::None => theme.fg, }), + item.extra + .get("imdb") + .cloned() + .unwrap_or_default() + .fg(theme.fg), item.extra .get("uploader") - .unwrap_or(&"???".to_owned()) - .to_owned() + .cloned() + .unwrap_or("???".to_owned()) .fg(item .extra .get("uploader_status") diff --git a/src/sync.rs b/src/sync.rs index a497326..b61f706 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -7,7 +7,7 @@ use crate::{ app::LoadType, client::{Client, ClientConfig, DownloadResult}, results::Results, - source::{Item, SourceConfig, Sources}, + source::{Item, SourceConfig, SourceResponse, SourceResults, Sources}, theme::Theme, widget::sort::SelectedSort, }; @@ -16,7 +16,7 @@ pub trait EventSync { #[allow(clippy::too_many_arguments)] fn load_results( self, - tx_res: mpsc::Sender>>, + tx_res: mpsc::Sender>>, load_type: LoadType, src: Sources, client: reqwest::Client, @@ -56,7 +56,7 @@ pub struct SearchQuery { impl EventSync for AppSync { async fn load_results( self, - tx_res: mpsc::Sender>>, + tx_res: mpsc::Sender>>, load_type: LoadType, src: Sources, client: reqwest::Client, @@ -68,13 +68,15 @@ impl EventSync for AppSync { let res = src .load(load_type, &client, &search, &config, date_format) .await; - let fmt = res.map(|res| { - Results::new( + let fmt = match res { + Ok(SourceResponse::Results(res)) => Ok(SourceResults::Results(Results::new( search.clone(), res.clone(), src.format_table(&res.items, &search, &config, &theme), - ) - }); + ))), + Ok(SourceResponse::Captcha(c)) => Ok(SourceResults::Captcha(c)), + Err(e) => Err(e), + }; let _ = tx_res.send(fmt).await; } diff --git a/src/util/html.rs b/src/util/html.rs index bd4ec25..18e271e 100644 --- a/src/util/html.rs +++ b/src/util/html.rs @@ -1,5 +1,15 @@ +use std::str::FromStr; + use scraper::{ElementRef, Selector}; +pub fn as_type(s: String) -> Option { + s.chars() + .filter(char::is_ascii_digit) + .collect::() + .parse::() + .ok() +} + pub fn inner(e: ElementRef, s: &Selector, default: &str) -> String { e.select(s) .next() diff --git a/src/widget.rs b/src/widget.rs index 375c946..742fe43 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,6 +17,7 @@ use unicode_width::UnicodeWidthStr as _; use crate::{app::Context, style, theme::Theme}; pub mod batch; +pub mod captcha; pub mod category; pub mod clients; pub mod filter; diff --git a/src/widget/captcha.rs b/src/widget/captcha.rs new file mode 100644 index 0000000..1269f27 --- /dev/null +++ b/src/widget/captcha.rs @@ -0,0 +1,96 @@ +use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; +use ratatui::{ + layout::{Constraint, Direction, Layout, Margin, Rect}, + widgets::StatefulWidget as _, + Frame, +}; +use ratatui_image::{protocol::StatefulProtocol, StatefulImage}; + +use crate::app::{Context, LoadType, Mode}; + +use super::{input::InputWidget, Widget}; + +pub struct CaptchaPopup { + pub image: Option>, + pub ses_id: Option, + pub input: InputWidget, +} + +impl Default for CaptchaPopup { + fn default() -> Self { + Self { + image: Default::default(), + ses_id: Default::default(), + input: InputWidget::new(5, None), + } + } +} + +impl Widget for CaptchaPopup { + fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { + let center = area.inner(&Margin { + horizontal: 4, + vertical: 4, + }); + super::clear(center, f.buffer_mut(), ctx.theme.bg); + let layout = Layout::new( + Direction::Vertical, + [Constraint::Fill(1), Constraint::Length(3)], + ) + .split(center); + if let Some(img) = self.image.as_mut() { + let sess_id = self.ses_id.clone().unwrap_or_default(); + f.render_widget( + super::border_block(&ctx.theme, true).title(sess_id), + layout[0], + ); + StatefulImage::new(None).render( + layout[0].inner(&Margin { + horizontal: 1, + vertical: 1, + }), + f.buffer_mut(), + img, + ); + } + f.render_widget(super::border_block(&ctx.theme, true), layout[1]); + + let input_area = layout[1].inner(&Margin { + horizontal: 1, + vertical: 1, + }); + self.input.draw(f, ctx, input_area); + self.input.show_cursor(f, input_area); + } + + fn handle_event(&mut self, ctx: &mut Context, e: &Event) { + if let Event::Key(KeyEvent { + code, + kind: KeyEventKind::Press, + .. + }) = e + { + match code { + KeyCode::Esc => { + ctx.mode = Mode::Normal; + } + KeyCode::Enter => { + ctx.mode = Mode::Loading(LoadType::SolvingCaptcha(self.input.input.clone())); + } + _ => {} + } + } + self.input.handle_event(ctx, e); + } + + fn get_help() -> Option> { + Some(vec![ + ("Enter", "Confirm"), + ("Esc, f, q", "Close"), + ("g", "Top"), + ("G", "Bottom"), + ("j, ↓", "Down"), + ("k, ↑", "Up"), + ]) + } +} diff --git a/src/widget/notify_box.rs b/src/widget/notify_box.rs index ca49d30..922b05a 100644 --- a/src/widget/notify_box.rs +++ b/src/widget/notify_box.rs @@ -36,25 +36,21 @@ impl NotifyPosition { start_offset: u16, stop_offset: u16, ) -> ((i32, i32), (i32, i32), (i32, i32)) { - let stop_x = if self.is_left() { - area.left() as i32 - width as i32 - } else { - area.right() as i32 + 1 + let stop_x = match self.is_left() { + true => area.left() as i32 - width as i32, + false => area.right() as i32 + 1, }; - let start_x = if self.is_left() { - area.left() as i32 + 2 - } else { - area.right() as i32 - width as i32 - 2 + let start_x = match self.is_left() { + true => area.left() as i32 + 2, + false => area.right() as i32 - width as i32 - 1, }; - let start_y = if self.is_top() { - area.top() as i32 - height as i32 + start_offset as i32 + 1 - } else { - area.bottom() as i32 - start_offset as i32 - 1 + let start_y = match self.is_top() { + true => area.top() as i32 - height as i32 + start_offset as i32 + 1, + false => area.bottom() as i32 - start_offset as i32 - 1, }; - let stop_y = if self.is_top() { - area.top() as i32 + stop_offset as i32 + 1 - } else { - area.bottom() as i32 - stop_offset as i32 - height as i32 - 1 + let stop_y = match self.is_top() { + true => area.top() as i32 + stop_offset as i32 + 1, + false => area.bottom() as i32 - stop_offset as i32 - height as i32 - 1, }; ((start_x, start_y), (start_x, stop_y), (stop_x, stop_y)) } @@ -221,13 +217,6 @@ impl NotifyBox { self.stop_offset = (self.stop_offset as i32 + Into::::into(offset)).max(0) as u16; } - // pub fn sub_offset(&mut self, offset: u16) { - // self.start_offset = self.stop_offset + self.height; - // self.stop_offset = (self.stop_offset as i32 - offset as i32).max(0) as u16; - // - // self.enter_state.reset(); - // } - pub fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) { let max_width = match self.error { true => (area.width / 3).max(MAX_WIDTH), diff --git a/src/widget/results.rs b/src/widget/results.rs index 7c92e9a..be13050 100644 --- a/src/widget/results.rs +++ b/src/widget/results.rs @@ -67,7 +67,7 @@ impl super::Widget for ResultsWidget { let header = header.fg(focus_color).underlined(); Clear.render(area, buf); - let items: Vec = match ctx.load_type { + let items: Vec = match &ctx.load_type { Some(loadtype) => { let message = format!("{}…", loadtype); let load_area = centered_rect(message.len() as u16, 1, area); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6216139..b112251 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,7 +6,7 @@ use nyaa::{ client::{Client, ClientConfig, DownloadResult}, config::{Config, ConfigManager}, results::Results, - source::Item, + source::{Item, SourceResults}, sync::EventSync, }; use ratatui::{ @@ -151,7 +151,7 @@ impl EventSync for TestSync { async fn load_results( self, tx_res: tokio::sync::mpsc::Sender< - Result>, + Result>, >, _loadtype: nyaa::app::LoadType, _src: nyaa::source::Sources, @@ -161,7 +161,9 @@ impl EventSync for TestSync { _theme: nyaa::theme::Theme, _date_format: Option, ) { - let _ = tx_res.send(Ok(Results::default())).await; + let _ = tx_res + .send(Ok(SourceResults::Results(Results::default()))) + .await; } async fn read_event_loop(self, tx_evt: tokio::sync::mpsc::Sender) { diff --git a/tests/popups.rs b/tests/popups.rs index 26d0bbb..61170b1 100644 --- a/tests/popups.rs +++ b/tests/popups.rs @@ -23,7 +23,7 @@ async fn test_categories() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search──────────────────────────────└───────────────────┘p┐", + "┌Search──────────────────────────────P└───────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/0─dl: Run Command, src: Nyaa┐", @@ -62,7 +62,7 @@ async fn test_filters() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search─────────────────────────└────────────────────────┘p┐", + "┌Search──────────────────────────└────────────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/0─dl: Run Command, src: Nyaa┐", @@ -101,7 +101,7 @@ async fn test_sort() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search──────────────────────────────Pr└─────────────────┘p┐", + "┌Search──────────────────────────────Pre└─────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/0─dl: Run Command, src: Nyaa┐", @@ -140,7 +140,7 @@ async fn test_sort_reverse() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search──────────────────────────────Pr└─────────────────┘p┐", + "┌Search──────────────────────────────Pre└─────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/0─dl: Run Command, src: Nyaa┐", @@ -178,7 +178,7 @@ async fn test_themes() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "╭Search──────────╰───────────────────────────────────────╯p╮", + "╭Search───────────╰───────────────────────────────────────╯╮", "│ │", "╰──────────────────────────────────────────────────────────╯", "╭Results 1-0 (0 total): Page 1/0─dl: Run Command, src: Nyaa╮", @@ -217,7 +217,7 @@ async fn test_download_client() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search─────────└────────────────────────────────────────┘p┐", + "┌Search──────────└────────────────────────────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/0─dl: Default App, src: Nyaa┐", @@ -256,7 +256,7 @@ async fn test_source() { assert_eq!( reset_buffer(&run_app(sync, 60, 22).await.unwrap()), Buffer::with_lines([ - "┌Search───────────────────────└──────────────────────────┘p┐", + "┌Search────────────────────────└──────────────────────────┘┐", "│ │", "└──────────────────────────────────────────────────────────┘", "┌Results 1-0 (0 total): Page 1/dl: Run Command, src: Subeki┐",