diff --git a/Cargo.lock b/Cargo.lock index fc52e1e..4130340 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,6 +410,7 @@ dependencies = [ "async-trait", "chrono", "futures", + "irc", "once_cell", "reqwest 0.12.5", "rustls 0.23.12", @@ -432,6 +433,70 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -936,6 +1001,43 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "irc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b59a23035df48e37386a6d03dc084fbab6ab835134bb99d081954199d7dd20df" +dependencies = [ + "chrono", + "encoding", + "futures-util", + "irc-proto", + "log", + "native-tls", + "parking_lot", + "pin-project", + "serde", + "serde_derive", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-stream", + "tokio-util", + "toml", +] + +[[package]] +name = "irc-proto" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5c7fb0c03989e8b31de1c91d0f625057887677e387448e7fc10a6afd4d9e1" +dependencies = [ + "bytes", + "encoding", + "thiserror", + "tokio", + "tokio-util", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1743,6 +1845,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2076,6 +2187,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -2121,6 +2243,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -2653,6 +2809,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" diff --git a/Cargo.toml b/Cargo.toml index 7141bcf..b3f2fd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ anyhow = "1.0.86" async-trait = "0.1.81" tokio-tungstenite = { version = "0.23.1", features = ["rustls-tls-native-roots"] } rustls = { version = "0.23.12", features = ["ring"] } +irc = "1.0.0" diff --git a/src/twitch_handler/chat.rs b/src/twitch_handler/chat.rs new file mode 100644 index 0000000..59bbb77 --- /dev/null +++ b/src/twitch_handler/chat.rs @@ -0,0 +1,79 @@ +use anyhow::bail; +use anyhow::Result; + +use irc::client::data::Config as IrcConfig; +use irc::client::prelude::Capability as IrcCap; +use irc::client::Client as IrcClient; +use irc::client::ClientStream as IrcStream; + +use super::auth; +use super::helix; + + +impl helix::Client { + pub async fn connect_chat(&mut self, channels: Vec) -> Result<(IrcClient, IrcStream)> { + match self.validate_token().await { + Err(e) => { + println!("{e:?}"); + bail!("Invalid refresh token or no internet"); + } + _ => {} + }; + + let channels = channels + .into_iter() + .map(|c| { + format!( + "{0}{1}", + if c.starts_with("#") { "" } else { "#" }, + c.to_lowercase() + ) + }) + .collect(); + + let config = IrcConfig { + server: Some("irc.chat.twitch.tv".to_owned()), + port: Some(6697), + use_tls: Some(true), + nickname: Some(self.get_token_user_login().await?.to_lowercase().to_owned()), + password: Some(format!("oauth:{0}", self.token.access_token)), + channels: channels, + ..Default::default() + }; + + let mut client = match IrcClient::from_config(config).await { + Ok(v) => v, + Err(e) => { + println!("{e:?}"); + bail!("IrcClient::from_config failed"); + } + }; + match client.send_cap_req(&[ + IrcCap::Custom("twitch.tv/tags"), + IrcCap::Custom("twitch.tv/commands"), + ]) { + Err(e) => { + println!("{e:?}"); + bail!("IrcClient.send_cap_req failed"); + } + _ => {} + }; + match client.identify() { + Err(e) => { + println!("{e:?}"); + bail!("IrcClient.identify failed"); + } + _ => {} + }; + + let stream = match client.stream() { + Ok(v) => v, + Err(e) => { + println!("{e:?}"); + bail!("IrcClient.stream failed"); + } + }; + + Ok((client, stream)) + } +} diff --git a/src/twitch_handler/mod.rs b/src/twitch_handler/mod.rs index ad8495d..e0e14b2 100644 --- a/src/twitch_handler/mod.rs +++ b/src/twitch_handler/mod.rs @@ -1,6 +1,7 @@ pub mod eventsub; pub mod helix; pub mod auth; +pub mod chat; use chrono::{DateTime, Utc}; use futures::StreamExt; @@ -63,7 +64,7 @@ pub async fn notify_stream_online(title: String, game: String) { impl TwitchBot { - pub async fn start() { + pub async fn start_twitch_eventsub() { println!("Starting Twitch bot..."); let token_storage = TokenStorage { @@ -103,8 +104,11 @@ impl TwitchBot { ], config::CONFIG.twitch_channel_id.clone() ).await.unwrap(); - println!("Connected to Twitch EventSub..."); + + let (_chat_client, mut chat_stream) = client.connect_chat(vec![config::CONFIG.twitch_channel_id.clone()]).await.unwrap(); + println!("Connected to Twitch Chat..."); + client.validate_token().await.unwrap(); loop { @@ -160,6 +164,15 @@ impl TwitchBot { } } + if let Some(event) = chat_stream.next().await { + match event { + Ok(v) => { + println!("{:?}", v); + }, + Err(_) => {}, + } + } + client.validate_token().await.unwrap(); } }