From 82120fff8e836557a6b05973394d5985707e44d0 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 24 Sep 2021 23:18:01 -0400 Subject: [PATCH 01/17] First commit / hello world --- .gitignore | 7 + Cargo.lock | 752 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 32 +++ README.md | 5 + src/main.rs | 6 + 5 files changed, 802 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..40897488f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.swo +*.swp +*~ +.env +.idea/ +__pycache__/ +target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..5345806bff --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,752 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-dup" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c" +dependencies = [ + "futures-io", + "simple-mutex", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "cc" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fastrand" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-io" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite 0.2.7", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "instant" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log", + "mio", + "miow 0.3.7", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "prost" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" +dependencies = [ + "bytes 1.1.0", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi 0.3.9", +] + +[[package]] +name = "rustls" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "simple-mutex" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "slab" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite 0.1.12", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" +dependencies = [ + "futures-core", + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-io", + "futures-sink", + "log", + "pin-project-lite 0.1.12", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "twitch-tts-desktop-client" +version = "0.0.0" +dependencies = [ + "anyhow", + "log", + "prost", + "rustls", + "serde", + "serde_derive", + "tokio", + "toml", + "twitchchat", +] + +[[package]] +name = "twitchchat" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "123f2cab8e6afe5183f40523e4f628b230ddcdbbabf0c094fb946eac73cb0da3" +dependencies = [ + "async-channel", + "async-dup", + "fastrand", + "futures-lite", + "futures-timer", + "log", + "pin-project-lite 0.1.12", + "tokio", + "tokio-rustls", + "tokio-util", + "webpki-roots", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..bfe1b5de66 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "twitch-tts-desktop-client" +version = "0.0.0" +edition = "2018" +authors = [ + "Brandon Thomas ", + "Brandon Thomas " +] + +[[bin]] +name = "twitch-tts-desktop-client" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.38" +log = "0.4.14" +prost = "0.7.0" +rustls = "0.18" +serde = "1.0.123" +serde_derive = "1.0.123" +tokio = { version = "0.2", features = ["full"] } +toml = "0.5.8" + +# Twitch Helix etc. API, but no PubSub just yet +#twitch_api2 = "0.4.1" +#twitch_oauth2 = "0.4.1" + +# Fairly mature and robust Twitch chat library +twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] } + +#[build-dependencies] + diff --git a/README.md b/README.md new file mode 100644 index 0000000000..743134816e --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +twitch-tts-desktop-client +========================= +Name TBD + + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000000..b4b7219f29 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,6 @@ + + +fn main() { + println!("Hello World"); +} + From 1ccf1e90da00754392bd4ba040683b12d004f177 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 25 Sep 2021 16:50:34 -0400 Subject: [PATCH 02/17] Adding a Twitch client wrapper, secrets store --- Cargo.lock | 138 ++++++++++++++++++++--------- Cargo.toml | 23 +++-- src/main.rs | 2 + src/twitch/mod.rs | 2 + src/twitch/secrets.rs | 41 +++++++++ src/twitch/twitch_client.rs | 171 ++++++++++++++++++++++++++++++++++++ src/util/anyhow_result.rs | 2 + src/util/mod.rs | 1 + 8 files changed, 331 insertions(+), 49 deletions(-) create mode 100644 src/twitch/mod.rs create mode 100644 src/twitch/secrets.rs create mode 100644 src/twitch/twitch_client.rs create mode 100644 src/util/anyhow_result.rs create mode 100644 src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5345806bff..e947a70355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.44" @@ -29,6 +38,17 @@ dependencies = [ "simple-mutex", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "base64" version = "0.12.3" @@ -53,12 +73,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - [[package]] name = "cache-padded" version = "1.1.1" @@ -93,10 +107,34 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.6.1" +name = "dotenv" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "easyenv" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8044ea5153408cb704ee2a9dafbd33aa5d14c8a34c8405910d90e8c8ef45944" +dependencies = [ + "dotenv", + "env_logger", + "log", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] [[package]] name = "event-listener" @@ -183,6 +221,15 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "instant" version = "0.1.11" @@ -201,15 +248,6 @@ dependencies = [ "libc", ] -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "js-sys" version = "0.3.55" @@ -374,27 +412,10 @@ dependencies = [ ] [[package]] -name = "prost" -version = "0.7.0" +name = "quick-error" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" -dependencies = [ - "bytes 1.1.0", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" @@ -405,6 +426,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "ring" version = "0.16.20" @@ -501,13 +539,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "tokio" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" dependencies = [ - "bytes 0.5.6", + "bytes", "fnv", "futures-core", "iovec", @@ -554,7 +601,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" dependencies = [ - "bytes 0.5.6", + "bytes", "futures-core", "futures-io", "futures-sink", @@ -577,8 +624,8 @@ name = "twitch-tts-desktop-client" version = "0.0.0" dependencies = [ "anyhow", + "easyenv", "log", - "prost", "rustls", "serde", "serde_derive", @@ -735,6 +782,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index bfe1b5de66..d7cc975c8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,21 +12,28 @@ name = "twitch-tts-desktop-client" path = "src/main.rs" [dependencies] -anyhow = "1.0.38" -log = "0.4.14" -prost = "0.7.0" -rustls = "0.18" -serde = "1.0.123" -serde_derive = "1.0.123" +# Async runtime tokio = { version = "0.2", features = ["full"] } + +# Crypto +rustls = "0.18" + +# Serialization +serde = "1.0.130" +serde_derive = "1.0.130" toml = "0.5.8" +# Twitch +# (This is a fairly mature and robust Twitch chat library) +twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] } # Twitch Helix etc. API, but no PubSub just yet #twitch_api2 = "0.4.1" #twitch_oauth2 = "0.4.1" -# Fairly mature and robust Twitch chat library -twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] } +# Utils +anyhow = "1.0.44" +easyenv = "0.0.6" +log = "0.4.14" #[build-dependencies] diff --git a/src/main.rs b/src/main.rs index b4b7219f29..9a61818a70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ +pub mod twitch; +pub mod util; fn main() { println!("Hello World"); diff --git a/src/twitch/mod.rs b/src/twitch/mod.rs new file mode 100644 index 0000000000..f79395be0d --- /dev/null +++ b/src/twitch/mod.rs @@ -0,0 +1,2 @@ +pub mod secrets; +pub mod twitch_client; \ No newline at end of file diff --git a/src/twitch/secrets.rs b/src/twitch/secrets.rs new file mode 100644 index 0000000000..237c5d18f1 --- /dev/null +++ b/src/twitch/secrets.rs @@ -0,0 +1,41 @@ +use crate::util::anyhow_result::AnyhowResult; + +// TODO: Not sure I need all of these. The less I need to keep, the better. +// Oauth delegation in particular seems not necessary. +#[derive(Clone)] +pub struct TwitchSecrets { + pub username: String, + pub stream_key: String, + pub app_client_id: String, + pub app_client_secret: String, + pub oauth_access_token: String, +} + +impl TwitchSecrets { + pub fn new( + username: &str, + stream_key: &str, + app_client_id: &str, + app_client_secret: &str, + oauth_access_token: &str + ) -> Self { + Self { + username: username.to_string(), + stream_key: stream_key.to_string(), + app_client_id: app_client_id.to_string(), + app_client_secret: app_client_secret.to_string(), + oauth_access_token: oauth_access_token.to_string(), + } + } + + pub fn from_env() -> AnyhowResult { + Ok(Self::new( + &easyenv::get_env_string_required("TWITCH_USERNAME")?, + &easyenv::get_env_string_required("TWITCH_STREAM_KEY")?, + &easyenv::get_env_string_required("TWITCH_APP_CLIENT_ID")?, + &easyenv::get_env_string_required("TWITCH_APP_CLIENT_SECRET")?, + &easyenv::get_env_string_required("TWITCH_OAUTH_ACCESS_TOKEN")?, + )) + } +} + diff --git a/src/twitch/twitch_client.rs b/src/twitch/twitch_client.rs new file mode 100644 index 0000000000..566352fbac --- /dev/null +++ b/src/twitch/twitch_client.rs @@ -0,0 +1,171 @@ +use crate::twitch::secrets::TwitchSecrets; +use crate::util::anyhow_result::AnyhowResult; +use log::debug; +use log::info; +use log::warn; +use std::time::Duration; +use twitchchat::messages::Commands::*; +use twitchchat::messages::Join; +use twitchchat::messages::Privmsg; +use twitchchat::{AsyncRunner, Status, UserConfig}; + +pub struct TwitchClient { + secrets: TwitchSecrets, + channels_to_join: Vec, + + /// This manages our connection. + /// It's meant to be absent when not connected. + /// TODO: Probably need to reset this when we disconnect. + runner: Option, +} + +impl TwitchClient { + + pub fn new(secrets: &TwitchSecrets) -> Self + { + Self { + secrets: secrets.clone(), + //channels_to_join: secrets.watch_channels.clone(), // TODO: Configure these elsewhere. + channels_to_join: vec![], + runner: None, + } + } + + pub async fn connect(&mut self) -> AnyhowResult<()> { + let user_config = get_user_config(&self.secrets)?; + let connector = twitchchat::connector::tokio::Connector::twitch()?; + + info!("Connecting to Twitch..."); + let mut runner = AsyncRunner::connect(connector, &user_config).await?; + + info!("Connected. Our Twitch identity: {:#?}", runner.identity); + + for channel in self.channels_to_join.iter() { + info!("Attempting to join channel '{}'", channel); + let _ = runner.join(&channel).await?; + + info!("Joined channel '{}'!", channel); + } + + self.runner = Some(runner); + + Ok(()) + } + + pub async fn main_loop(&mut self) { + loop { + let mut runner = match &mut self.runner { + Some(runner) => runner, + None => { + info!("Connecting to Twitch..."); + match self.connect().await { + Ok(_) => {}, + Err(e) => { + warn!("Failure to connect to Twitch: {:?}", e); + std::thread::sleep(Duration::from_secs(5)); + }, + } + continue; + }, + }; + + let next_message = match runner.next_message().await { + Ok(msg) => msg, + Err(e) => { + warn!("Error getting message from Twitch: {:?}", e); + std::thread::sleep(Duration::from_secs(5)); + continue; + }, + }; + + match next_message { + Status::Message(message) => { + // NB: Handles across all channels (and notifications from Twitch) + match self.handle_message(message).await { + Ok(_) => {}, + Err(e) => { + warn!("Error handling most recent message: {:?}", e); + } + } + }, + Status::Quit => { + // we signaled a quit + warn!("Status: Quit."); + }, + Status::Eof => { + // the connection closed normally + warn!("Status: EOF."); + }, + } + } + } + + async fn handle_message<'a>( + &mut self, + message: twitchchat::messages::Commands<'a> + ) -> AnyhowResult<()> { + match message { + // These happen when you initially connect + IrcReady(_) => {} + Ready(_) => {} + Cap(_) => {} + + // This is the one users send to channels + Privmsg(msg) => self.handle_privmsg(&msg).await?, + + // This is when a user joins the channel + // NB: These messages are delayed about 15-30 seconds. + // Doesn't appear to work...? + Join(msg) => info!("Join: {:?}", msg), + + // This should happen on subscription, etc. + // Doesn't appear to work...? + UserNotice(msg) => info!("UserNotice: {:?}", msg), + + // This should happen when a user joins, etc. + // Doesn't appear to work...? + // https://dev.twitch.tv/docs/irc/tags#userstate-twitch-tags + UserState(msg) => info!("UserState: {:?}", msg), + + // and a bunch of other messages you may be interested in + ClearChat(msg) => debug!("ClearChat: {:?}", msg), + ClearMsg(msg) => debug!("ClearMsg: {:?}", msg), + GlobalUserState(msg) => debug!("GlobalUserState: {:?}", msg), + HostTarget(msg) => debug!("HostTarget: {:?}", msg), + Notice(msg) => debug!("Notice: {:?}", msg), + Part(msg) => debug!("Part: {:?}", msg), + Ping(_) => {} + Pong(_) => {} + Reconnect(_) => {} + RoomState(msg) => debug!("RoomState: {:?}", msg), + Whisper(msg) => debug!("Whisper: {:?}", msg), + + // This one is special, if twitch adds any new message + // types, this will catch it until future releases of + // this crate add them. + Raw(msg) => debug!("Raw: {:?}", msg), + + _ => {} + } + + Ok(()) + } + + /// Handle user messages. + /// The "privmsg" type is for normal in-channel messages. + async fn handle_privmsg<'a>(&mut self, message: &Privmsg<'a>) -> AnyhowResult<()> { + info!("[{}] {}: {}", message.channel(), message.name(), message.data()); + debug!("Full Message: {:?}", message); + + Ok(()) + } +} + +pub fn get_user_config(twitch_secrets: &TwitchSecrets) -> AnyhowResult { + let config = UserConfig::builder() + .name(twitch_secrets.username.to_string()) + .token(twitch_secrets.oauth_access_token.to_string()) + .enable_all_capabilities() + .build()?; + Ok(config) +} diff --git a/src/util/anyhow_result.rs b/src/util/anyhow_result.rs new file mode 100644 index 0000000000..cf8fba51aa --- /dev/null +++ b/src/util/anyhow_result.rs @@ -0,0 +1,2 @@ + +pub type AnyhowResult = anyhow::Result; \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000000..0bb75d1b62 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1 @@ +pub mod anyhow_result; \ No newline at end of file From 5e270d5c695bcb5880839c8524a389de71e953a4 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 02:23:37 -0400 Subject: [PATCH 03/17] client successfully calls twitch with helix+secrets, not oauth flow --- .gitignore | 1 + Cargo.lock | 1035 +++++++++++++++++++++++++++-------- Cargo.toml | 11 +- src/main.rs | 48 +- src/twitch/twitch_client.rs | 7 +- 5 files changed, 860 insertions(+), 242 deletions(-) diff --git a/.gitignore b/.gitignore index 40897488f5..bfcf7218a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ .env .idea/ __pycache__/ +secrets.toml target/ diff --git a/Cargo.lock b/Cargo.lock index e947a70355..89be8f6a85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliri_braid" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4961918d91b2182cf3adc6e3cd8d6f44452e0f42db367691447af31afdd1ad7d" +dependencies = [ + "aliri_braid_impl", +] + +[[package]] +name = "aliri_braid_impl" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e21d6298c933984056b381a70c7248a97370b76dfdbc95dc3da0fcbcd994342" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "anyhow" version = "1.0.44" @@ -18,24 +38,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] -name = "async-channel" -version = "1.6.1" +name = "async-trait" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-dup" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c" -dependencies = [ - "futures-io", - "simple-mutex", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -46,15 +56,27 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "base64" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" @@ -69,15 +91,9 @@ checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" [[package]] name = "bytes" -version = "0.5.6" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - -[[package]] -name = "cache-padded" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" @@ -85,12 +101,6 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -98,12 +108,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "concurrent-queue" -version = "1.2.2" +name = "core-foundation" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" dependencies = [ - "cache-padded", + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[package]] +name = "displaydoc" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -123,6 +151,15 @@ dependencies = [ "log", ] +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -136,21 +173,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "event-listener" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" - -[[package]] -name = "fastrand" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" -dependencies = [ - "instant", -] - [[package]] name = "fnv" version = "1.0.7" @@ -158,20 +180,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "bitflags", - "fuchsia-zircon-sys", + "foreign-types-shared", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "foreign-types-shared" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", + "futures-sink", +] [[package]] name = "futures-core" @@ -179,6 +235,17 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +[[package]] +name = "futures-executor" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.17" @@ -186,18 +253,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" [[package]] -name = "futures-lite" -version = "1.12.0" +name = "futures-macro" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite 0.2.7", - "waker-fn", + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -207,10 +272,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" [[package]] -name = "futures-timer" -version = "3.0.2" +name = "futures-task" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "hermit-abi" @@ -221,6 +343,40 @@ dependencies = [ "libc", ] +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + [[package]] name = "humantime" version = "1.3.0" @@ -231,23 +387,75 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.11" +name = "hyper" +version = "0.14.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" dependencies = [ - "cfg-if 1.0.0", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "hyper-tls" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "libc", + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "js-sys" version = "0.3.55" @@ -257,16 +465,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -285,67 +483,38 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "mio" -version = "0.6.23" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", "libc", "log", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log", - "mio", - "miow 0.3.7", - "winapi 0.3.9", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "miow", + "ntapi", + "winapi", ] [[package]] @@ -354,18 +523,34 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] -name = "net2" -version = "0.2.37" +name = "native-tls" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ - "cfg-if 0.1.10", + "lazy_static", "libc", - "winapi 0.3.9", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", ] [[package]] @@ -385,16 +570,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] -name = "parking" -version = "2.0.0" +name = "openssl" +version = "0.10.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] [[package]] -name = "pin-project-lite" -version = "0.1.12" +name = "openssl-probe" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project-lite" @@ -402,6 +614,36 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + [[package]] name = "proc-macro2" version = "1.0.29" @@ -426,6 +668,55 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.5.4" @@ -443,6 +734,49 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +dependencies = [ + "base64 0.13.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "ring" version = "0.16.20" @@ -455,7 +789,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -464,13 +798,29 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" dependencies = [ - "base64", + "base64 0.12.3", "log", "ring", "sct", "webpki", ] +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "sct" version = "0.6.1" @@ -481,11 +831,37 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -499,21 +875,46 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.0" +name = "serde_json" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ - "libc", + "itoa", + "ryu", + "serde", ] [[package]] -name = "simple-mutex" -version = "1.1.5" +name = "serde_path_to_error" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6" +checksum = "d0421d4f173fab82d72d6babf36d57fae38b994ca5c2d78e704260ba6d12118b" dependencies = [ - "event-listener", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", ] [[package]] @@ -522,6 +923,16 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -539,6 +950,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -549,34 +974,19 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "0.2.25" +name = "thiserror" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" dependencies = [ - "bytes", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "libc", - "memchr", - "mio", - "mio-named-pipes", - "mio-uds", - "num_cpus", - "pin-project-lite 0.1.12", - "signal-hook-registry", - "slab", - "tokio-macros", - "winapi 0.3.9", + "thiserror-impl", ] [[package]] -name = "tokio-macros" -version = "0.2.6" +name = "thiserror-impl" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" dependencies = [ "proc-macro2", "quote", @@ -584,29 +994,69 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.14.1" +name = "tinyvec" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" +checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" dependencies = [ - "futures-core", - "rustls", + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", "tokio", - "webpki", ] [[package]] name = "tokio-util" -version = "0.3.1" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", "log", - "pin-project-lite 0.1.12", + "pin-project-lite", "tokio", ] @@ -619,6 +1069,38 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "twitch-tts-desktop-client" version = "0.0.0" @@ -626,31 +1108,86 @@ dependencies = [ "anyhow", "easyenv", "log", + "reqwest", "rustls", "serde", "serde_derive", "tokio", "toml", - "twitchchat", + "twitch_api2", + "twitch_oauth2", ] [[package]] -name = "twitchchat" -version = "0.14.8" +name = "twitch_api2" +version = "0.6.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123f2cab8e6afe5183f40523e4f628b230ddcdbbabf0c094fb946eac73cb0da3" +checksum = "003e4481fa1bc7cf6587dd212a4ce5f3b69dee893c9f7fdf8a0ed7149f470f1b" dependencies = [ - "async-channel", - "async-dup", - "fastrand", - "futures-lite", - "futures-timer", - "log", - "pin-project-lite 0.1.12", - "tokio", - "tokio-rustls", - "tokio-util", - "webpki-roots", + "aliri_braid", + "async-trait", + "displaydoc", + "futures", + "http", + "once_cell", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_repr", + "thiserror", + "twitch_oauth2", + "typed-builder", + "url", + "version_check", +] + +[[package]] +name = "twitch_oauth2" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3ac686c0fd364beafa745e4471c602d945ff5e1962e36e27c38ad92660e929" +dependencies = [ + "aliri_braid", + "async-trait", + "base64 0.13.0", + "displaydoc", + "http", + "once_cell", + "rand", + "reqwest", + "serde", + "serde_json", + "thiserror", + "url", + "version_check", +] + +[[package]] +name = "typed-builder" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a46ee5bd706ff79131be9c94e7edcb82b703c487766a114434e5790361cf08c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", ] [[package]] @@ -666,10 +1203,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "waker-fn" -version = "1.1.0" +name = "url" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" @@ -677,7 +1248,9 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -696,6 +1269,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.78" @@ -745,21 +1330,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" -dependencies = [ - "webpki", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -770,12 +1340,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -788,7 +1352,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -798,11 +1362,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "winreg" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index d7cc975c8f..cff34fa515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" [dependencies] # Async runtime -tokio = { version = "0.2", features = ["full"] } +#tokio = { version = "0.2", features = ["full"] } # Crypto rustls = "0.18" @@ -25,10 +25,17 @@ toml = "0.5.8" # Twitch # (This is a fairly mature and robust Twitch chat library) -twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] } +#twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] } # Twitch Helix etc. API, but no PubSub just yet #twitch_api2 = "0.4.1" #twitch_oauth2 = "0.4.1" +#twitch_api2 = "0.5.0" +#twitch_oauth2 = "0.5.0" +twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client"] } +twitch_oauth2 = "0.6.0-rc.2" +reqwest = { version = "0.11.4" } +tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } + # Utils anyhow = "1.0.44" diff --git a/src/main.rs b/src/main.rs index 9a61818a70..519732d546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,54 @@ +extern crate twitch_api2; +extern crate reqwest; + +use crate::util::anyhow_result::AnyhowResult; +use twitch_api2::TwitchClient; +use twitch_api2::helix::channels::GetChannelInformationRequest; +use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; pub mod twitch; pub mod util; -fn main() { +fn main() -> AnyhowResult<()> { println!("Hello World"); + + 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; + //} + } + + Ok(()) } +#[tokio::main] +async fn run() -> AnyhowResult<()> { + let client_id = "".to_string(); + let client_secret = "".to_string(); + + let client_id = ClientId::new(client_id); + let client_secret = ClientSecret::new(client_secret); + + let client: TwitchClient = TwitchClient::default(); + let token = AppAccessToken::get_app_access_token( + &client, + client_id, + client_secret, + Scope::all(), + ).await?; + + let req = GetChannelInformationRequest::builder() + .broadcaster_id("27620241") + .build(); + + println!( + "{:?}", + &client.helix.req_get(req, &token).await?.data.unwrap().title + ); + + Ok(()) +} \ No newline at end of file diff --git a/src/twitch/twitch_client.rs b/src/twitch/twitch_client.rs index 566352fbac..95054c57cf 100644 --- a/src/twitch/twitch_client.rs +++ b/src/twitch/twitch_client.rs @@ -1,4 +1,4 @@ -use crate::twitch::secrets::TwitchSecrets; +/*use crate::twitch::secrets::TwitchSecrets; use crate::util::anyhow_result::AnyhowResult; use log::debug; use log::info; @@ -9,7 +9,7 @@ use twitchchat::messages::Join; use twitchchat::messages::Privmsg; use twitchchat::{AsyncRunner, Status, UserConfig}; -pub struct TwitchClient { +pub struct TwitchClient2 { secrets: TwitchSecrets, channels_to_join: Vec, @@ -19,7 +19,7 @@ pub struct TwitchClient { runner: Option, } -impl TwitchClient { +impl TwitchClient2 { pub fn new(secrets: &TwitchSecrets) -> Self { @@ -169,3 +169,4 @@ pub fn get_user_config(twitch_secrets: &TwitchSecrets) -> AnyhowResult Date: Sun, 26 Sep 2021 02:32:16 -0400 Subject: [PATCH 04/17] read secrets file --- src/main.rs | 11 ++++++----- src/twitch/secrets.rs | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index 519732d546..ad37c70cec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use crate::util::anyhow_result::AnyhowResult; use twitch_api2::TwitchClient; use twitch_api2::helix::channels::GetChannelInformationRequest; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; +use crate::twitch::secrets::TwitchSecrets; pub mod twitch; pub mod util; @@ -27,11 +28,10 @@ fn main() -> AnyhowResult<()> { #[tokio::main] async fn run() -> AnyhowResult<()> { - let client_id = "".to_string(); - let client_secret = "".to_string(); + let secrets = TwitchSecrets::from_file("secrets.toml")?; - let client_id = ClientId::new(client_id); - let client_secret = ClientSecret::new(client_secret); + let client_id = ClientId::new(&secrets.app_client_id); + let client_secret = ClientSecret::new(&secrets.app_client_secret); let client: TwitchClient = TwitchClient::default(); let token = AppAccessToken::get_app_access_token( @@ -42,7 +42,8 @@ async fn run() -> AnyhowResult<()> { ).await?; let req = GetChannelInformationRequest::builder() - .broadcaster_id("27620241") + //.broadcaster_id("27620241") + .broadcaster_id("650154491") .build(); println!( diff --git a/src/twitch/secrets.rs b/src/twitch/secrets.rs index 237c5d18f1..539a6507b0 100644 --- a/src/twitch/secrets.rs +++ b/src/twitch/secrets.rs @@ -2,13 +2,12 @@ use crate::util::anyhow_result::AnyhowResult; // TODO: Not sure I need all of these. The less I need to keep, the better. // Oauth delegation in particular seems not necessary. -#[derive(Clone)] +#[derive(Clone, serde::Deserialize)] pub struct TwitchSecrets { pub username: String, pub stream_key: String, pub app_client_id: String, pub app_client_secret: String, - pub oauth_access_token: String, } impl TwitchSecrets { @@ -17,14 +16,12 @@ impl TwitchSecrets { stream_key: &str, app_client_id: &str, app_client_secret: &str, - oauth_access_token: &str ) -> Self { Self { username: username.to_string(), stream_key: stream_key.to_string(), app_client_id: app_client_id.to_string(), app_client_secret: app_client_secret.to_string(), - oauth_access_token: oauth_access_token.to_string(), } } @@ -34,8 +31,13 @@ impl TwitchSecrets { &easyenv::get_env_string_required("TWITCH_STREAM_KEY")?, &easyenv::get_env_string_required("TWITCH_APP_CLIENT_ID")?, &easyenv::get_env_string_required("TWITCH_APP_CLIENT_SECRET")?, - &easyenv::get_env_string_required("TWITCH_OAUTH_ACCESS_TOKEN")?, )) } + + pub fn from_file(filename: &str) -> AnyhowResult { + let contents = std::fs::read_to_string(filename)?; + let secrets = toml::from_str(&contents)?; + Ok(secrets) + } } From e95ff9a7fafd355f510a70ac37cd40267b182cfe Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 03:08:34 -0400 Subject: [PATCH 05/17] try to connect over websockets (400) --- Cargo.lock | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- src/main.rs | 67 +++++++++++++++++++- 3 files changed, 245 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89be8f6a85..74002cbdb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,12 +83,27 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.1.0" @@ -107,6 +122,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "winapi", +] + [[package]] name = "core-foundation" version = "0.9.1" @@ -123,6 +150,24 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "displaydoc" version = "0.2.3" @@ -298,6 +343,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -553,6 +608,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -569,6 +643,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.36" @@ -608,6 +688,26 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.7" @@ -805,6 +905,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct", + "webpki", +] + [[package]] name = "ryu" version = "1.0.5" @@ -917,6 +1030,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "slab" version = "0.4.4" @@ -1046,6 +1172,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "rustls 0.19.1", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.6.8" @@ -1101,18 +1241,39 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "twitch-tts-desktop-client" version = "0.0.0" dependencies = [ "anyhow", "easyenv", + "futures-util", "log", "reqwest", - "rustls", + "rustls 0.18.1", "serde", "serde_derive", "tokio", + "tokio-tungstenite", "toml", "twitch_api2", "twitch_oauth2", @@ -1126,6 +1287,7 @@ checksum = "003e4481fa1bc7cf6587dd212a4ce5f3b69dee893c9f7fdf8a0ed7149f470f1b" dependencies = [ "aliri_braid", "async-trait", + "chrono", "displaydoc", "futures", "http", @@ -1175,6 +1337,12 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" + [[package]] name = "unicode-bidi" version = "0.3.6" @@ -1214,6 +1382,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index cff34fa515..314ec03f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" # Crypto rustls = "0.18" +tokio-tungstenite = { version = "0.15.0", features = ["rustls"] } # Serialization serde = "1.0.130" @@ -31,11 +32,12 @@ toml = "0.5.8" #twitch_oauth2 = "0.4.1" #twitch_api2 = "0.5.0" #twitch_oauth2 = "0.5.0" -twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client"] } +# NB "unsupported" pubsub features may disappear from the Twitch API at any time. It's an unofficial API. +twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client", "pubsub", "chrono", "unsupported"] } twitch_oauth2 = "0.6.0-rc.2" reqwest = { version = "0.11.4" } tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } - +futures-util = "0.3.17" # Utils anyhow = "1.0.44" diff --git a/src/main.rs b/src/main.rs index ad37c70cec..55a54ea592 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,20 @@ extern crate twitch_api2; +use futures_util::{SinkExt, StreamExt}; extern crate reqwest; +use crate::twitch::secrets::TwitchSecrets; use crate::util::anyhow_result::AnyhowResult; use twitch_api2::TwitchClient; use twitch_api2::helix::channels::GetChannelInformationRequest; +use twitch_api2::pubsub; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; -use crate::twitch::secrets::TwitchSecrets; +use twitch_api2::pubsub::Topic; + +use tokio_tungstenite::{ + connect_async, + tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, +}; +use reqwest::Url; pub mod twitch; pub mod util; @@ -51,5 +60,61 @@ async fn run() -> AnyhowResult<()> { &client.helix.req_get(req, &token).await?.data.unwrap().title ); + + // Want this: + // twitch_api2::pubsub::channel_bits::BitsEventData + + + + /*// We want to subscribe to moderator actions on channel with id 1234 + // as if we were a user with id 4321 that is moderator on the channel. + let chat_mod_actions = pubsub::moderation::ChatModeratorActions { + user_id: 4321, + channel_id: 1234, + } + .into_topic();*/ + + // Listen to follows as well + let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + let topics = [follows]; + // Create the topic command to send to twitch + let command = pubsub::listen_command( + &topics, + "authtoken", + "random nonce string", + )?; + + println!("Pubsub command: {:?}", command); + + // Send the message with your favorite websocket client + //send_command(command).unwrap(); + // To parse the websocket messages, use pubsub::Response::parse + + let url = Url::parse("wss://pubsub-edge.twitch.tv")?; + + println!("Connecting..."); + + let (mut socket, _response) = + connect_async(url).await?; + + println!("Connected!"); + + let msg = socket.next().await.expect("Can't fetch case count")?; + socket.close(None).await?; + + let text = msg.into_text()?; + println!("Text: {}", text); + Ok(()) +} + + +async fn get_case_count() -> TungsteniteResult { + let (mut socket, _) = connect_async( + Url::parse("ws://localhost:9001/getCaseCount").expect("Can't connect to case count URL"), + ) + .await?; + let msg = socket.next().await.expect("Can't fetch case count")?; + socket.close(None).await?; + Ok(msg.into_text()?.parse::().expect("Can't parse case count")) } \ No newline at end of file From f67d048f276f3156eda94d2d9e6599547f5059bc Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 04:04:07 -0400 Subject: [PATCH 06/17] connects to websockets (wrong library feature enabled) --- Cargo.lock | 103 +++++++++++++++++++++++++++++++++++++++------------- Cargo.toml | 17 +++++---- src/main.rs | 17 +++++---- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74002cbdb7..1f0d4e5015 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,12 +65,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -849,7 +843,7 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" dependencies = [ - "base64 0.13.0", + "base64", "bytes", "encoding_rs", "futures-core", @@ -892,30 +886,41 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustls" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" -dependencies = [ - "base64 0.12.3", - "log", - "ring", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.0", + "base64", "log", "ring", - "sct", - "webpki", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.0-beta2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85530c337a44ed700862acf71f9013cd62600f79de996d3c3ea87773df910d5" +dependencies = [ + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls 0.19.1", + "schannel", + "security-framework", ] [[package]] @@ -944,6 +949,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.4.2" @@ -1172,6 +1187,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + [[package]] name = "tokio-tungstenite" version = "0.15.0" @@ -1183,7 +1209,10 @@ dependencies = [ "pin-project", "rustls 0.19.1", "tokio", + "tokio-rustls", "tungstenite", + "webpki 0.21.4", + "webpki-roots", ] [[package]] @@ -1247,17 +1276,20 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" dependencies = [ - "base64 0.13.0", + "base64", "byteorder", "bytes", "http", "httparse", "log", "rand", + "rustls 0.19.1", + "rustls-native-certs", "sha-1", "thiserror", "url", "utf-8", + "webpki 0.21.4", ] [[package]] @@ -1269,7 +1301,7 @@ dependencies = [ "futures-util", "log", "reqwest", - "rustls 0.18.1", + "rustls 0.20.0-beta2", "serde", "serde_derive", "tokio", @@ -1313,7 +1345,7 @@ checksum = "af3ac686c0fd364beafa745e4471c602d945ff5e1962e36e27c38ad92660e929" dependencies = [ "aliri_braid", "async-trait", - "base64 0.13.0", + "base64", "displaydoc", "http", "once_cell", @@ -1504,6 +1536,25 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 314ec03f88..a7847ce405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,18 @@ name = "twitch-tts-desktop-client" path = "src/main.rs" [dependencies] -# Async runtime -#tokio = { version = "0.2", features = ["full"] } +# Async runtime, Futures, etc. +tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } +futures-util = "0.3.17" # Crypto -rustls = "0.18" -tokio-tungstenite = { version = "0.15.0", features = ["rustls"] } +rustls = "0.20.0-beta2" + +# Websockets +tokio-tungstenite = { version = "0.15.0", features = ["rustls", "rustls-tls"] } + +# HTTP +reqwest = { version = "0.11.4" } # Serialization serde = "1.0.130" @@ -35,9 +41,6 @@ toml = "0.5.8" # NB "unsupported" pubsub features may disappear from the Twitch API at any time. It's an unofficial API. twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client", "pubsub", "chrono", "unsupported"] } twitch_oauth2 = "0.6.0-rc.2" -reqwest = { version = "0.11.4" } -tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } -futures-util = "0.3.17" # Utils anyhow = "1.0.44" diff --git a/src/main.rs b/src/main.rs index 55a54ea592..dc1ec4fbb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,10 +10,7 @@ use twitch_api2::pubsub; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; use twitch_api2::pubsub::Topic; -use tokio_tungstenite::{ - connect_async, - tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, -}; +use tokio_tungstenite::{connect_async, tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, connect_async_with_config}; use reqwest::Url; pub mod twitch; @@ -39,7 +36,7 @@ fn main() -> AnyhowResult<()> { async fn run() -> AnyhowResult<()> { let secrets = TwitchSecrets::from_file("secrets.toml")?; - let client_id = ClientId::new(&secrets.app_client_id); + /*let client_id = ClientId::new(&secrets.app_client_id); let client_secret = ClientSecret::new(&secrets.app_client_secret); let client: TwitchClient = TwitchClient::default(); @@ -58,7 +55,7 @@ async fn run() -> AnyhowResult<()> { println!( "{:?}", &client.helix.req_get(req, &token).await?.data.unwrap().title - ); + );*/ // Want this: @@ -75,7 +72,7 @@ async fn run() -> AnyhowResult<()> { .into_topic();*/ // Listen to follows as well - let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + /*let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); let topics = [follows]; // Create the topic command to send to twitch let command = pubsub::listen_command( @@ -84,7 +81,7 @@ async fn run() -> AnyhowResult<()> { "random nonce string", )?; - println!("Pubsub command: {:?}", command); + println!("Pubsub command: {:?}", command);*/ // Send the message with your favorite websocket client //send_command(command).unwrap(); @@ -94,6 +91,10 @@ async fn run() -> AnyhowResult<()> { println!("Connecting..."); + if let Err(e) = connect_async(&url).await { + println!("Error connecting: {:?}", e); + } + let (mut socket, _response) = connect_async(url).await?; From 90b43bf7620f529697a4a9d030efbb6f959b7ed3 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 12:15:51 -0400 Subject: [PATCH 07/17] send ping --- src/main.rs | 13 +++-- src/twitch/mod.rs | 3 +- src/twitch/websocket_client.rs | 103 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 src/twitch/websocket_client.rs diff --git a/src/main.rs b/src/main.rs index dc1ec4fbb3..3eaa665a0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use twitch_api2::pubsub::Topic; use tokio_tungstenite::{connect_async, tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, connect_async_with_config}; use reqwest::Url; +use crate::twitch::websocket_client::TwitchWebsocketClient; pub mod twitch; pub mod util; @@ -91,7 +92,7 @@ async fn run() -> AnyhowResult<()> { println!("Connecting..."); - if let Err(e) = connect_async(&url).await { + /*if let Err(e) = connect_async(&url).await { println!("Error connecting: {:?}", e); } @@ -101,10 +102,14 @@ async fn run() -> AnyhowResult<()> { println!("Connected!"); let msg = socket.next().await.expect("Can't fetch case count")?; - socket.close(None).await?; + socket.close(None).await?;*/ - let text = msg.into_text()?; - println!("Text: {}", text); + println!("Connected"); + let mut ws_client = TwitchWebsocketClient::new()?; + ws_client.connect().await?; + + //let text = msg.into_text()?; + //println!("Text: {}", text); Ok(()) } diff --git a/src/twitch/mod.rs b/src/twitch/mod.rs index f79395be0d..180c2ab938 100644 --- a/src/twitch/mod.rs +++ b/src/twitch/mod.rs @@ -1,2 +1,3 @@ pub mod secrets; -pub mod twitch_client; \ No newline at end of file +pub mod twitch_client; +pub mod websocket_client; \ No newline at end of file diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs new file mode 100644 index 0000000000..05578aadbb --- /dev/null +++ b/src/twitch/websocket_client.rs @@ -0,0 +1,103 @@ +use crate::util::anyhow_result::AnyhowResult; +use reqwest::Url; +use std::sync::atomic::{AtomicBool, Ordering}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, WebSocketStream, MaybeTlsStream}; +use twitch_api2::pubsub; +use futures_util::{SinkExt, StreamExt}; +use tokio_tungstenite::tungstenite::Message; +use twitch_api2::pubsub::{Response, TopicData}; + +// Reference javascript Twitch Websocket client: +// https://github.com/twitchdev/pubsub-javascript-sample/blob/main/main.js + +const WEBSOCKET_GATEWAY : &'static str = "wss://pubsub-edge.twitch.tv"; +const PING_COMMAND : &'static str = "{ \"type\": \"PING\" }"; + + +pub struct TwitchWebsocketClient { + socket: Option>>, + is_polling: AtomicBool, +} + +impl TwitchWebsocketClient { + + pub fn new() -> AnyhowResult { + Ok(Self { + socket: None, + is_polling: AtomicBool::new(false), + }) + } + + pub async fn connect(&mut self) -> AnyhowResult<()> { + let url = Url::parse(WEBSOCKET_GATEWAY)?; + + let (mut socket, _response) = + connect_async(url).await?; + + let message = Message::Text(PING_COMMAND.to_string()); + socket.send(message).await?; + + if let Some(response) = socket.next().await { + + if let Ok(r) = response { + let text = r.to_text()?; + println!("Text response: {:?}", text); + + let typed_response = Response::parse(text)?; + + match typed_response { + Response::Pong => { println!("Pong") } + Response::Reconnect => { println!("Reconnect") } + Response::Response(_0) => { println!("Sub/Unsub (pubsub?)") } + Response::Message { data } => { + match data { + TopicData::ChannelCheerEventsPublicV1 { topic, reply } => {} + TopicData::ChannelSubGiftsV1 { topic, reply } => {} + TopicData::ChannelSubscribeEventsV1 { topic, reply } => {} + TopicData::CommunityPointsChannelV1 { topic, reply } => {} + TopicData::Following { topic, reply} => {} + // Not interested in these events + TopicData::AutoModQueue { .. } => {} + TopicData::ChannelBitsBadgeUnlocks { .. } => {} + TopicData::ChannelBitsEventsV2 { .. } => {} + TopicData::ChannelPointsChannelV1 { .. } => {} + TopicData::ChatModeratorActions { .. } => {} + TopicData::HypeTrainEventsV1 { .. } => {} + TopicData::HypeTrainEventsV1Rewards { .. } => {} + TopicData::Raid { .. } => {} + TopicData::UserModerationNotifications { .. } => {} + TopicData::VideoPlayback { .. } => {} + TopicData::VideoPlaybackById { .. } => {} + } + } + } + } + } + + + self.socket = Some(socket); + + Ok(()) + } + + async fn start_polling(&self) { + if self.is_polling.load(Ordering::Relaxed) { + return; + } + + /*tokio::spawn(async move { + self.is_polling.store(true, Ordering::Relaxed); + + let command = pubsub::channel_sub_gifts( + &topics, + "authtoken", + "random nonce string", + )?; + + }); + */ + + + } +} \ No newline at end of file From 33867a54852d1eabfb8c5adead16990596afa5a4 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 17:32:41 -0400 Subject: [PATCH 08/17] refactor --- src/main.rs | 23 ++++------ src/twitch/websocket_client.rs | 83 +++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3eaa665a0c..9466c213a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +#![allow(unused_imports)] +#![allow(dead_code)] + + extern crate twitch_api2; use futures_util::{SinkExt, StreamExt}; extern crate reqwest; @@ -88,26 +92,15 @@ async fn run() -> AnyhowResult<()> { //send_command(command).unwrap(); // To parse the websocket messages, use pubsub::Response::parse - let url = Url::parse("wss://pubsub-edge.twitch.tv")?; - - println!("Connecting..."); - - /*if let Err(e) = connect_async(&url).await { - println!("Error connecting: {:?}", e); - } - - let (mut socket, _response) = - connect_async(url).await?; - - println!("Connected!"); - - let msg = socket.next().await.expect("Can't fetch case count")?; - socket.close(None).await?;*/ println!("Connected"); let mut ws_client = TwitchWebsocketClient::new()?; ws_client.connect().await?; + ws_client.send_ping().await?; + + println!("success ping"); + //let text = msg.into_text()?; //println!("Text: {}", text); diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index 05578aadbb..2c171b1409 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -1,12 +1,14 @@ +use anyhow::anyhow; +use anyhow::bail; use crate::util::anyhow_result::AnyhowResult; +use futures_util::{SinkExt, StreamExt}; use reqwest::Url; use std::sync::atomic::{AtomicBool, Ordering}; use tokio::net::TcpStream; -use tokio_tungstenite::{connect_async, WebSocketStream, MaybeTlsStream}; -use twitch_api2::pubsub; -use futures_util::{SinkExt, StreamExt}; use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::{connect_async, WebSocketStream, MaybeTlsStream}; use twitch_api2::pubsub::{Response, TopicData}; +use twitch_api2::pubsub; // Reference javascript Twitch Websocket client: // https://github.com/twitchdev/pubsub-javascript-sample/blob/main/main.js @@ -14,18 +16,28 @@ use twitch_api2::pubsub::{Response, TopicData}; const WEBSOCKET_GATEWAY : &'static str = "wss://pubsub-edge.twitch.tv"; const PING_COMMAND : &'static str = "{ \"type\": \"PING\" }"; - -pub struct TwitchWebsocketClient { - socket: Option>>, +pub struct PollingTwitchWebsocketClient { + client: TwitchWebsocketClient, is_polling: AtomicBool, } -impl TwitchWebsocketClient { +impl PollingTwitchWebsocketClient { + pub fn new() -> AnyhowResult { + Ok(Self { + client: TwitchWebsocketClient::new()?, + is_polling: AtomicBool::new(false), + }) + } +} +pub struct TwitchWebsocketClient { + socket: Option>>, +} + +impl TwitchWebsocketClient { pub fn new() -> AnyhowResult { Ok(Self { socket: None, - is_polling: AtomicBool::new(false), }) } @@ -38,14 +50,11 @@ impl TwitchWebsocketClient { let message = Message::Text(PING_COMMAND.to_string()); socket.send(message).await?; - if let Some(response) = socket.next().await { - - if let Ok(r) = response { - let text = r.to_text()?; - println!("Text response: {:?}", text); - - let typed_response = Response::parse(text)?; + self.socket = Some(socket); + match self.read_response().await { + Err(e) => println!("Error getting PONG: {:?}", e), + Ok(typed_response) => { match typed_response { Response::Pong => { println!("Pong") } Response::Reconnect => { println!("Reconnect") } @@ -75,18 +84,48 @@ impl TwitchWebsocketClient { } } - - self.socket = Some(socket); - Ok(()) } - async fn start_polling(&self) { + pub async fn send_ping(&mut self) -> AnyhowResult<()> { + let mut socket = match self.socket { + None => return Err(anyhow!("not connected")), + Some(ref mut socket) => socket, + }; + + let message = Message::Text(PING_COMMAND.to_string()); + socket.send(message).await?; + + match self.read_response().await { + Err(e) => Err(e), + Ok(Response::Pong) => Ok(()), + _ => Err(bail!("wrong response type")), + } + } + + async fn read_response(&mut self) -> AnyhowResult { + let socket = match self.socket { + None => return Err(anyhow!("not connected")), + Some(ref mut s) => s, + }; + + match socket.next().await { + None => Err(anyhow!("nothing to read")), + Some(Err(e)) => Err(anyhow!("websocket error: {:?}", e)), + Some(Ok(response)) => { + let text = response.to_text()?; + let typed_response = Response::parse(text)?; + Ok(typed_response) + }, + } + } + + /*async fn start_polling(&self) { if self.is_polling.load(Ordering::Relaxed) { return; } - /*tokio::spawn(async move { + tokio::spawn(async move { self.is_polling.store(true, Ordering::Relaxed); let command = pubsub::channel_sub_gifts( @@ -96,8 +135,8 @@ impl TwitchWebsocketClient { )?; }); - */ + / - } + }*/ } \ No newline at end of file From 00fef2a2d2430259f904bbe2fd7cca77f17d9d90 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 18:16:22 -0400 Subject: [PATCH 09/17] more structure, but still too rigid --- Cargo.toml | 2 +- src/main.rs | 18 +++- src/twitch/websocket_client.rs | 165 +++++++++++++++++++++------------ 3 files changed, 121 insertions(+), 64 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a7847ce405..0a57ac5219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" [dependencies] # Async runtime, Futures, etc. -tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.10.0", features = ["rt-multi-thread", "macros", "time"] } futures-util = "0.3.17" # Crypto diff --git a/src/main.rs b/src/main.rs index 9466c213a0..2c8af70a2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,8 @@ use twitch_api2::pubsub::Topic; use tokio_tungstenite::{connect_async, tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, connect_async_with_config}; use reqwest::Url; -use crate::twitch::websocket_client::TwitchWebsocketClient; +use crate::twitch::websocket_client::{TwitchWebsocketClient, PollingTwitchWebsocketClient}; +use std::time::Duration; pub mod twitch; pub mod util; @@ -93,13 +94,24 @@ async fn run() -> AnyhowResult<()> { // To parse the websocket messages, use pubsub::Response::parse - println!("Connected"); + /*println!("Connected"); let mut ws_client = TwitchWebsocketClient::new()?; ws_client.connect().await?; ws_client.send_ping().await?; - println!("success ping"); + println!("success ping");*/ + + let mut client = PollingTwitchWebsocketClient::new()?; + + println!("Connecting..."); + client.connect().await?; + println!("Connected"); + + println!("Starting polling thread..."); + client.polling_thread().await; + + std::thread::sleep(Duration::from_millis(30000)); //let text = msg.into_text()?; //println!("Text: {}", text); diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index 2c171b1409..a8fa06df82 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -4,7 +4,10 @@ use crate::util::anyhow_result::AnyhowResult; use futures_util::{SinkExt, StreamExt}; use reqwest::Url; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, PoisonError, MutexGuard}; +use std::time::Duration; use tokio::net::TcpStream; +use tokio; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{connect_async, WebSocketStream, MaybeTlsStream}; use twitch_api2::pubsub::{Response, TopicData}; @@ -17,17 +20,108 @@ const WEBSOCKET_GATEWAY : &'static str = "wss://pubsub-edge.twitch.tv"; const PING_COMMAND : &'static str = "{ \"type\": \"PING\" }"; pub struct PollingTwitchWebsocketClient { - client: TwitchWebsocketClient, + client: Arc>, is_polling: AtomicBool, } impl PollingTwitchWebsocketClient { pub fn new() -> AnyhowResult { Ok(Self { - client: TwitchWebsocketClient::new()?, + client: Arc::new(Mutex::new(TwitchWebsocketClient::new()?)), is_polling: AtomicBool::new(false), }) } + + async fn connect_keepalive(&self) -> AnyhowResult<()> { + + //tokio::spawn(self.polling_thread); + + Ok(()) + } + + pub async fn connect(&mut self) -> AnyhowResult<()> { + match self.client.lock() { + Err(e) => Err(anyhow!("could not connect: {:?}", e)), + Ok(mut client) => { + client.connect().await + } + } + } + + pub async fn polling_thread(&self) { + let client = self.client.clone(); + tokio::spawn(async move { + loop { + println!("Poll"); + + match client.lock() { + Ok(mut c) => { + println!("Ping..."); + c.send_ping(); + println!("Read response..."); + c.read_response(); + } + Err(_) => {} + } + + tokio::time::sleep(Duration::from_millis(5000)).await; + } + }); + } + + /*async fn start_polling(&self) { + if self.is_polling.load(Ordering::Relaxed) { + return; + } + + tokio::spawn(async move { + self.is_polling.store(true, Ordering::Relaxed); + + let command = pubsub::channel_sub_gifts( + &topics, + "authtoken", + "random nonce string", + )?; + + }); + / + }*/ + + + /*let message = Message::Text(PING_COMMAND.to_string()); + socket.send(message).await?; + match self.read_response().await { + Err(e) => println!("Error getting PONG: {:?}", e), + Ok(typed_response) => { + match typed_response { + Response::Pong => { println!("Pong") } + Response::Reconnect => { println!("Reconnect") } + Response::Response(_0) => { println!("Sub/Unsub (pubsub?)") } + Response::Message { data } => { + match data { + TopicData::ChannelCheerEventsPublicV1 { topic, reply } => {} + TopicData::ChannelSubGiftsV1 { topic, reply } => {} + TopicData::ChannelSubscribeEventsV1 { topic, reply } => {} + TopicData::CommunityPointsChannelV1 { topic, reply } => {} + TopicData::Following { topic, reply} => {} + // Not interested in these events + TopicData::AutoModQueue { .. } => {} + TopicData::ChannelBitsBadgeUnlocks { .. } => {} + TopicData::ChannelBitsEventsV2 { .. } => {} + TopicData::ChannelPointsChannelV1 { .. } => {} + TopicData::ChatModeratorActions { .. } => {} + TopicData::HypeTrainEventsV1 { .. } => {} + TopicData::HypeTrainEventsV1Rewards { .. } => {} + TopicData::Raid { .. } => {} + TopicData::UserModerationNotifications { .. } => {} + TopicData::VideoPlayback { .. } => {} + TopicData::VideoPlaybackById { .. } => {} + } + } + } + } + }*/ + } pub struct TwitchWebsocketClient { @@ -43,56 +137,27 @@ impl TwitchWebsocketClient { pub async fn connect(&mut self) -> AnyhowResult<()> { let url = Url::parse(WEBSOCKET_GATEWAY)?; - let (mut socket, _response) = connect_async(url).await?; - - let message = Message::Text(PING_COMMAND.to_string()); - socket.send(message).await?; - self.socket = Some(socket); - - match self.read_response().await { - Err(e) => println!("Error getting PONG: {:?}", e), - Ok(typed_response) => { - match typed_response { - Response::Pong => { println!("Pong") } - Response::Reconnect => { println!("Reconnect") } - Response::Response(_0) => { println!("Sub/Unsub (pubsub?)") } - Response::Message { data } => { - match data { - TopicData::ChannelCheerEventsPublicV1 { topic, reply } => {} - TopicData::ChannelSubGiftsV1 { topic, reply } => {} - TopicData::ChannelSubscribeEventsV1 { topic, reply } => {} - TopicData::CommunityPointsChannelV1 { topic, reply } => {} - TopicData::Following { topic, reply} => {} - // Not interested in these events - TopicData::AutoModQueue { .. } => {} - TopicData::ChannelBitsBadgeUnlocks { .. } => {} - TopicData::ChannelBitsEventsV2 { .. } => {} - TopicData::ChannelPointsChannelV1 { .. } => {} - TopicData::ChatModeratorActions { .. } => {} - TopicData::HypeTrainEventsV1 { .. } => {} - TopicData::HypeTrainEventsV1Rewards { .. } => {} - TopicData::Raid { .. } => {} - TopicData::UserModerationNotifications { .. } => {} - TopicData::VideoPlayback { .. } => {} - TopicData::VideoPlaybackById { .. } => {} - } - } - } - } - } - Ok(()) } + pub fn is_connected(&self) -> bool { + // TODO: This probably isn't accurate + self.socket.is_some() + } + pub async fn send_ping(&mut self) -> AnyhowResult<()> { let mut socket = match self.socket { None => return Err(anyhow!("not connected")), Some(ref mut socket) => socket, }; + // TODO: + // To keep the server from closing the connection, clients must send a PING command + // at least once every 5 minutes. If a client does not receive a PONG message within + // 10 seconds of issuing a PING command, it should reconnect to the server. let message = Message::Text(PING_COMMAND.to_string()); socket.send(message).await?; @@ -119,24 +184,4 @@ impl TwitchWebsocketClient { }, } } - - /*async fn start_polling(&self) { - if self.is_polling.load(Ordering::Relaxed) { - return; - } - - tokio::spawn(async move { - self.is_polling.store(true, Ordering::Relaxed); - - let command = pubsub::channel_sub_gifts( - &topics, - "authtoken", - "random nonce string", - )?; - - }); - / - - - }*/ } \ No newline at end of file From b6ec22e3437346487cf696e6040cfef5352277ae Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 20:07:02 -0400 Subject: [PATCH 10/17] this doesn't behave as I'd expect. --- src/main.rs | 36 +++++++++------ src/twitch/websocket_client.rs | 84 +++++++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2c8af70a2f..82e7f304fb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,31 @@ +// TODO: These are temporary - #![allow(unused_imports)] +#![allow(unused_mut)] #![allow(dead_code)] - -extern crate twitch_api2; -use futures_util::{SinkExt, StreamExt}; -extern crate reqwest; - use crate::twitch::secrets::TwitchSecrets; +use crate::twitch::websocket_client::{TwitchWebsocketClient, PollingTwitchWebsocketClient}; use crate::util::anyhow_result::AnyhowResult; +use futures_util::{SinkExt, StreamExt}; +use log::info; +use reqwest::Url; +use std::time::Duration; +use tokio_tungstenite::{connect_async, tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, connect_async_with_config}; use twitch_api2::TwitchClient; use twitch_api2::helix::channels::GetChannelInformationRequest; +use twitch_api2::pubsub::Topic; use twitch_api2::pubsub; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; -use twitch_api2::pubsub::Topic; - -use tokio_tungstenite::{connect_async, tungstenite::{Error as TungsteniteError, Result as TungsteniteResult}, connect_async_with_config}; -use reqwest::Url; -use crate::twitch::websocket_client::{TwitchWebsocketClient, PollingTwitchWebsocketClient}; -use std::time::Duration; pub mod twitch; pub mod util; +const DEFAULT_LOG : &'static str = "debug,rustls=warn,tungstenite=warn"; + fn main() -> AnyhowResult<()> { - println!("Hello World"); + easyenv::init_all_with_default_logging(Some(DEFAULT_LOG)); + + info!("Starting"); use std::error::Error; if let Err(err) = run() { @@ -109,8 +111,16 @@ async fn run() -> AnyhowResult<()> { println!("Connected"); println!("Starting polling thread..."); - client.polling_thread().await; + client.start_ping_thread().await; + println!("Try read next..."); + client.try_next().await?; + println!("Try read next..."); + client.try_next().await?; + println!("Try read next..."); + client.try_next().await?; + + println!("Sleep..."); std::thread::sleep(Duration::from_millis(30000)); //let text = msg.into_text()?; diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index a8fa06df82..d4e0f7a4f2 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -1,7 +1,8 @@ use anyhow::anyhow; use anyhow::bail; use crate::util::anyhow_result::AnyhowResult; -use futures_util::{SinkExt, StreamExt}; +use futures_util::{SinkExt, StreamExt, TryStreamExt}; +use log::debug; use reqwest::Url; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex, PoisonError, MutexGuard}; @@ -17,7 +18,9 @@ use twitch_api2::pubsub; // https://github.com/twitchdev/pubsub-javascript-sample/blob/main/main.js const WEBSOCKET_GATEWAY : &'static str = "wss://pubsub-edge.twitch.tv"; + const PING_COMMAND : &'static str = "{ \"type\": \"PING\" }"; +const PING_SECONDS : u64 = 1 * 60; // Twitch calls for every 5 minutes, we'll do every 1 minute. pub struct PollingTwitchWebsocketClient { client: Arc>, @@ -41,34 +44,55 @@ impl PollingTwitchWebsocketClient { pub async fn connect(&mut self) -> AnyhowResult<()> { match self.client.lock() { - Err(e) => Err(anyhow!("could not connect: {:?}", e)), + Err(e) => Err(anyhow!("unlock client err: {:?}", e)), Ok(mut client) => { client.connect().await } } } - pub async fn polling_thread(&self) { + /// Start a pinging background thread. + /// Per Twitch PubSub docs, + /// To keep the server from closing the connection, clients must send a PING command + /// at least once every 5 minutes. If a client does not receive a PONG message within + /// 10 seconds of issuing a PING command, it should reconnect to the server. + pub async fn start_ping_thread(&self) { let client = self.client.clone(); tokio::spawn(async move { loop { - println!("Poll"); - match client.lock() { Ok(mut c) => { - println!("Ping..."); c.send_ping(); - println!("Read response..."); - c.read_response(); + // TODO: Need to read from all call sites due to ordering. + //c.read_response(); + } + Err(_) => { + // TODO } - Err(_) => {} } - - tokio::time::sleep(Duration::from_millis(5000)).await; + tokio::time::sleep(Duration::from_secs(PING_SECONDS)).await; } }); } + pub async fn next(&mut self) -> AnyhowResult { + match self.client.lock() { + Err(e) => Err(anyhow!("unlock client err: {:?}", e)), + Ok(mut client) => { + client.next().await + } + } + } + + pub async fn try_next(&mut self) -> AnyhowResult> { + match self.client.lock() { + Err(e) => Err(anyhow!("unlock client err: {:?}", e)), + Ok(mut client) => { + client.try_next().await + } + } + } + /*async fn start_polling(&self) { if self.is_polling.load(Ordering::Relaxed) { return; @@ -154,21 +178,20 @@ impl TwitchWebsocketClient { Some(ref mut socket) => socket, }; - // TODO: - // To keep the server from closing the connection, clients must send a PING command - // at least once every 5 minutes. If a client does not receive a PONG message within - // 10 seconds of issuing a PING command, it should reconnect to the server. + debug!("Sending ping"); let message = Message::Text(PING_COMMAND.to_string()); socket.send(message).await?; - match self.read_response().await { - Err(e) => Err(e), - Ok(Response::Pong) => Ok(()), - _ => Err(bail!("wrong response type")), - } + // TODO: Need to read from all call sites due to ordering. + //match self.read_response().await { + // Err(e) => Err(e), + // Ok(Response::Pong) => Ok(()), + // _ => Err(bail!("wrong response type")), + //} + Ok(()) } - async fn read_response(&mut self) -> AnyhowResult { + pub async fn next(&mut self) -> AnyhowResult { let socket = match self.socket { None => return Err(anyhow!("not connected")), Some(ref mut s) => s, @@ -184,4 +207,21 @@ impl TwitchWebsocketClient { }, } } -} \ No newline at end of file + + pub async fn try_next(&mut self) -> AnyhowResult> { + let socket = match self.socket { + None => return Err(anyhow!("not connected")), + Some(ref mut s) => s, + }; + + match socket.try_next().await { + Err(e) => Err(anyhow!("websocket error: {:?}", e)), + Ok(None) => Ok(None), + Ok(Some(response)) => { + let text = response.to_text()?; + let typed_response = Response::parse(text)?; + Ok(Some(typed_response)) + }, + } + } +} From 94ea57deacaa8d0593b3c0a7ec28bda6e53bb437 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 20:11:04 -0400 Subject: [PATCH 11/17] close method --- src/twitch/websocket_client.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index d4e0f7a4f2..b8ece637eb 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -167,6 +167,18 @@ impl TwitchWebsocketClient { Ok(()) } + pub async fn close(&mut self) -> AnyhowResult<()> { + let mut socket = match self.socket { + None => return Ok(()), // Already closed. + Some(ref mut socket) => socket, + }; + + socket.close(None).await?; + self.socket = None; + + Ok(()) + } + pub fn is_connected(&self) -> bool { // TODO: This probably isn't accurate self.socket.is_some() From fc19a1430bc71276c74e9228bd41f7749b4fd104 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 20:29:57 -0400 Subject: [PATCH 12/17] listen command --- Cargo.lock | 1 + Cargo.toml | 1 + src/twitch/websocket_client.rs | 36 ++++++++++++++++++++++++++++++++-- src/util/mod.rs | 4 +++- src/util/random_nonce.rs | 24 +++++++++++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 src/util/random_nonce.rs diff --git a/Cargo.lock b/Cargo.lock index 1f0d4e5015..3c3628861a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1300,6 +1300,7 @@ dependencies = [ "easyenv", "futures-util", "log", + "rand", "reqwest", "rustls 0.20.0-beta2", "serde", diff --git a/Cargo.toml b/Cargo.toml index 0a57ac5219..af96cc2d8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ twitch_oauth2 = "0.6.0-rc.2" anyhow = "1.0.44" easyenv = "0.0.6" log = "0.4.14" +rand = "0.8.4" #[build-dependencies] diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index b8ece637eb..4c060a21b9 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -11,8 +11,9 @@ use tokio::net::TcpStream; use tokio; use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::{connect_async, WebSocketStream, MaybeTlsStream}; -use twitch_api2::pubsub::{Response, TopicData}; +use twitch_api2::pubsub::{Response, TopicData, Topic, Topics}; use twitch_api2::pubsub; +use crate::util::random_nonce::random_nonce; // Reference javascript Twitch Websocket client: // https://github.com/twitchdev/pubsub-javascript-sample/blob/main/main.js @@ -179,6 +180,37 @@ impl TwitchWebsocketClient { Ok(()) } + pub async fn listen<'t>(&mut self, auth_token: &str, topics: &'t [Topics]) -> AnyhowResult<()> { + //// We want to subscribe to moderator actions on channel with id 1234 + //// as if we were a user with id 4321 that is moderator on the channel. + //let chat_mod_actions = pubsub::moderation::ChatModeratorActions { + // user_id: 4321, + // channel_id: 1234, + //}.into_topic(); + + //// Listen to follows as well + //let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + + let mut socket = match self.socket { + None => return Err(anyhow!("not connected")), + Some(ref mut socket) => socket, + }; + + let nonce = random_nonce(128); + + let command = pubsub::listen_command( + topics, + auth_token, + nonce.as_ref(), + )?; + + debug!("Sending LISTEN"); + let message = Message::Text(command); + socket.send(message).await?; + + Ok(()) + } + pub fn is_connected(&self) -> bool { // TODO: This probably isn't accurate self.socket.is_some() @@ -190,7 +222,7 @@ impl TwitchWebsocketClient { Some(ref mut socket) => socket, }; - debug!("Sending ping"); + debug!("Sending PING"); let message = Message::Text(PING_COMMAND.to_string()); socket.send(message).await?; diff --git a/src/util/mod.rs b/src/util/mod.rs index 0bb75d1b62..145df6fe0c 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1 +1,3 @@ -pub mod anyhow_result; \ No newline at end of file +pub mod anyhow_result; +pub mod random_nonce; + diff --git a/src/util/random_nonce.rs b/src/util/random_nonce.rs new file mode 100644 index 0000000000..5f9034d3aa --- /dev/null +++ b/src/util/random_nonce.rs @@ -0,0 +1,24 @@ +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; + +/// Returns a random nonce for Twitch +pub fn random_nonce(length: usize) -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::util::random_nonce::random_nonce; + + #[test] + fn random_nonce_length() { + assert_eq(1, random_nonce(1).len()); + assert_eq(10, random_nonce(10).len()); + assert_eq(15, random_nonce(15).len()); + assert_eq(64, random_nonce(64).len()); + } +} \ No newline at end of file From c2eb31cbb2b75c6686fc2efcddf7b194a7f3a5ea Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 20:33:51 -0400 Subject: [PATCH 13/17] listen wip --- src/main.rs | 26 +++++++++++++++++++++++--- src/twitch/websocket_client.rs | 19 +++++++++---------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 82e7f304fb..1c80aa7e57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,12 +113,32 @@ async fn run() -> AnyhowResult<()> { println!("Starting polling thread..."); client.start_ping_thread().await; + + // We want to subscribe to moderator actions on channel with id 1234 + // as if we were a user with id 4321 that is moderator on the channel. + let chat_mod_actions = pubsub::moderation::ChatModeratorActions { + user_id: 4321, + channel_id: 1234, + }.into_topic(); + + // Listen to follows as well + let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + + println!("Begin LISTEN..."); + client.listen("auth_token", &[chat_mod_actions, follows]).await; + + println!("Try read next..."); - client.try_next().await?; + let r = client.try_next().await?; + println!("Result: {:?}", r); + println!("Try read next..."); - client.try_next().await?; + let r = client.try_next().await?; + println!("Result: {:?}", r); + println!("Try read next..."); - client.try_next().await?; + let r = client.try_next().await?; + println!("Result: {:?}", r); println!("Sleep..."); std::thread::sleep(Duration::from_millis(30000)); diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index 4c060a21b9..901cc8b23c 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -52,6 +52,15 @@ impl PollingTwitchWebsocketClient { } } + pub async fn listen<'t>(&mut self, auth_token: &str, topics: &'t [Topics]) -> AnyhowResult<()> { + match self.client.lock() { + Err(e) => Err(anyhow!("unlock client err: {:?}", e)), + Ok(mut client) => { + client.listen(auth_token, topics).await + } + } + } + /// Start a pinging background thread. /// Per Twitch PubSub docs, /// To keep the server from closing the connection, clients must send a PING command @@ -181,16 +190,6 @@ impl TwitchWebsocketClient { } pub async fn listen<'t>(&mut self, auth_token: &str, topics: &'t [Topics]) -> AnyhowResult<()> { - //// We want to subscribe to moderator actions on channel with id 1234 - //// as if we were a user with id 4321 that is moderator on the channel. - //let chat_mod_actions = pubsub::moderation::ChatModeratorActions { - // user_id: 4321, - // channel_id: 1234, - //}.into_topic(); - - //// Listen to follows as well - //let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); - let mut socket = match self.socket { None => return Err(anyhow!("not connected")), Some(ref mut socket) => socket, From 0018e51eb50b927a50d5e9359a0b02b54aaf63c6 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 21:12:38 -0400 Subject: [PATCH 14/17] attempt access tokens and scopes (failing) --- src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 1c80aa7e57..12ada9848d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,8 @@ pub mod util; const DEFAULT_LOG : &'static str = "debug,rustls=warn,tungstenite=warn"; +const TEST_CHANNEL_ID : u32 = 650154491; // Test channel + fn main() -> AnyhowResult<()> { easyenv::init_all_with_default_logging(Some(DEFAULT_LOG)); @@ -113,6 +115,40 @@ async fn run() -> AnyhowResult<()> { println!("Starting polling thread..."); client.start_ping_thread().await; + //match UserToken::from_existing(reqwest_http_client, token, None, None).await { + // Ok(t) => println!("user_token: {}", t.token().secret()), + // Err(e) => panic!("got error: {}", e), + //} + + let client_id = ClientId::new(&secrets.app_client_id); + let client_secret = ClientSecret::new(&secrets.app_client_secret); + + let http_client = reqwest::Client::new(); + + let scopes = vec![ + Scope::BitsRead, + ]; + + let scopes = Scope::all(); + + info!("Getting access token..."); + + let access_token = twitch_oauth2::AppAccessToken::get_app_access_token( + &http_client, + client_id, + client_secret, + scopes, + ).await?; + + info!("Access token obtained."); + + println!("Access Token Outer: {:?}", access_token); + let token = access_token.token(); + println!("Access Token Inner: {:?}", token.as_str()); + + let bit_topic = pubsub::channel_bits::ChannelBitsEventsV2 { + channel_id: TEST_CHANNEL_ID, + }.into_topic(); // We want to subscribe to moderator actions on channel with id 1234 // as if we were a user with id 4321 that is moderator on the channel. @@ -124,8 +160,13 @@ async fn run() -> AnyhowResult<()> { // Listen to follows as well let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + + println!("Begin LISTEN..."); - client.listen("auth_token", &[chat_mod_actions, follows]).await; + let auth_token = access_token.access_token.as_str(); + let topics = [bit_topic]; + + client.listen(auth_token, &topics).await; println!("Try read next..."); From 63c960a7aa9b7594b23947a4f61cf9e0bf6b53a2 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 23:20:34 -0400 Subject: [PATCH 15/17] user id lookup with helix --- src/main.rs | 183 ++++++++++-------- src/twitch/client.rs | 65 +++++++ src/twitch/mod.rs | 3 +- ...{twitch_client.rs => twitch_client_old.rs} | 0 4 files changed, 169 insertions(+), 82 deletions(-) create mode 100644 src/twitch/client.rs rename src/twitch/{twitch_client.rs => twitch_client_old.rs} (100%) diff --git a/src/main.rs b/src/main.rs index 12ada9848d..43f5e75666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,10 @@ use twitch_api2::helix::channels::GetChannelInformationRequest; use twitch_api2::pubsub::Topic; use twitch_api2::pubsub; use twitch_oauth2::{AppAccessToken, Scope, TwitchToken, tokens::errors::AppAccessTokenError, ClientId, ClientSecret}; +use twitch_oauth2::tokens::UserTokenBuilder; +use twitch_api2::helix::users::GetUsersRequest; +use twitch_api2::types::Nickname; +use crate::twitch::client::TwitchClientWrapper; pub mod twitch; pub mod util; @@ -44,107 +48,58 @@ fn main() -> AnyhowResult<()> { #[tokio::main] async fn run() -> AnyhowResult<()> { - let secrets = TwitchSecrets::from_file("secrets.toml")?; + // ==================== APPLICATION ACCESS ==================== - /*let client_id = ClientId::new(&secrets.app_client_id); + let secrets = TwitchSecrets::from_file("secrets.toml")?; + let client_id = ClientId::new(&secrets.app_client_id); let client_secret = ClientSecret::new(&secrets.app_client_secret); - let client: TwitchClient = TwitchClient::default(); - let token = AppAccessToken::get_app_access_token( - &client, - client_id, - client_secret, - Scope::all(), - ).await?; + info!("Getting app access token..."); - let req = GetChannelInformationRequest::builder() - //.broadcaster_id("27620241") - .broadcaster_id("650154491") - .build(); + //let scopes = Scope::all(); + let scopes = vec![ + Scope::BitsRead, + Scope::UserReadEmail, + ]; - println!( - "{:?}", - &client.helix.req_get(req, &token).await?.data.unwrap().title - );*/ + let mut twitch_client = TwitchClientWrapper::new(client_id.clone(), client_secret.clone()); + twitch_client.request_access_token(scopes).await?; + + let user_id = twitch_client.get_user_id_from_username("echelon").await?; + println!("user id: {:?}", user_id); + + std::thread::sleep(Duration::from_secs(5000)); - // Want this: - // twitch_api2::pubsub::channel_bits::BitsEventData + // ==================== OAUTH FLOW ==================== + let redirect_url = twitch_oauth2::url::Url::parse("http://localhost/test")?; + let mut builder = UserTokenBuilder::new(client_id, client_secret, redirect_url) + .force_verify(true); - /*// We want to subscribe to moderator actions on channel with id 1234 - // as if we were a user with id 4321 that is moderator on the channel. - let chat_mod_actions = pubsub::moderation::ChatModeratorActions { - user_id: 4321, - channel_id: 1234, - } - .into_topic();*/ + builder.add_scope(Scope::BitsRead); - // Listen to follows as well - /*let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); - let topics = [follows]; - // Create the topic command to send to twitch - let command = pubsub::listen_command( - &topics, - "authtoken", - "random nonce string", - )?; + let (url, _csrf_token) = builder.generate_url(); - println!("Pubsub command: {:?}", command);*/ + println!("Go to this page: {}", url); - // Send the message with your favorite websocket client - //send_command(command).unwrap(); - // To parse the websocket messages, use pubsub::Response::parse + //std::thread::sleep(Duration::from_secs(30)); - /*println!("Connected"); - let mut ws_client = TwitchWebsocketClient::new()?; - ws_client.connect().await?; - - ws_client.send_ping().await?; - - println!("success ping");*/ + // ==================== PUBSUB SUBSCRIPTION AND MAIN LOOP ==================== let mut client = PollingTwitchWebsocketClient::new()?; - println!("Connecting..."); + println!("Connecting PubSub..."); client.connect().await?; + println!("Connected"); println!("Starting polling thread..."); client.start_ping_thread().await; - //match UserToken::from_existing(reqwest_http_client, token, None, None).await { - // Ok(t) => println!("user_token: {}", t.token().secret()), - // Err(e) => panic!("got error: {}", e), - //} - let client_id = ClientId::new(&secrets.app_client_id); - let client_secret = ClientSecret::new(&secrets.app_client_secret); - - let http_client = reqwest::Client::new(); - - let scopes = vec![ - Scope::BitsRead, - ]; - - let scopes = Scope::all(); - - info!("Getting access token..."); - - let access_token = twitch_oauth2::AppAccessToken::get_app_access_token( - &http_client, - client_id, - client_secret, - scopes, - ).await?; - - info!("Access token obtained."); - - println!("Access Token Outer: {:?}", access_token); - let token = access_token.token(); - println!("Access Token Inner: {:?}", token.as_str()); let bit_topic = pubsub::channel_bits::ChannelBitsEventsV2 { channel_id: TEST_CHANNEL_ID, @@ -160,15 +115,14 @@ async fn run() -> AnyhowResult<()> { // Listen to follows as well let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); - - println!("Begin LISTEN..."); - let auth_token = access_token.access_token.as_str(); + //let auth_token = access_token.access_token.as_str(); + //let auth_token = ""; + let auth_token = ""; let topics = [bit_topic]; client.listen(auth_token, &topics).await; - println!("Try read next..."); let r = client.try_next().await?; println!("Result: {:?}", r); @@ -199,4 +153,71 @@ async fn get_case_count() -> TungsteniteResult { let msg = socket.next().await.expect("Can't fetch case count")?; socket.close(None).await?; Ok(msg.into_text()?.parse::().expect("Can't parse case count")) -} \ No newline at end of file +} + + +/*let client_id = ClientId::new(&secrets.app_client_id); +let client_secret = ClientSecret::new(&secrets.app_client_secret); + +let client: TwitchClient = TwitchClient::default(); +let token = AppAccessToken::get_app_access_token( + &client, + client_id, + client_secret, + Scope::all(), +).await?; + +let req = GetChannelInformationRequest::builder() + //.broadcaster_id("27620241") + .broadcaster_id("650154491") + .build(); + +println!( + "{:?}", + &client.helix.req_get(req, &token).await?.data.unwrap().title +);*/ + + +// Want this: +// twitch_api2::pubsub::channel_bits::BitsEventData + + + +/*// We want to subscribe to moderator actions on channel with id 1234 +// as if we were a user with id 4321 that is moderator on the channel. +let chat_mod_actions = pubsub::moderation::ChatModeratorActions { + user_id: 4321, + channel_id: 1234, +} + .into_topic();*/ + +// Listen to follows as well +/*let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); +let topics = [follows]; +// Create the topic command to send to twitch +let command = pubsub::listen_command( + &topics, + "authtoken", + "random nonce string", +)?; + +println!("Pubsub command: {:?}", command);*/ + +// Send the message with your favorite websocket client +//send_command(command).unwrap(); +// To parse the websocket messages, use pubsub::Response::parse + + +/*println!("Connected"); +let mut ws_client = TwitchWebsocketClient::new()?; +ws_client.connect().await?; + +ws_client.send_ping().await?; + +println!("success ping");*/ + +//match UserToken::from_existing(reqwest_http_client, token, None, None).await { +// Ok(t) => println!("user_token: {}", t.token().secret()), +// Err(e) => panic!("got error: {}", e), +//} + diff --git a/src/twitch/client.rs b/src/twitch/client.rs new file mode 100644 index 0000000000..26c40447b4 --- /dev/null +++ b/src/twitch/client.rs @@ -0,0 +1,65 @@ +//! The Helix client is independent of PubSub +//! This just wraps `twitch_api2` for more convenience. + +use crate::util::anyhow_result::AnyhowResult; +use twitch_api2::helix::users::GetUsersRequest; +use anyhow::anyhow; +use twitch_api2::TwitchClient; +use twitch_oauth2::{ClientSecret, ClientId, AccessToken, AppAccessToken, Scope}; +use twitch_api2::types::{Nickname, UserId}; + +pub struct TwitchClientWrapper<'a> { + client_id: ClientId, + client_secret: ClientSecret, + twitch_client: TwitchClient<'a, reqwest::Client>, + access_token: Option, +} + +impl <'a> TwitchClientWrapper <'a> { + pub fn new(client_id: ClientId, client_secret: ClientSecret) -> Self { + Self { + client_id, + client_secret, + twitch_client: TwitchClient::default(), + access_token: None, + } + } + + pub async fn request_access_token(&mut self, scopes: Vec) -> AnyhowResult<()> { + self.get_access_token(scopes).await?; + Ok(()) + } + + async fn get_access_token(&mut self, scopes: Vec) -> AnyhowResult<()> { + let mut http_client = reqwest::Client::new(); + let access_token = twitch_oauth2::AppAccessToken::get_app_access_token( + &http_client, + self.client_id.clone(), + self.client_secret.clone(), + scopes, + ).await?; + + self.access_token = Some(access_token); + Ok(()) + } + + /// We frequently need to look up IDs from usernames. + pub async fn get_user_id_from_username(&self, username: &str) -> AnyhowResult { + let access_token = match self.access_token { + None => return Err(anyhow!("no access token has been granted")), + Some(ref token) => token, + }; + + let req = GetUsersRequest::builder() + .login(vec![Nickname::new(username)]) + .build(); + + let response = &self.twitch_client.helix.req_get(req, access_token) + .await?; + + match response.data.get(0) { + None => Err(anyhow!("No such user: `{}`.", username)), + Some(user) => Ok(user.id.as_str().parse::()?), + } + } +} diff --git a/src/twitch/mod.rs b/src/twitch/mod.rs index 180c2ab938..6f58dedfeb 100644 --- a/src/twitch/mod.rs +++ b/src/twitch/mod.rs @@ -1,3 +1,4 @@ +pub mod client; pub mod secrets; -pub mod twitch_client; +pub mod twitch_client_old; pub mod websocket_client; \ No newline at end of file diff --git a/src/twitch/twitch_client.rs b/src/twitch/twitch_client_old.rs similarity index 100% rename from src/twitch/twitch_client.rs rename to src/twitch/twitch_client_old.rs From 6db56b89a425a02bfb7cbcabed8c710a523a6a46 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 26 Sep 2021 23:49:38 -0400 Subject: [PATCH 16/17] no progress --- src/main.rs | 55 ++++++++++++++++++++-------------- src/twitch/websocket_client.rs | 19 +++++++----- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/main.rs b/src/main.rs index 43f5e75666..9a97bb1edd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,26 +65,31 @@ async fn run() -> AnyhowResult<()> { let mut twitch_client = TwitchClientWrapper::new(client_id.clone(), client_secret.clone()); twitch_client.request_access_token(scopes).await?; - let user_id = twitch_client.get_user_id_from_username("echelon").await?; - println!("user id: {:?}", user_id); + info!("Getting user id ..."); - std::thread::sleep(Duration::from_secs(5000)); + let user_id = twitch_client.get_user_id_from_username("testytest512").await?; + + info!("User ID: {}", user_id); + + //std::thread::sleep(Duration::from_secs(5000)); // ==================== OAUTH FLOW ==================== + println!("Oauth flow..."); let redirect_url = twitch_oauth2::url::Url::parse("http://localhost/test")?; let mut builder = UserTokenBuilder::new(client_id, client_secret, redirect_url) + .set_scopes(Scope::all()) .force_verify(true); - builder.add_scope(Scope::BitsRead); + //builder.add_scope(Scope::BitsRead); let (url, _csrf_token) = builder.generate_url(); println!("Go to this page: {}", url); - //std::thread::sleep(Duration::from_secs(30)); + //std::thread::sleep(Duration::from_secs(3000)); // ==================== PUBSUB SUBSCRIPTION AND MAIN LOOP ==================== @@ -96,36 +101,29 @@ async fn run() -> AnyhowResult<()> { println!("Connected"); - println!("Starting polling thread..."); - client.start_ping_thread().await; + //println!("Starting polling thread..."); + //client.start_ping_thread().await; + println!("Sending PING..."); + + client.send_ping().await?; + + println!("Try read next..."); + let r = client.try_next().await?; + println!("Result: {:?}", r); let bit_topic = pubsub::channel_bits::ChannelBitsEventsV2 { channel_id: TEST_CHANNEL_ID, }.into_topic(); - // We want to subscribe to moderator actions on channel with id 1234 - // as if we were a user with id 4321 that is moderator on the channel. - let chat_mod_actions = pubsub::moderation::ChatModeratorActions { - user_id: 4321, - channel_id: 1234, - }.into_topic(); - - // Listen to follows as well - let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); - println!("Begin LISTEN..."); //let auth_token = access_token.access_token.as_str(); - //let auth_token = ""; let auth_token = ""; let topics = [bit_topic]; - client.listen(auth_token, &topics).await; + client.listen(auth_token, &topics).await?; - println!("Try read next..."); - let r = client.try_next().await?; - println!("Result: {:?}", r); println!("Try read next..."); let r = client.try_next().await?; @@ -216,6 +214,19 @@ ws_client.send_ping().await?; println!("success ping");*/ +/* + + // We want to subscribe to moderator actions on channel with id 1234 + // as if we were a user with id 4321 that is moderator on the channel. + let chat_mod_actions = pubsub::moderation::ChatModeratorActions { + user_id: 4321, + channel_id: 1234, + }.into_topic(); + + // Listen to follows as well + let follows = pubsub::following::Following { channel_id: 1234 }.into_topic(); + */ + //match UserToken::from_existing(reqwest_http_client, token, None, None).await { // Ok(t) => println!("user_token: {}", t.token().secret()), // Err(e) => panic!("got error: {}", e), diff --git a/src/twitch/websocket_client.rs b/src/twitch/websocket_client.rs index 901cc8b23c..486bd68800 100644 --- a/src/twitch/websocket_client.rs +++ b/src/twitch/websocket_client.rs @@ -36,13 +36,6 @@ impl PollingTwitchWebsocketClient { }) } - async fn connect_keepalive(&self) -> AnyhowResult<()> { - - //tokio::spawn(self.polling_thread); - - Ok(()) - } - pub async fn connect(&mut self) -> AnyhowResult<()> { match self.client.lock() { Err(e) => Err(anyhow!("unlock client err: {:?}", e)), @@ -61,6 +54,15 @@ impl PollingTwitchWebsocketClient { } } + pub async fn send_ping(&mut self) -> AnyhowResult<()> { + match self.client.lock() { + Err(e) => Err(anyhow!("unlock client err: {:?}", e)), + Ok(mut client) => { + client.send_ping().await + } + } + } + /// Start a pinging background thread. /// Per Twitch PubSub docs, /// To keep the server from closing the connection, clients must send a PING command @@ -72,7 +74,8 @@ impl PollingTwitchWebsocketClient { loop { match client.lock() { Ok(mut c) => { - c.send_ping(); + // TODO: Future isn't Send + //let _r = c.send_ping().await; // TODO: Need to read from all call sites due to ordering. //c.read_response(); } From 12543a1fdd687b1b6b9c2cf07ceb2b484f77f0d5 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 27 Sep 2021 00:32:30 -0400 Subject: [PATCH 17/17] oauth flow works, ugh --- Cargo.lock | 11 ++++++++ Cargo.toml | 3 +++ src/main.rs | 64 ++++++++++++++++++++++++++++++++++++++------ src/twitch/client.rs | 4 +-- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c3628861a..959c7d0729 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "rpassword" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rustls" version = "0.19.1" @@ -1302,6 +1312,7 @@ dependencies = [ "log", "rand", "reqwest", + "rpassword", "rustls 0.20.0-beta2", "serde", "serde_derive", diff --git a/Cargo.toml b/Cargo.toml index af96cc2d8f..11846476ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,5 +48,8 @@ easyenv = "0.0.6" log = "0.4.14" rand = "0.8.4" +# Temporary +rpassword = "5.0.1" + #[build-dependencies] diff --git a/src/main.rs b/src/main.rs index 9a97bb1edd..0a41932192 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,8 @@ async fn run() -> AnyhowResult<()> { info!("Getting user id ..."); - let user_id = twitch_client.get_user_id_from_username("testytest512").await?; + //let user_id = twitch_client.get_user_id_from_username("testytest512").await?; + let user_id = twitch_client.get_user_id_from_username("vocodes").await?; info!("User ID: {}", user_id); @@ -112,23 +113,70 @@ async fn run() -> AnyhowResult<()> { let r = client.try_next().await?; println!("Result: {:?}", r); + let input = rpassword::prompt_password_stdout( + "Paste in the resulting adress after authenticating (input hidden): ", + )?; - let bit_topic = pubsub::channel_bits::ChannelBitsEventsV2 { - channel_id: TEST_CHANNEL_ID, - }.into_topic(); + + let u = twitch_oauth2::url::Url::parse(&input)?; + + let map: std::collections::HashMap<_, _> = u.query_pairs().collect(); + + let user_token = match (map.get("state"), map.get("code")) { + (Some(state), Some(code)) => { + let token = builder + .get_user_token( + &reqwest::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build()?, + state, + code, + ) + .await?; + println!("Got token: {:?}", token); + + token + } + _ => match (map.get("error"), map.get("error_description")) { + (std::option::Option::Some(error), std::option::Option::Some(error_description)) => { + anyhow::bail!( + "twitch errored with error: {} - {}", + error, + error_description + ); + } + _ => anyhow::bail!("invalid url passed"), + }, + }; + + let auth_token = user_token.access_token.as_str(); println!("Begin LISTEN..."); - //let auth_token = access_token.access_token.as_str(); - let auth_token = ""; + + let bit_topic = pubsub::channel_bits::ChannelBitsEventsV2 { + channel_id: user_id, + }.into_topic(); + let topics = [bit_topic]; - client.listen(auth_token, &topics).await?; - + client.listen(&auth_token, &topics).await?; println!("Try read next..."); let r = client.try_next().await?; println!("Result: {:?}", r); + /* + Result: Some(Message { data: ChannelBitsEventsV2 { topic: ChannelBitsEventsV2 { + channel_id: 652567283 }, reply: BitsEvent { data: BitsEventData { badge_entitlement: + Some(BadgeEntitlement { new_version: 100, previous_version: 1 }), bits_used: 100, + channel_id: "652567283", channel_name: "vocodes", chat_message: "Cheer100 testing the cheer", + context: Cheer, is_anonymous: false, time: "2021-09-27T04:30:53.627717085Z", + total_bits_used: 101, user_id: "650154491", user_name: "testytest512" }, + message_id: "793f745e-8f3e-5f71-bc41-4808c4b49a53", version: "1.0", is_anonymous: false } } }) + */ + + + println!("Try read next..."); let r = client.try_next().await?; println!("Result: {:?}", r); diff --git a/src/twitch/client.rs b/src/twitch/client.rs index 26c40447b4..072d0ead60 100644 --- a/src/twitch/client.rs +++ b/src/twitch/client.rs @@ -44,7 +44,7 @@ impl <'a> TwitchClientWrapper <'a> { } /// We frequently need to look up IDs from usernames. - pub async fn get_user_id_from_username(&self, username: &str) -> AnyhowResult { + pub async fn get_user_id_from_username(&self, username: &str) -> AnyhowResult { let access_token = match self.access_token { None => return Err(anyhow!("no access token has been granted")), Some(ref token) => token, @@ -59,7 +59,7 @@ impl <'a> TwitchClientWrapper <'a> { match response.data.get(0) { None => Err(anyhow!("No such user: `{}`.", username)), - Some(user) => Ok(user.id.as_str().parse::()?), + Some(user) => Ok(user.id.as_str().parse::()?), } } }