diff --git a/.vscode/settings.json b/.vscode/settings.json index 89cefc3fc7..83f2697f56 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { - "rust-analyzer.cargo.features": "all", - "rust-analyzer.checkOnSave.enable": true, "rust-analyzer.checkOnSave.command": "clippy", + "rust-analyzer.cargo.features": ["helix", "client", "reqwest"], + "rust-analyzer.checkOnSave.enable": true, "rust-analyzer.diagnostics.disabled": [ "type-mismatch" ], diff --git a/Cargo.lock b/Cargo.lock index d1886b75d3..0ef2f50c6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,48 +97,6 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" -[[package]] -name = "askama" -version = "0.11.2" -source = "git+https://github.com/djc/askama?rev=ea8be44#ea8be4421d73a9ef056c05889d87062dfb5a69c8" -dependencies = [ - "askama_derive", - "askama_escape", - "humansize", - "num-traits", - "percent-encoding", -] - -[[package]] -name = "askama_axum" -version = "0.1.0" -source = "git+https://github.com/djc/askama?rev=ea8be44#ea8be4421d73a9ef056c05889d87062dfb5a69c8" -dependencies = [ - "askama", - "axum-core", - "http", -] - -[[package]] -name = "askama_derive" -version = "0.12.0" -source = "git+https://github.com/djc/askama?rev=ea8be44#ea8be4421d73a9ef056c05889d87062dfb5a69c8" -dependencies = [ - "mime", - "mime_guess", - "nom", - "proc-macro2", - "quote", - "serde", - "syn", - "toml", -] - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "git+https://github.com/djc/askama?rev=ea8be44#ea8be4421d73a9ef056c05889d87062dfb5a69c8" - [[package]] name = "async-channel" version = "1.7.1" @@ -241,17 +199,6 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" -[[package]] -name = "async-timer" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5fa6ed76cb2aa820707b4eb9ec46f42da9ce70b0eafab5e5e34942b38a44d5" -dependencies = [ - "libc", - "wasm-bindgen", - "winapi", -] - [[package]] name = "async-trait" version = "0.1.59" @@ -275,56 +222,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "axum" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" -dependencies = [ - "async-trait", - "axum-core", - "base64", - "bitflags", - "bytes 1.3.0", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sha-1", - "sync_wrapper", - "tokio", - "tokio-tungstenite 0.17.2", - "tower", - "tower-http", - "tower-layer", - "tower-service", -] - -[[package]] -name = "axum-core" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" -dependencies = [ - "async-trait", - "bytes 1.3.0", - "futures-util", - "http", - "http-body", - "mime", - "tower-layer", - "tower-service", -] - [[package]] name = "backtrace" version = "0.3.66" @@ -396,12 +293,6 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "bytes" version = "0.5.6" @@ -741,60 +632,6 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" -[[package]] -name = "eventsub" -version = "0.1.0" -dependencies = [ - "askama", - "askama_axum", - "axum", - "clap", - "color-eyre", - "dotenvy", - "eyre", - "futures", - "hyper", - "reqwest", - "retainer", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite 0.18.0", - "tower", - "tower-http", - "tracing", - "tracing-error", - "tracing-log", - "tracing-subscriber", - "twitch_api", - "twitch_oauth2", -] - -[[package]] -name = "eventsub_websocket" -version = "0.1.0" -dependencies = [ - "clap", - "color-eyre", - "dotenvy", - "eyre", - "futures", - "hyper", - "reqwest", - "retainer", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite 0.18.0", - "tracing", - "tracing-error", - "tracing-log", - "tracing-subscriber", - "twitch_api", - "twitch_oauth2", - "url", -] - [[package]] name = "eyre" version = "0.6.8" @@ -1139,12 +976,6 @@ dependencies = [ "log", ] -[[package]] -name = "http-range-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" - [[package]] name = "http-types" version = "2.12.0" @@ -1180,12 +1011,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humansize" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" - [[package]] name = "hyper" version = "0.14.23" @@ -1399,21 +1224,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matchit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" - [[package]] name = "memchr" version = "2.5.0" @@ -1436,12 +1246,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.5.4" @@ -1481,35 +1285,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.14.0" @@ -1592,12 +1367,6 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" version = "3.5.0" @@ -1813,30 +1582,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "regex" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1883,18 +1628,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "retainer" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8c01a8276c14d0f8d51ebcf8a48f0748f9f73f5f6b29e688126e6a52bcb145" -dependencies = [ - "async-lock", - "async-timer", - "log", - "rand 0.8.5", -] - [[package]] name = "ring" version = "0.16.20" @@ -2114,17 +1847,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.6", -] - [[package]] name = "sha1" version = "0.6.1" @@ -2134,17 +1856,6 @@ dependencies = [ "sha1_smol", ] -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.6", -] - [[package]] name = "sha1_smol" version = "1.0.0" @@ -2204,12 +1915,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - [[package]] name = "socket2" version = "0.4.7" @@ -2235,6 +1940,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "standback" version = "0.2.17" @@ -2283,7 +1994,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "sha1 0.6.1", + "sha1", "syn", ] @@ -2338,10 +2049,16 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "0.1.1" +name = "synstructure" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] [[package]] name = "tempfile" @@ -2514,32 +2231,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.17.3", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite 0.18.0", -] - [[package]] name = "tokio-util" version = "0.7.4" @@ -2554,63 +2245,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" -dependencies = [ - "bitflags", - "bytes 1.3.0", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "httpdate", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "tokio", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" version = "0.3.2" @@ -2671,33 +2305,15 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", "sharded-slab", - "smallvec", "thread_local", - "tracing", "tracing-core", - "tracing-log", ] [[package]] @@ -2706,45 +2322,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64", - "byteorder", - "bytes 1.3.0", - "http", - "httparse", - "log", - "rand 0.8.5", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" -dependencies = [ - "base64", - "byteorder", - "bytes 1.3.0", - "http", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1 0.10.5", - "thiserror", - "url", - "utf-8", -] - [[package]] name = "twitch_api" version = "0.7.0-rc.2" @@ -2776,6 +2353,7 @@ dependencies = [ "ureq", "url", "version_check", + "yoke", ] [[package]] @@ -2859,6 +2437,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.4.1" @@ -2903,12 +2487,6 @@ dependencies = [ "serde", ] -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "valuable" version = "0.1.0" @@ -3235,3 +2813,48 @@ dependencies = [ "serde_json", "xshell", ] + +[[package]] +name = "yoke" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe1d55ca72c32d573bfbd5cb2f0ca65a497854c44762957a6d3da96041a5184" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1346e4cd025ae818b88566eac7eb65ab33a994ea55f355c86889af2e7e56b14e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e9355fccf72b04b7deaa99ce7a0f6630530acf34045391b74460fcd714de54" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8aa86add9ddbd2409c1ed01e033cd457d79b1b1229b64922c25095c595e829" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index 3548078a75..abc32b0f98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rust-version = "1.64" [workspace] -members = ["xtask", "examples/*"] +members = ["xtask", ] #"examples/*"] exclude = ["twitch_types", "twitch_oauth2"] [workspace.dependencies] @@ -58,6 +58,7 @@ aliri_braid = "0.3.1" futures = { version = "0.3.25", optional = true } hyper = { version = "0.14.23", optional = true } twitch_types = { version = "0.3.10", path = "./twitch_types" } +yoke = { version = "0.6.2", features = ["derive", "serde"] } [features] default = ["deser_borrow"] @@ -69,83 +70,29 @@ deny_unknown_fields = [] trace_unknown_fields = ["dep:serde_ignored", "tracing"] serde_json = ["dep:serde_json", "dep:serde_path_to_error"] -helix = [ - "twitch_types/color", - "twitch_types/emote", - "twitch_types/goal", - "twitch_types/moderation", - "twitch_types/points", - "twitch_types/stream", - "twitch_types/timestamp", - "twitch_types/user", - "dep:async-trait", - "serde_json", - "dep:hyper", -] +helix = ["twitch_types/color", "twitch_types/emote", "twitch_types/goal", "twitch_types/moderation", "twitch_types/points", "twitch_types/stream", "twitch_types/timestamp", "twitch_types/user", "dep:async-trait", "serde_json", "dep:hyper"] deser_borrow = [] tmi = ["serde_json", "dep:serde_path_to_error", "dep:hyper"] -surf = [ - "dep:surf", - "dep:http-types", - "client", - "twitch_oauth2/surf", - "hyper?/stream", -] +surf = ["dep:surf", "dep:http-types", "client", "twitch_oauth2/surf", "hyper?/stream"] ureq = ["dep:ureq", "client"] reqwest = ["dep:reqwest", "client", "twitch_oauth2/reqwest"] -pubsub = [ - "serde_json", - "twitch_types/emote", - "twitch_types/moderation", - "twitch_types/stream", - "twitch_types/timestamp", - "twitch_types/user", - "twitch_types/points", -] +pubsub = ["serde_json", "twitch_types/emote", "twitch_types/moderation", "twitch_types/stream", "twitch_types/timestamp", "twitch_types/user", "twitch_types/points"] -eventsub = [ - "serde_json", - "twitch_types/emote", - "twitch_types/eventsub", - "twitch_types/goal", - "twitch_types/points", - "twitch_types/stream", - "twitch_types/timestamp", - # insert when websockets are stable - # "serde_json/raw_value", -] +eventsub = ["serde_json", "twitch_types/emote", "twitch_types/eventsub", "twitch_types/goal", "twitch_types/points", "twitch_types/stream", "twitch_types/timestamp"] hmac = ["dep:crypto_hmac", "dep:sha2"] mock_api = [] -all = [ - "tmi", - "helix", - "client", - "pubsub", - "eventsub", - "hmac", - "twitch_oauth2", - "tracing", - "twitch_types/time", -] +all = ["tmi", "helix", "client", "pubsub", "eventsub", "hmac", "twitch_oauth2", "tracing", "twitch_types/time"] -_all = [ - "all", - "typed-builder", - "surf", - "reqwest", - "ureq", - "twitch_oauth2/surf_client_curl", - "mock_api", -] +_all = ["all", "typed-builder", "surf", "reqwest", "ureq", "twitch_oauth2/surf_client_curl", "mock_api"] [dev-dependencies] tokio = { version = "1.22.0", features = ["rt-multi-thread", "macros"] } @@ -159,55 +106,61 @@ hyper = "0.14.23" [build-dependencies] version_check = "0.9.4" -[[example]] -name = "automod_check" -path = "examples/automod_check.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "get_channel_status" -path = "examples/get_channel_status.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "get_moderation" -path = "examples/get_moderation.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "get_streams_and_chatters" -path = "examples/get_streams_and_chatters.rs" -required-features = ["reqwest", "helix", "tmi"] - -[[example]] -name = "modify_channel" -path = "examples/modify_channel.rs" - -required-features = ["reqwest", "helix"] -[[example]] -name = "client" -path = "examples/client.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "channel_information" -path = "examples/channel_information.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "channel_information_custom" -path = "examples/channel_information_custom.rs" -required-features = ["reqwest", "helix", "unsupported"] - -[[example]] -name = "followed_streams" -path = "examples/followed_streams.rs" -required-features = ["reqwest", "helix"] - -[[example]] -name = "mock_api" -path = "examples/mock_api.rs" -required-features = ["reqwest", "helix", "mock_api", "twitch_oauth2/mock_api"] +# [[example]] +# name = "automod_check" +# path = "examples/automod_check.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "get_channel_status" +# path = "examples/get_channel_status.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "get_users" +# path = "examples/get_users.rs" +# required-features = ["reqwest", "helix"] + + +# [[example]] +# name = "get_moderation" +# path = "examples/get_moderation.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "get_streams_and_chatters" +# path = "examples/get_streams_and_chatters.rs" +# required-features = ["reqwest", "helix", "tmi"] + +# [[example]] +# name = "modify_channel" +# path = "examples/modify_channel.rs" + +# required-features = ["reqwest", "helix"] +# [[example]] +# name = "client" +# path = "examples/client.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "channel_information" +# path = "examples/channel_information.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "channel_information_custom" +# path = "examples/channel_information_custom.rs" +# required-features = ["reqwest", "helix", "unsupported"] + +# [[example]] +# name = "followed_streams" +# path = "examples/followed_streams.rs" +# required-features = ["reqwest", "helix"] + +# [[example]] +# name = "mock_api" +# path = "examples/mock_api.rs" +# required-features = ["reqwest", "helix", "mock_api", "twitch_oauth2/mock_api"] [package.metadata.docs.rs] features = ["all", "unsupported", "_all"] diff --git a/examples/get_users.rs b/examples/get_users.rs new file mode 100644 index 0000000000..077cf82c8d --- /dev/null +++ b/examples/get_users.rs @@ -0,0 +1,37 @@ +use twitch_api::HelixClient; +use twitch_oauth2::{AccessToken, UserToken}; + +fn main() { + use std::error::Error; + if let Err(err) = run() { + println!("Error: {err}"); + let mut e: &'_ dyn Error = err.as_ref(); + while let Some(cause) = e.source() { + println!("Caused by: {cause}"); + e = cause; + } + } +} + +#[tokio::main] +async fn run() -> Result<(), Box> { + let _ = dotenvy::dotenv(); + let mut args = std::env::args().skip(1); + let client: HelixClient = HelixClient::default(); + let token = std::env::var("TWITCH_TOKEN") + .ok() + .or_else(|| args.next()) + .map(AccessToken::new) + .expect("Please set env: TWITCH_TOKEN or pass token as first argument"); + let token = UserToken::from_existing(&client, token, None, None).await?; + + let user = client + .get_user_from_id(&*args.next().unwrap(), &token) + .await? + .expect("no user found"); + + println!("User information:\n\t{:#?}", user.get()); + let u = user.get(); + assert!(matches!(u.display_name, std::borrow::Cow::Borrowed(_))); + Ok(()) +} diff --git a/src/helix/client.rs b/src/helix/client.rs index c2f151f359..f88e006528 100644 --- a/src/helix/client.rs +++ b/src/helix/client.rs @@ -113,14 +113,19 @@ impl<'a, C: crate::HttpClient<'a>> HelixClient<'a, C> { /// # } /// # // fn main() {run()} /// ``` - pub async fn req_get( + pub async fn req_get<'req, R, T>( &'a self, request: R, token: &T, - ) -> Result, ClientRequestError<>::Error>> + ) -> Result< + Response>>, + ClientRequestError<>::Error>, + > where - R: Request + Request + RequestGet, - D: serde::de::DeserializeOwned + PartialEq, + R: Request + RequestGet, + for<'y> R::Response: yoke::Yokeable<'y>, + for<'y> yoke::trait_hack::YokeTraitHack<>::Output>: + serde::Deserialize<'y>, T: TwitchToken + ?Sized, C: Send, { @@ -133,107 +138,137 @@ impl<'a, C: crate::HttpClient<'a>> HelixClient<'a, C> { .map_err(ClientRequestError::RequestError)? .into_response_vec() .await?; - ::parse_response(Some(request), &uri, response).map_err(Into::into) - } + let (parts, body) = response.into_parts(); + let mut pagination = None; + let mut request_opt = None; + let mut total = None; + let mut other = None; + let resp: yoke::Yoke<_, _> = yoke::Yoke::try_attach_to_cart::<_, _>( + body, + |body| -> Result<_, ClientRequestError<>::Error>> { + let response = http::Response::from_parts(parts, body); + let Response { + data, + pagination: pagination_inner, + request: request_inner, + total: total_inner, + other: other_inner, + }: Response<_, _> = ::parse_response(Some(request), &uri, &response).unwrap(); + pagination = pagination_inner; + request_opt = request_inner; + total = total_inner; + other = other_inner; + Ok(data) + }, + )?; - /// Request on a valid [`RequestPost`] endpoint - pub async fn req_post( - &'a self, - request: R, - body: B, - token: &T, - ) -> Result, ClientRequestError<>::Error>> - where - R: Request + Request + RequestPost, - B: HelixRequestBody, - D: serde::de::DeserializeOwned + PartialEq, - T: TwitchToken + ?Sized, - { - let req = - request.create_request(body, token.token().secret(), token.client_id().as_str())?; - let uri = req.uri().clone(); - let response = self - .client - .req(req) - .await - .map_err(ClientRequestError::RequestError)? - .into_response_vec() - .await?; - ::parse_response(Some(request), &uri, response).map_err(Into::into) + Ok(Response { + data: resp, + pagination, + request: request_opt, + total, + other, + }) } - /// Request on a valid [`RequestPatch`] endpoint - pub async fn req_patch( - &'a self, - request: R, - body: B, - token: &T, - ) -> Result, ClientRequestError<>::Error>> - where - R: Request + Request + RequestPatch, - B: HelixRequestBody, - D: serde::de::DeserializeOwned + PartialEq, - T: TwitchToken + ?Sized, - { - let req = - request.create_request(body, token.token().secret(), token.client_id().as_str())?; - let uri = req.uri().clone(); - let response = self - .client - .req(req) - .await - .map_err(ClientRequestError::RequestError)? - .into_response_vec() - .await?; - ::parse_response(Some(request), &uri, response).map_err(Into::into) - } + // /// Request on a valid [`RequestPost`] endpoint + // pub async fn req_post( + // &'a self, + // request: R, + // body: B, + // token: &T, + // ) -> Result, ClientRequestError<>::Error>> + // where + // R: Request + Request + RequestPost, + // B: HelixRequestBody, + // D: serde::de::DeserializeOwned + PartialEq, + // T: TwitchToken + ?Sized, + // { + // let req = + // request.create_request(body, token.token().secret(), token.client_id().as_str())?; + // let uri = req.uri().clone(); + // let response = self + // .client + // .req(req) + // .await + // .map_err(ClientRequestError::RequestError)? + // .into_response_vec() + // .await?; + // ::parse_response(Some(request), &uri, response).map_err(Into::into) + // } - /// Request on a valid [`RequestDelete`] endpoint - pub async fn req_delete( - &'a self, - request: R, - token: &T, - ) -> Result, ClientRequestError<>::Error>> - where - R: Request + Request + RequestDelete, - D: serde::de::DeserializeOwned + PartialEq, - T: TwitchToken + ?Sized, - { - let req = request.create_request(token.token().secret(), token.client_id().as_str())?; - let uri = req.uri().clone(); - let response = self - .client - .req(req) - .await - .map_err(ClientRequestError::RequestError)? - .into_response_vec() - .await?; - ::parse_response(Some(request), &uri, response).map_err(Into::into) - } + // /// Request on a valid [`RequestPatch`] endpoint + // pub async fn req_patch( + // &'a self, + // request: R, + // body: B, + // token: &T, + // ) -> Result, ClientRequestError<>::Error>> + // where + // R: Request + Request + RequestPatch, + // B: HelixRequestBody, + // D: serde::de::DeserializeOwned + PartialEq, + // T: TwitchToken + ?Sized, + // { + // let req = + // request.create_request(body, token.token().secret(), token.client_id().as_str())?; + // let uri = req.uri().clone(); + // let response = self + // .client + // .req(req) + // .await + // .map_err(ClientRequestError::RequestError)? + // .into_response_vec() + // .await?; + // ::parse_response(Some(request), &uri, response).map_err(Into::into) + // } - /// Request on a valid [`RequestPut`] endpoint - pub async fn req_put( - &'a self, - request: R, - body: B, - token: &T, - ) -> Result, ClientRequestError<>::Error>> - where - R: Request + Request + RequestPut, - B: HelixRequestBody, - D: serde::de::DeserializeOwned + PartialEq, - T: TwitchToken + ?Sized, - { - let req = - request.create_request(body, token.token().secret(), token.client_id().as_str())?; - let uri = req.uri().clone(); - let response = self - .client - .req(req) - .await - .map_err(ClientRequestError::RequestError)? - .into_response_vec() - .await?; - ::parse_response(Some(request), &uri, response).map_err(Into::into) - } + // /// Request on a valid [`RequestDelete`] endpoint + // pub async fn req_delete( + // &'a self, + // request: R, + // token: &T, + // ) -> Result, ClientRequestError<>::Error>> + // where + // R: Request + Request + RequestDelete, + // D: serde::de::DeserializeOwned + PartialEq, + // T: TwitchToken + ?Sized, + // { + // let req = request.create_request(token.token().secret(), token.client_id().as_str())?; + // let uri = req.uri().clone(); + // let response = self + // .client + // .req(req) + // .await + // .map_err(ClientRequestError::RequestError)? + // .into_response_vec() + // .await?; + // ::parse_response(Some(request), &uri, response).map_err(Into::into) + // } + + // /// Request on a valid [`RequestPut`] endpoint + // pub async fn req_put( + // &'a self, + // request: R, + // body: B, + // token: &T, + // ) -> Result, ClientRequestError<>::Error>> + // where + // R: Request + Request + RequestPut, + // B: HelixRequestBody, + // D: serde::de::DeserializeOwned + PartialEq, + // T: TwitchToken + ?Sized, + // { + // let req = + // request.create_request(body, token.token().secret(), token.client_id().as_str())?; + // let uri = req.uri().clone(); + // let response = self + // .client + // .req(req) + // .await + // .map_err(ClientRequestError::RequestError)? + // .into_response_vec() + // .await?; + // ::parse_response(Some(request), &uri, response).map_err(Into::into) + // } } diff --git a/src/helix/client/client_ext.rs b/src/helix/client/client_ext.rs index eeb7611f6a..3ed86f32f0 100644 --- a/src/helix/client/client_ext.rs +++ b/src/helix/client/client_ext.rs @@ -1,7 +1,4 @@ //! Convenience functions for [HelixClient] - -use std::borrow::Cow; - use crate::helix::{self, ClientRequestError, HelixClient}; use crate::types; use twitch_oauth2::TwitchToken; @@ -11,29 +8,29 @@ type ClientError<'a, C> = ClientRequestError<>::Error // TODO: Consider moving these into the specific modules where the request is defined. Preferably backed by a macro impl<'client, C: crate::HttpClient<'client> + Sync> HelixClient<'client, C> { - /// Get [User](helix::users::User) from user login - pub async fn get_user_from_login( - &'client self, - login: impl Into<&types::UserNameRef>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - self.req_get( - helix::users::GetUsersRequest::logins(&[login.into()][..]), - token, - ) - .await - .map(|response| response.first()) - } + // /// Get [User](helix::users::User) from user login + // pub async fn get_user_from_login( + // &'client self, + // login: impl Into<&types::UserNameRef>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // self.req_get( + // helix::users::GetUsersRequest::logins(&[login.into()][..]), + // token, + // ) + // .await + // .map(|response| response.first()) + // } /// Get [User](helix::users::User) from user id pub async fn get_user_from_id( &'client self, id: impl Into<&types::UserIdRef>, token: &T, - ) -> Result, ClientError<'client, C>> + ) -> Result, Vec>>, ClientError<'client, C>> where T: TwitchToken + ?Sized, { @@ -42,1280 +39,1280 @@ impl<'client, C: crate::HttpClient<'client> + Sync> HelixClient<'client, C> { .map(|response| response.first()) } - /// Get multiple [User](helix::users::User)s from user ids. - pub async fn get_users_from_ids( - &'client self, - ids: impl AsRef<[&types::UserIdRef]>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let ids = ids.as_ref(); - if ids.len() > 100 { - return Err(ClientRequestError::Custom("too many IDs, max 100".into())); - } - self.req_get(helix::users::GetUsersRequest::ids(ids), token) - .await - .map(|response| response.first()) - } - - /// Get [ChannelInformation](helix::channels::ChannelInformation) from a broadcasters login - pub async fn get_channel_from_login( - &'client self, - login: impl Into<&types::UserNameRef>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - if let Some(user) = self.get_user_from_login(login.into(), token).await? { - self.get_channel_from_id(&user.id, token).await - } else { - Ok(None) - } - } - - /// Get [ChannelInformation](helix::channels::ChannelInformation) from a broadcasters id - pub async fn get_channel_from_id( - &'client self, - id: impl Into<&types::UserIdRef>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let ids: &[_] = &[id.into()]; - self.req_get( - helix::channels::GetChannelInformationRequest::broadcaster_ids(ids), - token, - ) - .await - .map(|response| response.first()) - } - - /// Get multiple [ChannelInformation](helix::channels::ChannelInformation) from broadcasters ids - pub async fn get_channels_from_ids<'b, T>( - &'client self, - ids: impl AsRef<[&types::UserIdRef]>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let ids = ids.as_ref(); - if ids.len() > 100 { - return Err(ClientRequestError::Custom("too many IDs, max 100".into())); - } - self.req_get( - helix::channels::GetChannelInformationRequest::broadcaster_ids(ids), - token, - ) - .await - .map(|response| response.data) - } - - /// Get chatters in a stream [Chatter][helix::chat::Chatter] - /// - /// `batch_size` sets the amount of chatters to retrieve per api call, max 1000, defaults to 100. - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let chatters: Vec = client.get_chatters("1234", "4321", 1000, &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - #[cfg(feature = "unsupported")] - pub fn get_chatters( - &'client self, - broadcaster_id: impl Into<&'client types::UserIdRef>, - moderator_id: impl Into<&'client types::UserIdRef>, - batch_size: impl Into>, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::chat::GetChattersRequest { - first: batch_size.into(), - ..helix::chat::GetChattersRequest::new(broadcaster_id.into(), moderator_id.into()) - }; - - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Search [Categories](helix::search::Category) - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let categories: Vec = client.search_categories("Fortnite", &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn search_categories( - &'client self, - query: impl Into<&'client str>, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::search::SearchCategoriesRequest::query(query.into()).first(100); - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Search [Channels](helix::search::Channel) via channel name or description - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let channel: Vec = client.search_channels("twitchdev", false, &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn search_channels<'b, T>( - &'client self, - query: impl Into<&'b str>, - live_only: bool, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - 'b: 'client, - { - let req = helix::search::SearchChannelsRequest::query(query.into()).live_only(live_only); - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Get information on a [follow relationship](helix::users::FollowRelationship) - /// - /// Can be used to see if X follows Y - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::{types, helix}; - /// use futures::TryStreamExt; - /// - /// // Get the followers of channel "1234" - /// let followers: Vec = client.get_follow_relationships(Some("1234".into()), None, &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_follow_relationships<'b, T>( - &'client self, - to_id: impl Into>, - from_id: impl Into>, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream< - Item = Result>, - > + Send - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - 'b: 'client, - { - let mut req = helix::users::GetUsersFollowsRequest::empty(); - req.to_id = to_id.into().map(Cow::Borrowed); - req.from_id = from_id.into().map(Cow::Borrowed); - - make_stream(req, token, self, |s| { - std::collections::VecDeque::from(s.follow_relationships) - }) - } - - /// Get authenticated users' followed [streams](helix::streams::Stream) - /// - /// Requires token with scope [`user:read:follows`](twitch_oauth2::Scope::UserReadFollows). - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let channels: Vec = client.get_followed_streams(&token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_followed_streams( - &'client self, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - use futures::StreamExt; - - let user_id = match token - .user_id() - .ok_or_else(|| ClientRequestError::Custom("no user_id found on token".into())) - { - Ok(t) => t, - Err(e) => return futures::stream::once(async { Err(e) }).boxed(), - }; - let req = helix::streams::GetFollowedStreamsRequest::user_id(user_id); - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Get authenticated broadcasters' [subscribers](helix::subscriptions::BroadcasterSubscription) - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let subs: Vec = client.get_broadcaster_subscriptions(&token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_broadcaster_subscriptions( - &'client self, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream< - Item = Result< - helix::subscriptions::BroadcasterSubscription, - ClientError<'client, C>, - >, - > + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - use futures::StreamExt; - - let user_id = match token - .user_id() - .ok_or_else(|| ClientRequestError::Custom("no user_id found on token".into())) - { - Ok(t) => t, - Err(e) => return futures::stream::once(async { Err(e) }).boxed(), - }; - let req = helix::subscriptions::GetBroadcasterSubscriptionsRequest::broadcaster_id(user_id); - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Get all moderators in a channel [Get Moderators](helix::moderation::GetModeratorsRequest) - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let moderators: Vec = client.get_moderators_in_channel_from_id("twitchdev", &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_moderators_in_channel_from_id<'b: 'client, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream< - Item = Result>, - > + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::moderation::GetModeratorsRequest::broadcaster_id(broadcaster_id); - - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Get all banned users in a channel [Get Banned Users](helix::moderation::GetBannedUsersRequest) - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let moderators: Vec = client.get_banned_users_in_channel_from_id("twitchdev", &token).try_collect().await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_banned_users_in_channel_from_id<'b: 'client, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream< - Item = Result>, - > + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::moderation::GetBannedUsersRequest::broadcaster_id(broadcaster_id); - - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Get a users, with login, follow count - pub async fn get_total_followers_from_login<'b, T>( - &'client self, - login: impl types::IntoCow<'b, types::UserNameRef> + 'b, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - if let Some(user) = self.get_user_from_login(&*login.to_cow(), token).await? { - self.get_total_followers_from_id(&user.id, token) - .await - .map(Some) - } else { - Ok(None) - } - } - - /// Get a users, with id, follow count - /// - /// # Notes - /// - /// This returns zero if the user doesn't exist - pub async fn get_total_followers_from_id<'b, T>( - &'client self, - to_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let resp = self - .req_get( - helix::users::GetUsersFollowsRequest::followers(to_id), - token, - ) - .await?; - - Ok(resp.data.total) - } - - /// Get games by ID. Can only be at max 100 ids. - pub async fn get_games_by_id( - &'client self, - ids: impl AsRef<[&'client types::CategoryIdRef]>, - token: &T, - ) -> Result< - std::collections::HashMap, - ClientError<'client, C>, - > - where - T: TwitchToken + ?Sized, - { - let ids = ids.as_ref(); - if ids.len() > 100 { - return Err(ClientRequestError::Custom("too many IDs, max 100".into())); - } - - let resp = self - .req_get(helix::games::GetGamesRequest::ids(ids), token) - .await?; - - Ok(resp - .data - .into_iter() - .map(|g: helix::games::Game| (g.id.clone(), g)) - .collect()) - } - - /// Block a user - pub async fn block_user<'b, T>( - &'client self, - target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_put( - helix::users::BlockUserRequest::block_user(target_user_id), - helix::EmptyBody, - token, - ) - .await? - .data) - } - - /// Unblock a user - pub async fn unblock_user<'b, T>( - &'client self, - target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_delete( - helix::users::UnblockUserRequest::unblock_user(target_user_id), - token, - ) - .await? - .data) - } - - /// Ban a user - pub async fn ban_user<'b, T>( - &'client self, - target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - reason: impl Into<&'b str>, - duration: impl Into>, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_post( - helix::moderation::BanUserRequest::new(broadcaster_id, moderator_id), - helix::moderation::BanUserBody::new(target_user_id, reason.into(), duration), - token, - ) - .await? - .data) - } - - /// Unban a user - pub async fn unban_user<'b, T>( - &'client self, - target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_delete( - helix::moderation::UnbanUserRequest::new( - broadcaster_id, - moderator_id, - target_user_id, - ), - token, - ) - .await? - .data) - } - - // FIXME: Example should use https://github.com/twitch-rs/twitch_api/issues/162 - /// Get all scheduled streams in a channel. - /// - /// # Notes - /// - /// Make sure to limit the data here using [`try_take_while`](futures::stream::TryStreamExt::try_take_while), otherwise this will never end on recurring scheduled streams. - /// - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// use futures::TryStreamExt; - /// - /// let schedule: Vec = client - /// .get_channel_schedule("twitchdev", &token) - /// .try_take_while(|s| { - /// futures::future::ready(Ok(!s.start_time.as_str().starts_with("2021-10"))) - /// }) - /// .try_collect() - /// .await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_channel_schedule<'b: 'client, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::schedule::GetChannelStreamScheduleRequest::broadcaster_id(broadcaster_id); - - make_stream(req, token, self, |broadcasts| broadcasts.segments.into()) - } - - /// Get all global emotes - pub async fn get_global_emotes( - &'client self, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let req = helix::chat::GetGlobalEmotesRequest::new(); - Ok(self.req_get(req, token).await?.data) - } - - /// Get channel emotes in channel with user id - pub async fn get_channel_emotes_from_id<'b, T>( - &'client self, - user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let req = helix::chat::GetChannelEmotesRequest::broadcaster_id(user_id); - Ok(self.req_get(req, token).await?.data) - } - - /// Get channel emotes in channel with user login - pub async fn get_channel_emotes_from_login( - &'client self, - login: impl types::IntoCow<'client, types::UserNameRef> + 'client, - token: &T, - ) -> Result>, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - if let Some(user) = self - .get_user_from_login(login.to_cow().as_ref(), token) - .await? - { - self.get_channel_emotes_from_id(&user.id, token) - .await - .map(Some) - } else { - Ok(None) - } - } - - /// Get emotes in emote set - pub async fn get_emote_sets( - &'client self, - emote_sets: impl AsRef<[&types::EmoteSetIdRef]>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let emote_sets = emote_sets.as_ref(); - let req = helix::chat::GetEmoteSetsRequest::emote_set_ids(emote_sets); - Ok(self.req_get(req, token).await?.data) - } - - /// Get a broadcaster's chat settings - pub async fn get_chat_settings<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl Into> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let mut req = helix::chat::GetChatSettingsRequest::broadcaster_id(broadcaster_id); - if let Some(moderator_id) = moderator_id.into() { - req = req.moderator_id(moderator_id); - } - Ok(self.req_get(req, token).await?.data) - } - - /// Send a chat announcement - pub async fn send_chat_announcement<'b, T, E>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - message: impl Into<&'b str>, - color: impl std::convert::TryInto, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::chat::SendChatAnnouncementRequest::new(broadcaster_id, moderator_id); - let body = helix::chat::SendChatAnnouncementBody::new(message.into(), color)?; - Ok(self - .req_post(req, body, token) - .await - .map_err(ClientExtError::ClientError)? - .data) - } - - /// Delete a specific chat message - pub async fn delete_chat_message<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - message_id: impl types::IntoCow<'b, types::MsgIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::moderation::DeleteChatMessagesRequest::new(broadcaster_id, moderator_id) - .message_id(message_id); - Ok(self.req_delete(req, token).await?.data) - } - - /// Delete all chat messages in a broadcasters chat room - pub async fn delete_all_chat_message<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::moderation::DeleteChatMessagesRequest::new(broadcaster_id, moderator_id); - Ok(self.req_delete(req, token).await?.data) - } - - /// Start a raid - pub async fn start_a_raid<'b, T>( - &'client self, - from_broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - to_broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::raids::StartARaidRequest::new(from_broadcaster_id, to_broadcaster_id); - Ok(self.req_post(req, helix::EmptyBody, token).await?.data) - } - - /// Cancel a raid - pub async fn cancel_a_raid<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::raids::CancelARaidRequest::broadcaster_id(broadcaster_id); - Ok(self.req_delete(req, token).await?.data) - } - - /// Get a users chat color - pub async fn get_user_chat_color( - &'client self, - user_id: impl Into<&types::UserIdRef>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_get( - helix::chat::GetUserChatColorRequest::user_ids(&[user_id.into()][..]), - token, - ) - .await? - .first()) - } - - /// Get a users chat color - pub async fn update_user_chat_color<'b, T>( - &'client self, - user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - color: impl Into> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::chat::UpdateUserChatColorRequest { - user_id: user_id.to_cow(), - color: color.into(), - }; - - Ok(self.req_put(req, helix::EmptyBody, token).await?.data) - } - - /// Get multiple users chat colors - pub async fn get_users_chat_colors( - &'client self, - user_ids: impl AsRef<[&types::UserIdRef]>, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - let user_ids = user_ids.as_ref(); - if user_ids.len() > 100 { - return Err(ClientRequestError::Custom("too many IDs, max 100".into())); - } - let req = helix::chat::GetUserChatColorRequest::user_ids(user_ids); - - Ok(self.req_get(req, token).await?.data) - } - - /// Add a channel moderator - pub async fn add_channel_moderator<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::moderation::AddChannelModeratorRequest { - broadcaster_id: broadcaster_id.to_cow(), - moderator_id: moderator_id.to_cow(), - }; - - Ok(self.req_post(req, helix::EmptyBody, token).await?.data) - } - - /// Remove a channel moderator - pub async fn remove_channel_moderator<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::moderation::RemoveChannelModeratorRequest { - broadcaster_id: broadcaster_id.to_cow(), - moderator_id: moderator_id.to_cow(), - }; - - Ok(self.req_delete(req, token).await?.data) - } - - /// Get channel VIPs - pub fn get_vips_in_channel<'b: 'client, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream>> - + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::channels::GetVipsRequest::broadcaster_id(broadcaster_id); - - make_stream(req, token, self, std::collections::VecDeque::from) - } - - /// Add a channel vip - pub async fn add_channel_vip<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::channels::AddChannelVipRequest { - broadcaster_id: broadcaster_id.to_cow(), - user_id: user_id.to_cow(), - }; - - Ok(self.req_post(req, helix::EmptyBody, token).await?.data) - } - - /// Remove a channel vip - pub async fn remove_channel_vip<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - let req = helix::channels::RemoveChannelVipRequest { - broadcaster_id: broadcaster_id.to_cow(), - user_id: user_id.to_cow(), - }; - - Ok(self.req_delete(req, token).await?.data) - } - - /// Send a whisper - pub async fn send_whisper<'b, T>( - &'client self, - from: impl types::IntoCow<'b, types::UserIdRef> + 'b, - to: impl types::IntoCow<'b, types::UserIdRef> + 'b, - message: impl Into<&'b str>, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_post( - helix::whispers::SendWhisperRequest::new(from, to), - helix::whispers::SendWhisperBody::new(message.into()), - token, - ) - .await? - .data) - } - - /// Get all custom rewards - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// - /// let rewards: Vec = client - /// .get_all_custom_rewards("1234", true, &token) - /// .await?; - /// - /// # Ok(()) } - /// ``` - pub async fn get_all_custom_rewards<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - only_managable_rewards: bool, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - self.get_custom_rewards(broadcaster_id, only_managable_rewards, &[], token) - .await - } - - /// Get specific custom rewards, see [`get_all_custom_rewards`](HelixClient::get_all_custom_rewards) to get all rewards - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::helix; - /// - /// let rewards: Vec = client - /// .get_custom_rewards("1234", true, &["8969ec47-55b6-4559-a8fe-3f1fc4e6fe58".into()], &token) - /// .await?; - /// - /// # Ok(()) } - /// ``` - pub async fn get_custom_rewards<'b, T>( - &'client self, - broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, - only_managable_rewards: bool, - // FIXME: This should be `impl AsRef<[&'b T]> + 'b` - ids: &'b [&'b types::RewardIdRef], - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_get( - helix::points::GetCustomRewardRequest::broadcaster_id(broadcaster_id) - .only_manageable_rewards(only_managable_rewards) - .ids(ids), - token, - ) - .await? - .data) - } - - #[cfg(feature = "eventsub")] - /// Create an [EventSub](crate::eventsub) subscription - pub async fn create_eventsub_subscription( - &'client self, - subscription: E, - transport: crate::eventsub::Transport, - token: &T, - ) -> Result, ClientError<'client, C>> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_post( - helix::eventsub::CreateEventSubSubscriptionRequest::new(), - helix::eventsub::CreateEventSubSubscriptionBody::new(subscription, transport), - token, - ) - .await? - .data) - } - - #[cfg(feature = "eventsub")] - /// Delete an [EventSub](crate::eventsub) subscription - pub async fn delete_eventsub_subscription<'b, T>( - &'client self, - id: impl types::IntoCow<'b, types::EventSubIdRef> + 'b, - token: &T, - ) -> Result> - where - T: TwitchToken + ?Sized, - { - Ok(self - .req_delete( - helix::eventsub::DeleteEventSubSubscriptionRequest::id(id), - token, - ) - .await? - .data) - } - - #[cfg(feature = "eventsub")] - /// Get all [EventSub](crate::eventsub) subscriptions for this [Client](twitch_oauth2::TwitchToken) - /// - /// # Notes - /// - /// The return item is a struct [`EventSubSubscriptions`](helix::eventsub::EventSubSubscriptions) which contains the subscriptions. - /// See the example for getting only the subscriptions - /// - /// # Examples - /// - /// ```rust, no_run - /// # #[tokio::main] - /// # async fn main() -> Result<(), Box> { - /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); - /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); - /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; - /// use twitch_api::{helix, eventsub}; - /// use futures::{TryStreamExt, stream}; - /// - /// let mut total_cost = None; - /// - /// let chatters: Vec = client - /// .get_eventsub_subscriptions(None, None, None, &token) - /// .map_ok(|r| { - /// total_cost = Some(r.total_cost); - /// stream::iter( - /// r.subscriptions - /// .into_iter() - /// .map(Ok::<_, twitch_api::helix::ClientRequestError<_>>), - /// ) - /// }) - /// .try_flatten() - /// .try_collect() - /// .await?; - /// - /// # Ok(()) } - /// ``` - pub fn get_eventsub_subscriptions<'b: 'client, T>( - &'client self, - status: impl Into>, - event_type: impl Into>, - // FIXME: IntoOptionCow? - user_id: Option<&'b types::UserIdRef>, - token: &'client T, - ) -> std::pin::Pin< - Box< - dyn futures::Stream< - Item = Result>, - > + 'client, - >, - > - where - T: TwitchToken + Send + Sync + ?Sized, - { - let req = helix::eventsub::GetEventSubSubscriptionsRequest { - status: status.into(), - type_: event_type.into(), - user_id: user_id.map(|c| c.as_cow()), - after: None, - first: None, - }; - - make_stream(req, token, self, |r| { - let mut vec = std::collections::VecDeque::new(); - vec.push_front(r); - vec - }) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum ClientExtError<'a, C: crate::HttpClient<'a>, E> { - #[error(transparent)] - ClientError(ClientError<'a, C>), - #[error(transparent)] - Other(#[from] E), + // /// Get multiple [User](helix::users::User)s from user ids. + // pub async fn get_users_from_ids( + // &'client self, + // ids: impl AsRef<[&types::UserIdRef]>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let ids = ids.as_ref(); + // if ids.len() > 100 { + // return Err(ClientRequestError::Custom("too many IDs, max 100".into())); + // } + // self.req_get(helix::users::GetUsersRequest::ids(ids), token) + // .await + // .map(|response| response.first()) + // } + + // /// Get [ChannelInformation](helix::channels::ChannelInformation) from a broadcasters login + // pub async fn get_channel_from_login( + // &'client self, + // login: impl Into<&types::UserNameRef>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // if let Some(user) = self.get_user_from_login(login.into(), token).await? { + // self.get_channel_from_id(&user.id, token).await + // } else { + // Ok(None) + // } + // } + + // /// Get [ChannelInformation](helix::channels::ChannelInformation) from a broadcasters id + // pub async fn get_channel_from_id( + // &'client self, + // id: impl Into<&types::UserIdRef>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let ids: &[_] = &[id.into()]; + // self.req_get( + // helix::channels::GetChannelInformationRequest::broadcaster_ids(ids), + // token, + // ) + // .await + // .map(|response| response.first()) + // } + + // /// Get multiple [ChannelInformation](helix::channels::ChannelInformation) from broadcasters ids + // pub async fn get_channels_from_ids<'b, T>( + // &'client self, + // ids: impl AsRef<[&types::UserIdRef]>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let ids = ids.as_ref(); + // if ids.len() > 100 { + // return Err(ClientRequestError::Custom("too many IDs, max 100".into())); + // } + // self.req_get( + // helix::channels::GetChannelInformationRequest::broadcaster_ids(ids), + // token, + // ) + // .await + // .map(|response| response.data) + // } + + // /// Get chatters in a stream [Chatter][helix::chat::Chatter] + // /// + // /// `batch_size` sets the amount of chatters to retrieve per api call, max 1000, defaults to 100. + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let chatters: Vec = client.get_chatters("1234", "4321", 1000, &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // #[cfg(feature = "unsupported")] + // pub fn get_chatters( + // &'client self, + // broadcaster_id: impl Into<&'client types::UserIdRef>, + // moderator_id: impl Into<&'client types::UserIdRef>, + // batch_size: impl Into>, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::chat::GetChattersRequest { + // first: batch_size.into(), + // ..helix::chat::GetChattersRequest::new(broadcaster_id.into(), moderator_id.into()) + // }; + + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Search [Categories](helix::search::Category) + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let categories: Vec = client.search_categories("Fortnite", &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn search_categories( + // &'client self, + // query: impl Into<&'client str>, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::search::SearchCategoriesRequest::query(query.into()).first(100); + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Search [Channels](helix::search::Channel) via channel name or description + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let channel: Vec = client.search_channels("twitchdev", false, &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn search_channels<'b, T>( + // &'client self, + // query: impl Into<&'b str>, + // live_only: bool, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // 'b: 'client, + // { + // let req = helix::search::SearchChannelsRequest::query(query.into()).live_only(live_only); + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Get information on a [follow relationship](helix::users::FollowRelationship) + // /// + // /// Can be used to see if X follows Y + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::{types, helix}; + // /// use futures::TryStreamExt; + // /// + // /// // Get the followers of channel "1234" + // /// let followers: Vec = client.get_follow_relationships(Some("1234".into()), None, &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_follow_relationships<'b, T>( + // &'client self, + // to_id: impl Into>, + // from_id: impl Into>, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream< + // Item = Result>, + // > + Send + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // 'b: 'client, + // { + // let mut req = helix::users::GetUsersFollowsRequest::empty(); + // req.to_id = to_id.into().map(Cow::Borrowed); + // req.from_id = from_id.into().map(Cow::Borrowed); + + // make_stream(req, token, self, |s| { + // std::collections::VecDeque::from(s.follow_relationships) + // }) + // } + + // /// Get authenticated users' followed [streams](helix::streams::Stream) + // /// + // /// Requires token with scope [`user:read:follows`](twitch_oauth2::Scope::UserReadFollows). + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let channels: Vec = client.get_followed_streams(&token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_followed_streams( + // &'client self, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // use futures::StreamExt; + + // let user_id = match token + // .user_id() + // .ok_or_else(|| ClientRequestError::Custom("no user_id found on token".into())) + // { + // Ok(t) => t, + // Err(e) => return futures::stream::once(async { Err(e) }).boxed(), + // }; + // let req = helix::streams::GetFollowedStreamsRequest::user_id(user_id); + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Get authenticated broadcasters' [subscribers](helix::subscriptions::BroadcasterSubscription) + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let subs: Vec = client.get_broadcaster_subscriptions(&token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_broadcaster_subscriptions( + // &'client self, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream< + // Item = Result< + // helix::subscriptions::BroadcasterSubscription, + // ClientError<'client, C>, + // >, + // > + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // use futures::StreamExt; + + // let user_id = match token + // .user_id() + // .ok_or_else(|| ClientRequestError::Custom("no user_id found on token".into())) + // { + // Ok(t) => t, + // Err(e) => return futures::stream::once(async { Err(e) }).boxed(), + // }; + // let req = helix::subscriptions::GetBroadcasterSubscriptionsRequest::broadcaster_id(user_id); + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Get all moderators in a channel [Get Moderators](helix::moderation::GetModeratorsRequest) + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let moderators: Vec = client.get_moderators_in_channel_from_id("twitchdev", &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_moderators_in_channel_from_id<'b: 'client, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream< + // Item = Result>, + // > + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::moderation::GetModeratorsRequest::broadcaster_id(broadcaster_id); + + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Get all banned users in a channel [Get Banned Users](helix::moderation::GetBannedUsersRequest) + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let moderators: Vec = client.get_banned_users_in_channel_from_id("twitchdev", &token).try_collect().await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_banned_users_in_channel_from_id<'b: 'client, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream< + // Item = Result>, + // > + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::moderation::GetBannedUsersRequest::broadcaster_id(broadcaster_id); + + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Get a users, with login, follow count + // pub async fn get_total_followers_from_login<'b, T>( + // &'client self, + // login: impl types::IntoCow<'b, types::UserNameRef> + 'b, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // if let Some(user) = self.get_user_from_login(&*login.to_cow(), token).await? { + // self.get_total_followers_from_id(&user.id, token) + // .await + // .map(Some) + // } else { + // Ok(None) + // } + // } + + // /// Get a users, with id, follow count + // /// + // /// # Notes + // /// + // /// This returns zero if the user doesn't exist + // pub async fn get_total_followers_from_id<'b, T>( + // &'client self, + // to_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let resp = self + // .req_get( + // helix::users::GetUsersFollowsRequest::followers(to_id), + // token, + // ) + // .await?; + + // Ok(resp.data.total) + // } + + // /// Get games by ID. Can only be at max 100 ids. + // pub async fn get_games_by_id( + // &'client self, + // ids: impl AsRef<[&'client types::CategoryIdRef]>, + // token: &T, + // ) -> Result< + // std::collections::HashMap, + // ClientError<'client, C>, + // > + // where + // T: TwitchToken + ?Sized, + // { + // let ids = ids.as_ref(); + // if ids.len() > 100 { + // return Err(ClientRequestError::Custom("too many IDs, max 100".into())); + // } + + // let resp = self + // .req_get(helix::games::GetGamesRequest::ids(ids), token) + // .await?; + + // Ok(resp + // .data + // .into_iter() + // .map(|g: helix::games::Game| (g.id.clone(), g)) + // .collect()) + // } + + // /// Block a user + // pub async fn block_user<'b, T>( + // &'client self, + // target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_put( + // helix::users::BlockUserRequest::block_user(target_user_id), + // helix::EmptyBody, + // token, + // ) + // .await? + // .data) + // } + + // /// Unblock a user + // pub async fn unblock_user<'b, T>( + // &'client self, + // target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_delete( + // helix::users::UnblockUserRequest::unblock_user(target_user_id), + // token, + // ) + // .await? + // .data) + // } + + // /// Ban a user + // pub async fn ban_user<'b, T>( + // &'client self, + // target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // reason: impl Into<&'b str>, + // duration: impl Into>, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_post( + // helix::moderation::BanUserRequest::new(broadcaster_id, moderator_id), + // helix::moderation::BanUserBody::new(target_user_id, reason.into(), duration), + // token, + // ) + // .await? + // .data) + // } + + // /// Unban a user + // pub async fn unban_user<'b, T>( + // &'client self, + // target_user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_delete( + // helix::moderation::UnbanUserRequest::new( + // broadcaster_id, + // moderator_id, + // target_user_id, + // ), + // token, + // ) + // .await? + // .data) + // } + + // // FIXME: Example should use https://github.com/twitch-rs/twitch_api/issues/162 + // /// Get all scheduled streams in a channel. + // /// + // /// # Notes + // /// + // /// Make sure to limit the data here using [`try_take_while`](futures::stream::TryStreamExt::try_take_while), otherwise this will never end on recurring scheduled streams. + // /// + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// use futures::TryStreamExt; + // /// + // /// let schedule: Vec = client + // /// .get_channel_schedule("twitchdev", &token) + // /// .try_take_while(|s| { + // /// futures::future::ready(Ok(!s.start_time.as_str().starts_with("2021-10"))) + // /// }) + // /// .try_collect() + // /// .await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_channel_schedule<'b: 'client, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::schedule::GetChannelStreamScheduleRequest::broadcaster_id(broadcaster_id); + + // make_stream(req, token, self, |broadcasts| broadcasts.segments.into()) + // } + + // /// Get all global emotes + // pub async fn get_global_emotes( + // &'client self, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::chat::GetGlobalEmotesRequest::new(); + // Ok(self.req_get(req, token).await?.data) + // } + + // /// Get channel emotes in channel with user id + // pub async fn get_channel_emotes_from_id<'b, T>( + // &'client self, + // user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::chat::GetChannelEmotesRequest::broadcaster_id(user_id); + // Ok(self.req_get(req, token).await?.data) + // } + + // /// Get channel emotes in channel with user login + // pub async fn get_channel_emotes_from_login( + // &'client self, + // login: impl types::IntoCow<'client, types::UserNameRef> + 'client, + // token: &T, + // ) -> Result>, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // if let Some(user) = self + // .get_user_from_login(login.to_cow().as_ref(), token) + // .await? + // { + // self.get_channel_emotes_from_id(&user.id, token) + // .await + // .map(Some) + // } else { + // Ok(None) + // } + // } + + // /// Get emotes in emote set + // pub async fn get_emote_sets( + // &'client self, + // emote_sets: impl AsRef<[&types::EmoteSetIdRef]>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let emote_sets = emote_sets.as_ref(); + // let req = helix::chat::GetEmoteSetsRequest::emote_set_ids(emote_sets); + // Ok(self.req_get(req, token).await?.data) + // } + + // /// Get a broadcaster's chat settings + // pub async fn get_chat_settings<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl Into> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let mut req = helix::chat::GetChatSettingsRequest::broadcaster_id(broadcaster_id); + // if let Some(moderator_id) = moderator_id.into() { + // req = req.moderator_id(moderator_id); + // } + // Ok(self.req_get(req, token).await?.data) + // } + + // /// Send a chat announcement + // pub async fn send_chat_announcement<'b, T, E>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // message: impl Into<&'b str>, + // color: impl std::convert::TryInto, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::chat::SendChatAnnouncementRequest::new(broadcaster_id, moderator_id); + // let body = helix::chat::SendChatAnnouncementBody::new(message.into(), color)?; + // Ok(self + // .req_post(req, body, token) + // .await + // .map_err(ClientExtError::ClientError)? + // .data) + // } + + // /// Delete a specific chat message + // pub async fn delete_chat_message<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // message_id: impl types::IntoCow<'b, types::MsgIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::moderation::DeleteChatMessagesRequest::new(broadcaster_id, moderator_id) + // .message_id(message_id); + // Ok(self.req_delete(req, token).await?.data) + // } + + // /// Delete all chat messages in a broadcasters chat room + // pub async fn delete_all_chat_message<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::moderation::DeleteChatMessagesRequest::new(broadcaster_id, moderator_id); + // Ok(self.req_delete(req, token).await?.data) + // } + + // /// Start a raid + // pub async fn start_a_raid<'b, T>( + // &'client self, + // from_broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // to_broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::raids::StartARaidRequest::new(from_broadcaster_id, to_broadcaster_id); + // Ok(self.req_post(req, helix::EmptyBody, token).await?.data) + // } + + // /// Cancel a raid + // pub async fn cancel_a_raid<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::raids::CancelARaidRequest::broadcaster_id(broadcaster_id); + // Ok(self.req_delete(req, token).await?.data) + // } + + // /// Get a users chat color + // pub async fn get_user_chat_color( + // &'client self, + // user_id: impl Into<&types::UserIdRef>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_get( + // helix::chat::GetUserChatColorRequest::user_ids(&[user_id.into()][..]), + // token, + // ) + // .await? + // .first()) + // } + + // /// Get a users chat color + // pub async fn update_user_chat_color<'b, T>( + // &'client self, + // user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // color: impl Into> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::chat::UpdateUserChatColorRequest { + // user_id: user_id.to_cow(), + // color: color.into(), + // }; + + // Ok(self.req_put(req, helix::EmptyBody, token).await?.data) + // } + + // /// Get multiple users chat colors + // pub async fn get_users_chat_colors( + // &'client self, + // user_ids: impl AsRef<[&types::UserIdRef]>, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // let user_ids = user_ids.as_ref(); + // if user_ids.len() > 100 { + // return Err(ClientRequestError::Custom("too many IDs, max 100".into())); + // } + // let req = helix::chat::GetUserChatColorRequest::user_ids(user_ids); + + // Ok(self.req_get(req, token).await?.data) + // } + + // /// Add a channel moderator + // pub async fn add_channel_moderator<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::moderation::AddChannelModeratorRequest { + // broadcaster_id: broadcaster_id.to_cow(), + // moderator_id: moderator_id.to_cow(), + // }; + + // Ok(self.req_post(req, helix::EmptyBody, token).await?.data) + // } + + // /// Remove a channel moderator + // pub async fn remove_channel_moderator<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // moderator_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::moderation::RemoveChannelModeratorRequest { + // broadcaster_id: broadcaster_id.to_cow(), + // moderator_id: moderator_id.to_cow(), + // }; + + // Ok(self.req_delete(req, token).await?.data) + // } + + // /// Get channel VIPs + // pub fn get_vips_in_channel<'b: 'client, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream>> + // + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::channels::GetVipsRequest::broadcaster_id(broadcaster_id); + + // make_stream(req, token, self, std::collections::VecDeque::from) + // } + + // /// Add a channel vip + // pub async fn add_channel_vip<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::channels::AddChannelVipRequest { + // broadcaster_id: broadcaster_id.to_cow(), + // user_id: user_id.to_cow(), + // }; + + // Ok(self.req_post(req, helix::EmptyBody, token).await?.data) + // } + + // /// Remove a channel vip + // pub async fn remove_channel_vip<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // user_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // let req = helix::channels::RemoveChannelVipRequest { + // broadcaster_id: broadcaster_id.to_cow(), + // user_id: user_id.to_cow(), + // }; + + // Ok(self.req_delete(req, token).await?.data) + // } + + // /// Send a whisper + // pub async fn send_whisper<'b, T>( + // &'client self, + // from: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // to: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // message: impl Into<&'b str>, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_post( + // helix::whispers::SendWhisperRequest::new(from, to), + // helix::whispers::SendWhisperBody::new(message.into()), + // token, + // ) + // .await? + // .data) + // } + + // /// Get all custom rewards + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// + // /// let rewards: Vec = client + // /// .get_all_custom_rewards("1234", true, &token) + // /// .await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub async fn get_all_custom_rewards<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // only_managable_rewards: bool, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // self.get_custom_rewards(broadcaster_id, only_managable_rewards, &[], token) + // .await + // } + + // /// Get specific custom rewards, see [`get_all_custom_rewards`](HelixClient::get_all_custom_rewards) to get all rewards + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::helix; + // /// + // /// let rewards: Vec = client + // /// .get_custom_rewards("1234", true, &["8969ec47-55b6-4559-a8fe-3f1fc4e6fe58".into()], &token) + // /// .await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub async fn get_custom_rewards<'b, T>( + // &'client self, + // broadcaster_id: impl types::IntoCow<'b, types::UserIdRef> + 'b, + // only_managable_rewards: bool, + // // FIXME: This should be `impl AsRef<[&'b T]> + 'b` + // ids: &'b [&'b types::RewardIdRef], + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_get( + // helix::points::GetCustomRewardRequest::broadcaster_id(broadcaster_id) + // .only_manageable_rewards(only_managable_rewards) + // .ids(ids), + // token, + // ) + // .await? + // .data) + // } + + // #[cfg(feature = "eventsub")] + // /// Create an [EventSub](crate::eventsub) subscription + // pub async fn create_eventsub_subscription( + // &'client self, + // subscription: E, + // transport: crate::eventsub::Transport, + // token: &T, + // ) -> Result, ClientError<'client, C>> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_post( + // helix::eventsub::CreateEventSubSubscriptionRequest::new(), + // helix::eventsub::CreateEventSubSubscriptionBody::new(subscription, transport), + // token, + // ) + // .await? + // .data) + // } + + // #[cfg(feature = "eventsub")] + // /// Delete an [EventSub](crate::eventsub) subscription + // pub async fn delete_eventsub_subscription<'b, T>( + // &'client self, + // id: impl types::IntoCow<'b, types::EventSubIdRef> + 'b, + // token: &T, + // ) -> Result> + // where + // T: TwitchToken + ?Sized, + // { + // Ok(self + // .req_delete( + // helix::eventsub::DeleteEventSubSubscriptionRequest::id(id), + // token, + // ) + // .await? + // .data) + // } + + // #[cfg(feature = "eventsub")] + // /// Get all [EventSub](crate::eventsub) subscriptions for this [Client](twitch_oauth2::TwitchToken) + // /// + // /// # Notes + // /// + // /// The return item is a struct [`EventSubSubscriptions`](helix::eventsub::EventSubSubscriptions) which contains the subscriptions. + // /// See the example for getting only the subscriptions + // /// + // /// # Examples + // /// + // /// ```rust, no_run + // /// # #[tokio::main] + // /// # async fn main() -> Result<(), Box> { + // /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); + // /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); + // /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; + // /// use twitch_api::{helix, eventsub}; + // /// use futures::{TryStreamExt, stream}; + // /// + // /// let mut total_cost = None; + // /// + // /// let chatters: Vec = client + // /// .get_eventsub_subscriptions(None, None, None, &token) + // /// .map_ok(|r| { + // /// total_cost = Some(r.total_cost); + // /// stream::iter( + // /// r.subscriptions + // /// .into_iter() + // /// .map(Ok::<_, twitch_api::helix::ClientRequestError<_>>), + // /// ) + // /// }) + // /// .try_flatten() + // /// .try_collect() + // /// .await?; + // /// + // /// # Ok(()) } + // /// ``` + // pub fn get_eventsub_subscriptions<'b: 'client, T>( + // &'client self, + // status: impl Into>, + // event_type: impl Into>, + // // FIXME: IntoOptionCow? + // user_id: Option<&'b types::UserIdRef>, + // token: &'client T, + // ) -> std::pin::Pin< + // Box< + // dyn futures::Stream< + // Item = Result>, + // > + 'client, + // >, + // > + // where + // T: TwitchToken + Send + Sync + ?Sized, + // { + // let req = helix::eventsub::GetEventSubSubscriptionsRequest { + // status: status.into(), + // type_: event_type.into(), + // user_id: user_id.map(|c| c.as_cow()), + // after: None, + // first: None, + // }; + + // make_stream(req, token, self, |r| { + // let mut vec = std::collections::VecDeque::new(); + // vec.push_front(r); + // vec + // }) + // } } -/// Make a paginate-able request into a stream -/// -/// # Examples -/// -/// ```rust, no_run -/// # #[tokio::main] -/// # async fn main() -> Result<(), Box> { -/// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); -/// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); -/// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; -/// use twitch_api::helix; -/// use futures::TryStreamExt; -/// -/// let req = helix::moderation::GetModeratorsRequest::broadcaster_id("1234"); -/// -/// helix::make_stream(req, &token, &client, std::collections::VecDeque::from).try_collect::>().await? -/// # ; -/// # Ok(()) -/// # } -/// ``` -pub fn make_stream< - 'a, - C: crate::HttpClient<'a> + Send + Sync, - T: TwitchToken + ?Sized + Send + Sync, - // FIXME: Why does this have to be clone and debug? - Req: super::Request - + super::RequestGet - + super::Paginated - + Clone - + std::fmt::Debug - + Send - + Sync - + 'a, - // FIXME: this 'a seems suspicious - Item: Send + 'a, ->( - req: Req, - token: &'a T, - client: &'a super::HelixClient<'a, C>, - fun: impl Fn(::Response) -> std::collections::VecDeque - + Send - + Sync - + Copy - + 'static, -) -> std::pin::Pin>> + 'a + Send>> -where - // FIXME: This clone is bad. I want to be able to return the data, but not in a way that limits the response to be Default - // I also want to keep allocations low, so std::mem::take is perfect, but that makes get_next not work optimally. - ::Response: Send + Sync + std::fmt::Debug + Clone, -{ - use futures::StreamExt; - enum StateMode { - /// A request needs to be done. - Req(Option), - /// We have made a request, now working through the data - Cont( - super::Response::Response>, - std::collections::VecDeque, - ), - Next(Option::Response>>), - /// The operation failed, allowing no further processing - Failed, - } - - impl StateMode { - fn take_initial(&mut self) -> Req { - match self { - StateMode::Req(ref mut r) if r.is_some() => std::mem::take(r).expect("oops"), - _ => todo!("hmmm"), - } - } - - fn take_next(&mut self) -> super::Response::Response> { - match self { - StateMode::Next(ref mut r) if r.is_some() => std::mem::take(r).expect("oops"), - _ => todo!("hmmm"), - } - } - } - - struct State< - 'a, - C: crate::HttpClient<'a>, - T: TwitchToken + ?Sized, - Req: super::Request + super::RequestGet, - Item, - > { - mode: StateMode, - client: &'a HelixClient<'a, C>, - token: &'a T, - } - - impl< - 'a, - C: crate::HttpClient<'a>, - T: TwitchToken + ?Sized, - Req: super::Request + super::RequestGet + super::Paginated, - Item, - > State<'a, C, T, Req, Item> - { - /// Process a request, with a given deq - fn process( - mut self, - r: super::Response::Response>, - d: std::collections::VecDeque, - ) -> Self { - self.mode = StateMode::Cont(r, d); - self - } - - fn failed(mut self) -> Self { - self.mode = StateMode::Failed; - self - } - - /// get the next - fn get_next(mut self) -> Self { - match self.mode { - StateMode::Cont(r, d) => { - assert!(d.is_empty()); - self.mode = StateMode::Next(Some(r)); - self - } - _ => panic!("oops"), - } - } - } - let statemode = StateMode::Req(Some(req)); - let state = State { - mode: statemode, - client, - token, - }; - futures::stream::unfold(state, move |mut state: State<_, _, _, _>| async move { - match state.mode { - StateMode::Req(Some(_)) => { - let req = state.mode.take_initial(); - let f = state.client.req_get(req, state.token); - let resp = match f.await { - Ok(resp) => resp, - Err(e) => return Some((Err(e), state.failed())), - }; - let mut deq = fun(resp.data.clone()); - deq.pop_front().map(|d| (Ok(d), state.process(resp, deq))) - } - StateMode::Cont(_, ref mut deq) => { - if let Some(d) = deq.pop_front() { - if deq.is_empty() { - Some((Ok(d), state.get_next())) - } else { - Some((Ok(d), state)) - } - } else { - // New request returned empty. - None - } - } - StateMode::Next(Some(_)) => { - let resp = state.mode.take_next(); - let f = resp.get_next(state.client, state.token); - let resp = match f.await { - Ok(Some(resp)) => resp, - Ok(None) => return None, - Err(e) => return Some((Err(e), state.failed())), - }; - let mut deq = fun(resp.data.clone()); - deq.pop_front().map(|d| (Ok(d), state.process(resp, deq))) - } - _ => todo!("failed to process request"), - } - }) - .boxed() -} +// #[derive(Debug, thiserror::Error)] +// pub enum ClientExtError<'a, C: crate::HttpClient<'a>, E> { +// #[error(transparent)] +// ClientError(ClientError<'a, C>), +// #[error(transparent)] +// Other(#[from] E), +// } + +// /// Make a paginate-able request into a stream +// /// +// /// # Examples +// /// +// /// ```rust, no_run +// /// # #[tokio::main] +// /// # async fn main() -> Result<(), Box> { +// /// # let client: helix::HelixClient<'static, twitch_api::client::DummyHttpClient> = helix::HelixClient::default(); +// /// # let token = twitch_oauth2::AccessToken::new("validtoken".to_string()); +// /// # let token = twitch_oauth2::UserToken::from_existing(&client, token, None, None).await?; +// /// use twitch_api::helix; +// /// use futures::TryStreamExt; +// /// +// /// let req = helix::moderation::GetModeratorsRequest::broadcaster_id("1234"); +// /// +// /// helix::make_stream(req, &token, &client, std::collections::VecDeque::from).try_collect::>().await? +// /// # ; +// /// # Ok(()) +// /// # } +// /// ``` +// pub fn make_stream< +// 'a, +// C: crate::HttpClient<'a> + Send + Sync, +// T: TwitchToken + ?Sized + Send + Sync, +// // FIXME: Why does this have to be clone and debug? +// Req: super::Request +// + super::RequestGet +// + super::Paginated +// + Clone +// + std::fmt::Debug +// + Send +// + Sync +// + 'a, +// // FIXME: this 'a seems suspicious +// Item: Send + 'a, +// >( +// req: Req, +// token: &'a T, +// client: &'a super::HelixClient<'a, C>, +// fun: impl Fn(::Response) -> std::collections::VecDeque +// + Send +// + Sync +// + Copy +// + 'static, +// ) -> std::pin::Pin>> + 'a + Send>> +// where +// // FIXME: This clone is bad. I want to be able to return the data, but not in a way that limits the response to be Default +// // I also want to keep allocations low, so std::mem::take is perfect, but that makes get_next not work optimally. +// ::Response: Send + Sync + std::fmt::Debug + Clone, +// { +// use futures::StreamExt; +// enum StateMode { +// /// A request needs to be done. +// Req(Option), +// /// We have made a request, now working through the data +// Cont( +// super::Response::Response>, +// std::collections::VecDeque, +// ), +// Next(Option::Response>>), +// /// The operation failed, allowing no further processing +// Failed, +// } + +// impl StateMode { +// fn take_initial(&mut self) -> Req { +// match self { +// StateMode::Req(ref mut r) if r.is_some() => std::mem::take(r).expect("oops"), +// _ => todo!("hmmm"), +// } +// } + +// fn take_next(&mut self) -> super::Response::Response> { +// match self { +// StateMode::Next(ref mut r) if r.is_some() => std::mem::take(r).expect("oops"), +// _ => todo!("hmmm"), +// } +// } +// } + +// struct State< +// 'a, +// C: crate::HttpClient<'a>, +// T: TwitchToken + ?Sized, +// Req: super::Request + super::RequestGet, +// Item, +// > { +// mode: StateMode, +// client: &'a HelixClient<'a, C>, +// token: &'a T, +// } + +// impl< +// 'a, +// C: crate::HttpClient<'a>, +// T: TwitchToken + ?Sized, +// Req: super::Request + super::RequestGet + super::Paginated, +// Item, +// > State<'a, C, T, Req, Item> +// { +// /// Process a request, with a given deq +// fn process( +// mut self, +// r: super::Response::Response>, +// d: std::collections::VecDeque, +// ) -> Self { +// self.mode = StateMode::Cont(r, d); +// self +// } + +// fn failed(mut self) -> Self { +// self.mode = StateMode::Failed; +// self +// } + +// /// get the next +// fn get_next(mut self) -> Self { +// match self.mode { +// StateMode::Cont(r, d) => { +// assert!(d.is_empty()); +// self.mode = StateMode::Next(Some(r)); +// self +// } +// _ => panic!("oops"), +// } +// } +// } +// let statemode = StateMode::Req(Some(req)); +// let state = State { +// mode: statemode, +// client, +// token, +// }; +// futures::stream::unfold(state, move |mut state: State<_, _, _, _>| async move { +// match state.mode { +// StateMode::Req(Some(_)) => { +// let req = state.mode.take_initial(); +// let f = state.client.req_get(req, state.token); +// let resp = match f.await { +// Ok(resp) => resp, +// Err(e) => return Some((Err(e), state.failed())), +// }; +// let mut deq = fun(resp.data.clone()); +// deq.pop_front().map(|d| (Ok(d), state.process(resp, deq))) +// } +// StateMode::Cont(_, ref mut deq) => { +// if let Some(d) = deq.pop_front() { +// if deq.is_empty() { +// Some((Ok(d), state.get_next())) +// } else { +// Some((Ok(d), state)) +// } +// } else { +// // New request returned empty. +// None +// } +// } +// StateMode::Next(Some(_)) => { +// let resp = state.mode.take_next(); +// let f = resp.get_next(state.client, state.token); +// let resp = match f.await { +// Ok(Some(resp)) => resp, +// Ok(None) => return None, +// Err(e) => return Some((Err(e), state.failed())), +// }; +// let mut deq = fun(resp.data.clone()); +// deq.pop_front().map(|d| (Ok(d), state.process(resp, deq))) +// } +// _ => todo!("failed to process request"), +// } +// }) +// .boxed() +// } diff --git a/src/helix/endpoints/mod.rs b/src/helix/endpoints/mod.rs index 150bea310d..99e3b1734f 100644 --- a/src/helix/endpoints/mod.rs +++ b/src/helix/endpoints/mod.rs @@ -1,26 +1,26 @@ -pub mod bits; -pub mod channels; -#[cfg(feature = "unsupported")] -pub mod charity; -pub mod chat; -pub mod clips; -#[cfg(feature = "eventsub")] -#[cfg_attr(nightly, doc(cfg(feature = "eventsub")))] -pub mod eventsub; -pub mod games; -pub mod goals; -pub mod hypetrain; -pub mod moderation; -pub mod points; -pub mod polls; -pub mod predictions; -pub mod raids; -pub mod schedule; -pub mod search; -pub mod streams; -pub mod subscriptions; -pub mod tags; -pub mod teams; +// pub mod bits; +// pub mod channels; +// #[cfg(feature = "unsupported")] +// pub mod charity; +// pub mod chat; +// pub mod clips; +// #[cfg(feature = "eventsub")] +// #[cfg_attr(nightly, doc(cfg(feature = "eventsub")))] +// pub mod eventsub; +// pub mod games; +// pub mod goals; +// pub mod hypetrain; +// pub mod moderation; +// pub mod points; +// pub mod polls; +// pub mod predictions; +// pub mod raids; +// pub mod schedule; +// pub mod search; +// pub mod streams; +// pub mod subscriptions; +// pub mod tags; +// pub mod teams; pub mod users; -pub mod videos; -pub mod whispers; +// pub mod videos; +// pub mod whispers; diff --git a/src/helix/endpoints/users/get_users.rs b/src/helix/endpoints/users/get_users.rs index d45a441943..4b6ee1649f 100644 --- a/src/helix/endpoints/users/get_users.rs +++ b/src/helix/endpoints/users/get_users.rs @@ -105,24 +105,25 @@ impl<'a> GetUsersRequest<'a> { /// Return Values for [Get Users](super::get_users) /// /// [`get-users`](https://dev.twitch.tv/docs/api/reference#get-users) -#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone)] +#[derive(PartialEq, Eq, Deserialize, Serialize, Debug, Clone, yoke::Yokeable)] #[cfg_attr(feature = "deny_unknown_fields", serde(deny_unknown_fields))] #[non_exhaustive] -pub struct User { +pub struct User<'a> { /// User’s broadcaster type: "partner", "affiliate", or "". pub broadcaster_type: Option, /// Date when the user was created. - pub created_at: types::Timestamp, + #[serde(borrow)] + pub created_at: Cow<'a, types::TimestampRef>, /// User’s channel description. pub description: Option, /// User’s display name. - pub display_name: types::DisplayName, + pub display_name: Cow<'a, types::DisplayNameRef>, /// User’s email address. Returned if the request includes the [`user:read:email` scope](twitch_oauth2::Scope::UserReadEmail). pub email: Option, /// User’s ID. - pub id: types::UserId, + pub id: Cow<'a, types::UserId>, /// User’s login name. - pub login: types::UserName, + pub login: Cow<'a, types::UserNameRef>, /// URL of the user’s offline image. pub offline_image_url: Option, /// URL of the user’s profile image. @@ -140,7 +141,7 @@ pub struct User { } impl Request for GetUsersRequest<'_> { - type Response = Vec; + type Response = Vec>; #[cfg(feature = "twitch_oauth2")] const OPT_SCOPE: &'static [twitch_oauth2::Scope] = &[twitch_oauth2::Scope::UserReadEmail]; @@ -182,7 +183,7 @@ fn test_request() { "# .to_vec(); - let http_response = http::Response::builder().body(data).unwrap(); + let http_response = http::Response::builder().body(data.as_slice()).unwrap(); let uri = req.get_uri().unwrap(); assert_eq!( @@ -190,5 +191,5 @@ fn test_request() { "https://api.twitch.tv/helix/users?id=44322889" ); - dbg!(GetUsersRequest::parse_response(Some(req), &uri, http_response).unwrap()); + dbg!(GetUsersRequest::parse_response(Some(req), &uri, &http_response).unwrap()); } diff --git a/src/helix/endpoints/users/mod.rs b/src/helix/endpoints/users/mod.rs index 9007f03d01..095ae10c01 100644 --- a/src/helix/endpoints/users/mod.rs +++ b/src/helix/endpoints/users/mod.rs @@ -26,19 +26,19 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::borrow::Cow; -pub mod block_user; -pub mod get_user_block_list; +//pub mod block_user; +//pub mod get_user_block_list; pub mod get_users; -pub mod get_users_follows; -pub mod unblock_user; +//pub mod get_users_follows; +//pub mod unblock_user; -#[doc(inline)] -pub use block_user::{BlockUser, BlockUserRequest}; -#[doc(inline)] -pub use get_user_block_list::{GetUserBlockListRequest, UserBlock}; +//#[doc(inline)] +//pub use block_user::{BlockUser, BlockUserRequest}; +//#[doc(inline)] +//pub use get_user_block_list::{GetUserBlockListRequest, UserBlock}; #[doc(inline)] pub use get_users::{GetUsersRequest, User}; -#[doc(inline)] -pub use get_users_follows::{FollowRelationship, GetUsersFollowsRequest, UsersFollows}; -#[doc(inline)] -pub use unblock_user::{UnblockUser, UnblockUserRequest}; +//#[doc(inline)] +//pub use get_users_follows::{FollowRelationship, GetUsersFollowsRequest, UsersFollows}; +//#[doc(inline)] +//pub use unblock_user::{UnblockUser, UnblockUserRequest}; diff --git a/src/helix/mod.rs b/src/helix/mod.rs index f9e6edbe21..77b055e5b8 100644 --- a/src/helix/mod.rs +++ b/src/helix/mod.rs @@ -41,9 +41,10 @@ mod endpoints; pub mod request; pub mod response; -#[cfg(feature = "client")] -#[doc(inline)] -pub use client::{client_ext::make_stream, *}; +// #[cfg(feature = "client")] +// #[doc(inline)] +// pub use client::{client_ext::make_stream, *}; +pub use client::HelixClient; pub use endpoints::*; #[cfg(feature = "client")] #[doc(inline)] @@ -54,13 +55,13 @@ pub use request::errors::{ HelixRequestPostError, HelixRequestPutError, InvalidUri, SerializeError, }; #[doc(inline)] -pub use request::{Request, RequestDelete, RequestGet, RequestPatch, RequestPost, RequestPut}; +pub use request::{Request, RequestGet}; /* RequestDelete, RequestPatch, RequestPost, RequestPut}; */ #[doc(inline)] pub use response::Response; pub(crate) mod ser; -pub(crate) use crate::deserialize_default_from_null; -use crate::parse_json; +//pub(crate) use crate::deserialize_default_from_null; +//use crate::parse_json; #[derive(PartialEq, Deserialize, Debug)] struct InnerResponse { diff --git a/src/helix/request.rs b/src/helix/request.rs index bb1c35e3af..a5de485423 100644 --- a/src/helix/request.rs +++ b/src/helix/request.rs @@ -4,7 +4,7 @@ use std::{convert::TryInto, str::FromStr}; use crate::parse_json; -use super::{ser, HelixRequestBody, HelixRequestError, InnerResponse, Response}; +use super::{ser, /* HelixRequestBody , */ HelixRequestError, InnerResponse, Response}; use errors::*; /// A request is a Twitch endpoint, see [New Twitch API](https://dev.twitch.tv/docs/api/reference) reference #[async_trait::async_trait] @@ -18,7 +18,10 @@ pub trait Request: serde::Serialize { #[cfg(feature = "twitch_oauth2")] const OPT_SCOPE: &'static [twitch_oauth2::Scope] = &[]; /// Response type. twitch's response will deserialize to this. - type Response: serde::de::DeserializeOwned + PartialEq; + /// + /// # Notes + type Response: for<'y> yoke::Yokeable<'y>; + /// Defines layout of the url parameters. fn query(&self) -> Result { ser::to_string(self) } /// Returns full URI for the request, including query parameters. @@ -39,325 +42,326 @@ pub trait Request: serde::Serialize { } } -/// Helix endpoint POSTs information -pub trait RequestPost: Request { - /// Body parameters - type Body: HelixRequestBody; +// /// Helix endpoint POSTs information +// pub trait RequestPost: Request { +// /// Body parameters +// type Body: HelixRequestBody; - /// Create a [`http::Request`] from this [`Request`] in your client - fn create_request( - &self, - body: Self::Body, - token: &str, - client_id: &str, - ) -> Result, CreateRequestError> { - let uri = self.get_uri()?; +// /// Create a [`http::Request`] from this [`Request`] in your client +// fn create_request( +// &self, +// body: Self::Body, +// token: &str, +// client_id: &str, +// ) -> Result, CreateRequestError> { +// let uri = self.get_uri()?; +// +// let body = body.try_to_body()?; +// // eprintln!("\n\nbody is ------------ {:?} ------------", body); +// +// let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { +// CreateRequestError::Custom("Could not make token into headervalue".into()) +// })?; +// bearer.set_sensitive(true); +// http::Request::builder() +// .method(http::Method::POST) +// .uri(uri) +// .header("Client-ID", client_id) +// .header("Content-Type", "application/json") +// .header(http::header::AUTHORIZATION, bearer) +// .body(body) +// .map_err(Into::into) +// } +// +// /// Parse response. +// /// +// /// # Notes +// /// +// /// Pass in the request to enable [pagination](Response::get_next) if supported. +// fn parse_response>( +// // FIXME: Is this really needed? Its currently only used for error reporting. +// request: Option, +// uri: &http::Uri, +// response: http::Response, +// ) -> Result::Response>, HelixRequestPostError> +// where +// Self: Sized, +// { +// let response: http::Response = response.map(|b| b.into()); +// let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { +// HelixRequestPostError::Utf8Error(response.body().clone(), e, uri.clone()) +// })?; +// if let Ok(HelixRequestError { +// error, +// status, +// message, +// }) = parse_json::(text, false) +// { +// return Err(HelixRequestPostError::Error { +// error, +// status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), +// message, +// uri: uri.clone(), +// body: response.body().clone(), +// }); +// } +// ::parse_inner_response(request, uri, text, response.status()) +// } - let body = body.try_to_body()?; - // eprintln!("\n\nbody is ------------ {:?} ------------", body); +// /// Parse a response string into the response. +// fn parse_inner_response( +// request: Option, +// uri: &http::Uri, +// response: &str, +// status: http::StatusCode, +// ) -> Result::Response>, HelixRequestPostError> +// where +// Self: Sized, +// { +// let response: InnerResponse<::Response> = parse_json(response, true) +// .map_err(|e| { +// HelixRequestPostError::DeserializeError( +// response.to_string(), +// e, +// uri.clone(), +// status, +// ) +// })?; +// Ok(Response { +// data: response.data, +// pagination: response.pagination.cursor, +// request, +// total: response.total, +// other: None, +// }) +// } +// } - let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { - CreateRequestError::Custom("Could not make token into headervalue".into()) - })?; - bearer.set_sensitive(true); - http::Request::builder() - .method(http::Method::POST) - .uri(uri) - .header("Client-ID", client_id) - .header("Content-Type", "application/json") - .header(http::header::AUTHORIZATION, bearer) - .body(body) - .map_err(Into::into) - } +// /// Helix endpoint PATCHs information +// pub trait RequestPatch: Request { +// /// Body parameters +// type Body: HelixRequestBody; - /// Parse response. - /// - /// # Notes - /// - /// Pass in the request to enable [pagination](Response::get_next) if supported. - fn parse_response>( - // FIXME: Is this really needed? Its currently only used for error reporting. - request: Option, - uri: &http::Uri, - response: http::Response, - ) -> Result::Response>, HelixRequestPostError> - where - Self: Sized, - { - let response: http::Response = response.map(|b| b.into()); - let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { - HelixRequestPostError::Utf8Error(response.body().clone(), e, uri.clone()) - })?; - if let Ok(HelixRequestError { - error, - status, - message, - }) = parse_json::(text, false) - { - return Err(HelixRequestPostError::Error { - error, - status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), - message, - uri: uri.clone(), - body: response.body().clone(), - }); - } - ::parse_inner_response(request, uri, text, response.status()) - } +// /// Create a [`http::Request`] from this [`Request`] in your client +// fn create_request( +// &self, +// body: Self::Body, +// token: &str, +// client_id: &str, +// ) -> Result, CreateRequestError> { +// let uri = self.get_uri()?; - /// Parse a response string into the response. - fn parse_inner_response( - request: Option, - uri: &http::Uri, - response: &str, - status: http::StatusCode, - ) -> Result::Response>, HelixRequestPostError> - where - Self: Sized, - { - let response: InnerResponse<::Response> = parse_json(response, true) - .map_err(|e| { - HelixRequestPostError::DeserializeError( - response.to_string(), - e, - uri.clone(), - status, - ) - })?; - Ok(Response { - data: response.data, - pagination: response.pagination.cursor, - request, - total: response.total, - other: None, - }) - } -} - -/// Helix endpoint PATCHs information -pub trait RequestPatch: Request { - /// Body parameters - type Body: HelixRequestBody; - - /// Create a [`http::Request`] from this [`Request`] in your client - fn create_request( - &self, - body: Self::Body, - token: &str, - client_id: &str, - ) -> Result, CreateRequestError> { - let uri = self.get_uri()?; - - let body = body.try_to_body()?; - // eprintln!("\n\nbody is ------------ {} ------------", body); - - let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { - CreateRequestError::Custom("Could not make token into headervalue".into()) - })?; - bearer.set_sensitive(true); - http::Request::builder() - .method(http::Method::PATCH) - .uri(uri) - .header("Client-ID", client_id) - .header("Content-Type", "application/json") - .header(http::header::AUTHORIZATION, bearer) - .body(body) - .map_err(Into::into) - } - - /// Parse response. - /// - /// # Notes - /// - /// Pass in the request to enable [pagination](Response::get_next) if supported. - fn parse_response>( - // FIXME: Is this really needed? Its currently only used for error reporting. - request: Option, - uri: &http::Uri, - response: http::Response, - ) -> Result::Response>, HelixRequestPatchError> - where - Self: Sized, - { - let response: http::Response = response.map(|b| b.into()); - let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { - HelixRequestPatchError::Utf8Error(response.body().clone(), e, uri.clone()) - })?; - if let Ok(HelixRequestError { - error, - status, - message, - }) = parse_json::(text, false) - { - return Err(HelixRequestPatchError::Error { - error, - status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), - message, - uri: uri.clone(), - body: response.body().clone(), - }); - } - ::parse_inner_response(request, uri, text, response.status()) - } +// let body = body.try_to_body()?; +// // eprintln!("\n\nbody is ------------ {} ------------", body); +// +// let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { +// CreateRequestError::Custom("Could not make token into headervalue".into()) +// })?; +// bearer.set_sensitive(true); +// http::Request::builder() +// .method(http::Method::PATCH) +// .uri(uri) +// .header("Client-ID", client_id) +// .header("Content-Type", "application/json") +// .header(http::header::AUTHORIZATION, bearer) +// .body(body) +// .map_err(Into::into) +// } +// +// /// Parse response. +// /// +// /// # Notes +// /// +// /// Pass in the request to enable [pagination](Response::get_next) if supported. +// fn parse_response>( +// // FIXME: Is this really needed? Its currently only used for error reporting. +// request: Option, +// uri: &http::Uri, +// response: http::Response, +// ) -> Result::Response>, HelixRequestPatchError> +// where +// Self: Sized, +// { +// let response: http::Response = response.map(|b| b.into()); +// let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { +// HelixRequestPatchError::Utf8Error(response.body().clone(), e, uri.clone()) +// })?; +// if let Ok(HelixRequestError { +// error, +// status, +// message, +// }) = parse_json::(text, false) +// { +// return Err(HelixRequestPatchError::Error { +// error, +// status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), +// message, +// uri: uri.clone(), +// body: response.body().clone(), +// }); +// } +// ::parse_inner_response(request, uri, text, response.status()) +// } - /// Parse a response string into the response. - fn parse_inner_response( - request: Option, - uri: &http::Uri, - response: &str, - status: http::StatusCode, - ) -> Result::Response>, HelixRequestPatchError> - where - Self: Sized; -} +// /// Parse a response string into the response. +// fn parse_inner_response( +// request: Option, +// uri: &http::Uri, +// response: &str, +// status: http::StatusCode, +// ) -> Result::Response>, HelixRequestPatchError> +// where +// Self: Sized; +// } -/// Helix endpoint DELETEs information -pub trait RequestDelete: Request { - /// Create a [`http::Request`] from this [`Request`] in your client - fn create_request( - &self, - token: &str, - client_id: &str, - ) -> Result, CreateRequestError> { - let uri = self.get_uri()?; +// /// Helix endpoint DELETEs information +// pub trait RequestDelete: Request { +// /// Create a [`http::Request`] from this [`Request`] in your client +// fn create_request( +// &self, +// token: &str, +// client_id: &str, +// ) -> Result, CreateRequestError> { +// let uri = self.get_uri()?; - let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { - CreateRequestError::Custom("Could not make token into headervalue".into()) - })?; - bearer.set_sensitive(true); - http::Request::builder() - .method(http::Method::DELETE) - .uri(uri) - .header("Client-ID", client_id) - .header("Content-Type", "application/json") - .header(http::header::AUTHORIZATION, bearer) - .body(Vec::with_capacity(0).into()) - .map_err(Into::into) - } - /// Parse response. - /// - /// # Notes - /// - /// Pass in the request to enable [pagination](Response::get_next) if supported. - fn parse_response>( - // FIXME: Is this really needed? Its currently only used for error reporting. - request: Option, - uri: &http::Uri, - response: http::Response, - ) -> Result::Response>, HelixRequestDeleteError> - where - Self: Sized, - { - let response: http::Response = response.map(|b| b.into()); - let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { - HelixRequestDeleteError::Utf8Error(response.body().clone(), e, uri.clone()) - })?; - if let Ok(HelixRequestError { - error, - status, - message, - }) = parse_json::(text, false) - { - return Err(HelixRequestDeleteError::Error { - error, - status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), - message, - uri: uri.clone(), - body: response.body().clone(), - }); - } - ::parse_inner_response(request, uri, text, response.status()) - } - /// Parse a response string into the response. - fn parse_inner_response( - request: Option, - uri: &http::Uri, - response: &str, - status: http::StatusCode, - ) -> Result::Response>, HelixRequestDeleteError> - where - Self: Sized; -} +// let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { +// CreateRequestError::Custom("Could not make token into headervalue".into()) +// })?; +// bearer.set_sensitive(true); +// http::Request::builder() +// .method(http::Method::DELETE) +// .uri(uri) +// .header("Client-ID", client_id) +// .header("Content-Type", "application/json") +// .header(http::header::AUTHORIZATION, bearer) +// .body(Vec::with_capacity(0).into()) +// .map_err(Into::into) +// } +// /// Parse response. +// /// +// /// # Notes +// /// +// /// Pass in the request to enable [pagination](Response::get_next) if supported. +// fn parse_response>( +// // FIXME: Is this really needed? Its currently only used for error reporting. +// request: Option, +// uri: &http::Uri, +// response: http::Response, +// ) -> Result::Response>, HelixRequestDeleteError> +// where +// Self: Sized, +// { +// let response: http::Response = response.map(|b| b.into()); +// let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { +// HelixRequestDeleteError::Utf8Error(response.body().clone(), e, uri.clone()) +// })?; +// if let Ok(HelixRequestError { +// error, +// status, +// message, +// }) = parse_json::(text, false) +// { +// return Err(HelixRequestDeleteError::Error { +// error, +// status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), +// message, +// uri: uri.clone(), +// body: response.body().clone(), +// }); +// } +// ::parse_inner_response(request, uri, text, response.status()) +// } +// /// Parse a response string into the response. +// fn parse_inner_response( +// request: Option, +// uri: &http::Uri, +// response: &str, +// status: http::StatusCode, +// ) -> Result::Response>, HelixRequestDeleteError> +// where +// Self: Sized; +// } -/// Helix endpoint PUTs information -pub trait RequestPut: Request { - /// Body parameters - type Body: HelixRequestBody; +// /// Helix endpoint PUTs information +// pub trait RequestPut: Request { +// /// Body parameters +// type Body: HelixRequestBody; - /// Create a [`http::Request`] from this [`Request`] in your client - fn create_request( - &self, - body: Self::Body, - token: &str, - client_id: &str, - ) -> Result, CreateRequestError> { - let uri = self.get_uri()?; +// /// Create a [`http::Request`] from this [`Request`] in your client +// fn create_request( +// &self, +// body: Self::Body, +// token: &str, +// client_id: &str, +// ) -> Result, CreateRequestError> { +// let uri = self.get_uri()?; - let body = body.try_to_body()?; - // eprintln!("\n\nbody is ------------ {} ------------", body); +// let body = body.try_to_body()?; +// // eprintln!("\n\nbody is ------------ {} ------------", body); - let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { - CreateRequestError::Custom("Could not make token into headervalue".into()) - })?; - bearer.set_sensitive(true); - http::Request::builder() - .method(http::Method::PUT) - .uri(uri) - .header("Client-ID", client_id) - .header("Content-Type", "application/json") - .header(http::header::AUTHORIZATION, bearer) - .body(body) - .map_err(Into::into) - } +// let mut bearer = http::HeaderValue::from_str(&format!("Bearer {token}")).map_err(|_| { +// CreateRequestError::Custom("Could not make token into headervalue".into()) +// })?; +// bearer.set_sensitive(true); +// http::Request::builder() +// .method(http::Method::PUT) +// .uri(uri) +// .header("Client-ID", client_id) +// .header("Content-Type", "application/json") +// .header(http::header::AUTHORIZATION, bearer) +// .body(body) +// .map_err(Into::into) +// } - /// Parse response. - /// - /// # Notes - /// - /// Pass in the request to enable [pagination](Response::get_next) if supported. - fn parse_response>( - // FIXME: Is this really needed? Its currently only used for error reporting. - request: Option, - uri: &http::Uri, - response: http::Response, - ) -> Result::Response>, HelixRequestPutError> - where - Self: Sized, - { - let response: http::Response = response.map(|b| b.into()); - let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { - HelixRequestPutError::Utf8Error(response.body().clone(), e, uri.clone()) - })?; - if let Ok(HelixRequestError { - error, - status, - message, - }) = parse_json::(text, false) - { - return Err(HelixRequestPutError::Error { - error, - status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), - message, - uri: uri.clone(), - body: response.body().clone(), - }); - } - ::parse_inner_response(request, uri, text, response.status()) - } +// /// Parse response. +// /// +// /// # Notes +// /// +// /// Pass in the request to enable [pagination](Response::get_next) if supported. +// fn parse_response>( +// // FIXME: Is this really needed? Its currently only used for error reporting. +// request: Option, +// uri: &http::Uri, +// response: http::Response, +// ) -> Result::Response>, HelixRequestPutError> +// where +// Self: Sized, +// { +// let response: http::Response = response.map(|b| b.into()); +// let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { +// HelixRequestPutError::Utf8Error(response.body().clone(), e, uri.clone()) +// })?; +// if let Ok(HelixRequestError { +// error, +// status, +// message, +// }) = parse_json::(text, false) +// { +// return Err(HelixRequestPutError::Error { +// error, +// status: status.try_into().unwrap_or(http::StatusCode::BAD_REQUEST), +// message, +// uri: uri.clone(), +// body: response.body().clone(), +// }); +// } +// ::parse_inner_response(request, uri, text, response.status()) +// } - /// Parse a response string into the response. - fn parse_inner_response( - request: Option, - uri: &http::Uri, - response: &str, - status: http::StatusCode, - ) -> Result::Response>, HelixRequestPutError> - where - Self: Sized; -} +// /// Parse a response string into the response. +// fn parse_inner_response( +// request: Option, +// uri: &http::Uri, +// response: &str, +// status: http::StatusCode, +// ) -> Result::Response>, HelixRequestPutError> +// where +// Self: Sized; +// } /// Helix endpoint GETs information -pub trait RequestGet: Request { +pub trait RequestGet: Request +where for<'y> Self::Response: yoke::Yokeable<'y> { /// Create a [`http::Request`] from this [`Request`] in your client fn create_request( &self, @@ -385,17 +389,18 @@ pub trait RequestGet: Request { /// # Notes /// /// Pass in the request to enable [pagination](Response::get_next) if supported. - fn parse_response>( + fn parse_response<'de>( request: Option, uri: &http::Uri, - response: http::Response, - ) -> Result::Response>, HelixRequestGetError> + response: &http::Response<&'de [u8]>, + ) -> Result>::Output>, HelixRequestGetError> where Self: Sized, + yoke::trait_hack::YokeTraitHack<>::Output>: + serde::Deserialize<'de>, { - let response: http::Response = response.map(|b| b.into()); - let text = std::str::from_utf8(response.body().as_ref()).map_err(|e| { - HelixRequestGetError::Utf8Error(response.body().clone(), e, uri.clone()) + let text = std::str::from_utf8(response.body()).map_err(|e| { + HelixRequestGetError::Utf8Error(response.body().to_vec(), e, uri.clone()) })?; //eprintln!("\n\nmessage is ------------ {} ------------", text); if let Ok(HelixRequestError { @@ -415,20 +420,24 @@ pub trait RequestGet: Request { } /// Parse a response string into the response. - fn parse_inner_response( + fn parse_inner_response<'de>( request: Option, uri: &http::Uri, - response: &str, + response: &'de str, status: http::StatusCode, - ) -> Result::Response>, HelixRequestGetError> + ) -> Result>::Output>, HelixRequestGetError> where Self: Sized, + yoke::trait_hack::YokeTraitHack<>::Output>: + serde::Deserialize<'de>, { - let response: InnerResponse<_> = parse_json(response, true).map_err(|e| { + let response: InnerResponse< + yoke::trait_hack::YokeTraitHack<>::Output>, + > = parse_json(response, true).map_err(|e| { HelixRequestGetError::DeserializeError(response.to_string(), e, uri.clone(), status) })?; Ok(Response { - data: response.data, + data: response.data.0, pagination: response.pagination.cursor, request, total: response.total, diff --git a/src/helix/request/errors.rs b/src/helix/request/errors.rs index b6a0b54029..6fe3e5aa10 100644 --- a/src/helix/request/errors.rs +++ b/src/helix/request/errors.rs @@ -78,7 +78,7 @@ pub enum HelixRequestGetError { uri: http::Uri, }, /// could not parse response as utf8 when calling `GET {2}` - Utf8Error(hyper::body::Bytes, #[source] std::str::Utf8Error, http::Uri), + Utf8Error(Vec, #[source] std::str::Utf8Error, http::Uri), /// deserialization failed when processing request response calling `GET {2}` with response: {3} - {0:?} DeserializeError( String, diff --git a/src/helix/response.rs b/src/helix/response.rs index efb4fa5397..f400f28976 100644 --- a/src/helix/response.rs +++ b/src/helix/response.rs @@ -2,12 +2,8 @@ use super::{Cursor, Request}; /// Response retrieved from endpoint. Data is the type in [`Request::Response`] -#[derive(PartialEq, Eq, Debug)] #[non_exhaustive] -pub struct Response -where - R: Request, - D: serde::de::DeserializeOwned + PartialEq, { +pub struct Response { /// Twitch's response field for `data`. pub data: D, /// A cursor value, to be used in a subsequent request to specify the starting point of the next set of results. @@ -22,10 +18,45 @@ where pub other: Option>, } -impl Response +impl Eq for Response where - R: Request, - D: serde::de::DeserializeOwned + PartialEq, + R: Request + Eq, + D: PartialEq + Eq, +{ +} + +impl PartialEq for Response +where + R: Request + PartialEq, + D: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.data == other.data + && self.pagination == other.pagination + && self.request == other.request + && self.total == other.total + && self.other == other.other + } +} + +impl std::fmt::Debug for Response +where + R: Request + std::fmt::Debug, + D: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Response") + .field("data", &self.data) + .field("pagination", &self.pagination) + .field("request", &self.request) + .field("total", &self.total) + .field("other", &self.other) + .finish() + } +} + +impl Response +where R: Request { /// Get a field from the response that is not part of `data`. pub fn get_other(&self, key: &Q) -> Result, serde_json::Error> @@ -53,61 +84,67 @@ where } } -impl Response +impl Response> where R: Request, - D: IntoIterator + PartialEq + serde::de::DeserializeOwned, + D: for<'y> yoke::Yokeable<'y, Output = IT>, + T: for<'y> yoke::Yokeable<'y>, + IT: for<'y> yoke::Yokeable<'y> + IntoIterator, { /// Get first result of this response. - pub fn first(self) -> Option { self.data.into_iter().next() } + pub fn first(self) -> Option> { + self.data + .try_map_project(|yk, _| yk.into_iter().next().map(|t| t.transform_owned()).ok_or(())) + .ok() + } } -// impl CustomResponse<'_, R, D> +// // impl CustomResponse<'_, R, D> +// // where +// // R: Request, +// // D: IntoIterator, +// // { +// // /// Get first result of this response. +// // pub fn first(self) -> Option { self.data().into_iter().next() } +// // } + +// #[cfg(feature = "client")] +// impl Response // where -// R: Request, -// D: IntoIterator, +// R: Request + Clone + super::Paginated + super::RequestGet + std::fmt::Debug, +// D: serde::de::DeserializeOwned + std::fmt::Debug + PartialEq, // { -// /// Get first result of this response. -// pub fn first(self) -> Option { self.data().into_iter().next() } +// /// Get the next page in the responses. +// pub async fn get_next<'a, C: crate::HttpClient<'a>>( +// self, +// client: &'a super::HelixClient<'a, C>, +// token: &(impl super::TwitchToken + ?Sized), +// ) -> Result< +// Option>, +// super::ClientRequestError<>::Error>, +// > { +// if let Some(mut req) = self.request.clone() { +// if self.pagination.is_some() { +// req.set_pagination(self.pagination); +// let res = client.req_get(req, token).await.map(Some); +// if let Ok(Some(r)) = res { +// // FIXME: Workaround for https://github.com/twitchdev/issues/issues/18 +// if r.data == self.data { +// Ok(None) +// } else { +// Ok(Some(r)) +// } +// } else { +// res +// } +// } else { +// Ok(None) +// } +// } else { +// // TODO: Make into proper error +// Err(super::ClientRequestError::Custom( +// "no source request attached".into(), +// )) +// } +// } // } - -#[cfg(feature = "client")] -impl Response -where - R: Request + Clone + super::Paginated + super::RequestGet + std::fmt::Debug, - D: serde::de::DeserializeOwned + std::fmt::Debug + PartialEq, -{ - /// Get the next page in the responses. - pub async fn get_next<'a, C: crate::HttpClient<'a>>( - self, - client: &'a super::HelixClient<'a, C>, - token: &(impl super::TwitchToken + ?Sized), - ) -> Result< - Option>, - super::ClientRequestError<>::Error>, - > { - if let Some(mut req) = self.request.clone() { - if self.pagination.is_some() { - req.set_pagination(self.pagination); - let res = client.req_get(req, token).await.map(Some); - if let Ok(Some(r)) = res { - // FIXME: Workaround for https://github.com/twitchdev/issues/issues/18 - if r.data == self.data { - Ok(None) - } else { - Ok(Some(r)) - } - } else { - res - } - } else { - Ok(None) - } - } else { - // TODO: Make into proper error - Err(super::ClientRequestError::Custom( - "no source request attached".into(), - )) - } - } -}