Twitch chat integration works

This commit is contained in:
Brandon Thomas committed 2021-02-25 04:08:15 -05:00
1 parent cdd2cd1ac4
commit 173446ccce
6 files changed
+233 -1309

No files matched your search

Generated
+14 -1303
View File
File diff suppressed because it is too large. Load diff
+13 -6
View File
@@ -8,21 +8,28 @@ authors = [
]
[[bin]]
name = "twitch-gateway"
path = "src/main.rs"
name = "twitch-helix-demo"
path = "src/main_helix_demo.rs"
[[bin]]
name = "twitch-chat-gateway"
path = "src/main_chat_gateway.rs"
[dependencies]
anyhow = "1.0.38"
rustls = "0.18"
serde = "1.0.123"
serde_derive = "1.0.123"
smol = "1.2.5"
#smol = "1.2.5"
tokio = { version = "0.2", features = ["full"] }
#tokio = { version = "1.2.0", 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"
#twitch_api2 = "0.4.1"
#twitch_oauth2 = "0.4.1"
# Fairly mature and robust Twitch chat library
twitchchat = { version = "0.14.8", features = ["async", "async-tls", "smol"] }
#twitchchat = { version = "0.14.8", features = ["async", "async-tls", "smol"] }
twitchchat = { version = "0.14.8", features = ["tokio", "tokio-util", "async", "webpki-roots", "tokio-rustls"] }
+2
View File
@@ -9,6 +9,8 @@ pub struct Secrets {
pub application_client_id: String,
/// Application secret (generated)
pub application_client_secret: String,
/// OAuth access token generated with URL handshake
pub oauth_access_token: String,
}
impl Secrets {
+99
View File
@@ -0,0 +1,99 @@
#![allow(dead_code)]
use anyhow::Context as _;
use twitchchat::{messages, AsyncRunner, Status, UserConfig};
use crate::filesystem::secrets::Secrets;
// some helpers for the demo
// fn get_env_var(key: &str) -> anyhow::Result<String> {
// std::env::var(key).with_context(|| format!("please set `{}`", key))
// }
pub fn get_user_config(secrets: &Secrets) -> anyhow::Result<twitchchat::UserConfig> {
let name = "testytest512".to_string(); //get_env_var("TWITCH_NAME")?;
let token = secrets.oauth_access_token.to_string();
// you need a `UserConfig` to connect to Twitch
let config = UserConfig::builder()
// the name of the associated twitch account
.name(name)
// and the provided OAuth token
.token(token)
// and enable all of the advanced message signaling from Twitch
.enable_all_capabilities()
.build()?;
Ok(config)
}
// channels can be either in the form of '#museun' or 'museun'. the crate will internally add the missing #
pub fn channels_to_join() -> anyhow::Result<Vec<String>> {
let channels = "#vocodes,#testytest512,#ech310n" //get_env_var("TWITCH_CHANNEL")?
.split(',')
.map(ToString::to_string)
.collect();
Ok(channels)
}
// a 'main loop'
pub async fn main_loop(mut runner: AsyncRunner) -> anyhow::Result<()> {
loop {
match runner.next_message().await? {
// this is the parsed message -- across all channels (and notifications from Twitch)
Status::Message(msg) => {
println!("Handle message time!");
handle_message(msg).await;
}
// you signaled a quit
Status::Quit => {
println!("we signaled we wanted to quit");
break;
}
// the connection closed normally
Status::Eof => {
println!("we got a 'normal' eof");
break;
}
}
}
Ok(())
}
// you can generally ignore the lifetime for these types.
async fn handle_message(msg: messages::Commands<'_>) {
use messages::Commands::*;
// All sorts of messages
match msg {
// This is the one users send to channels
Privmsg(msg) => println!("[{}] {}: {}", msg.channel(), msg.name(), msg.data()),
// This one is special, if twitch adds any new message
// types, this will catch it until future releases of
// this crate add them.
Raw(_) => {}
// These happen when you initially connect
IrcReady(_) => {}
Ready(_) => {}
Cap(_) => {}
// and a bunch of other messages you may be interested in
ClearChat(_) => {}
ClearMsg(_) => {}
GlobalUserState(_) => {}
HostTarget(_) => {}
Join(_) => {}
Notice(_) => {}
Part(_) => {}
Ping(_) => {}
Pong(_) => {}
Reconnect(_) => {}
RoomState(_) => {}
UserNotice(_) => {}
UserState(_) => {}
Whisper(_) => {}
_ => {}
}
}
+105
View File
@@ -0,0 +1,105 @@
// NOTE: this demo requires `--features="tokio/full tokio-util"`.
use twitchchat::{
commands, connector, messages,
runner::{AsyncRunner, Status},
UserConfig,
};
// this is a helper module to reduce code deduplication
mod include;
use crate::include::{channels_to_join, get_user_config, main_loop};
mod filesystem;
async fn connect(user_config: &UserConfig, channels: &[String]) -> anyhow::Result<AsyncRunner> {
// create a connector using ``tokio``, this connects to Twitch.
// you can provide a different address with `custom`
let connector = connector::tokio::Connector::twitch()?;
println!("we're connecting!");
// create a new runner. this is a provided async 'main loop'
// this method will block until you're ready
let mut runner = AsyncRunner::connect(connector, user_config).await?;
println!("..and we're connected");
// and the identity Twitch gave you
println!("our identity: {:#?}", runner.identity);
for channel in channels {
// the runner itself has 'blocking' join/part to ensure you join/leave a channel.
// these two methods return whether the connection was closed early.
// we'll ignore it for this demo
println!("attempting to join '{}'", channel);
let _ = runner.join(&channel).await?;
println!("joined '{}'!", channel);
}
Ok(runner)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// create a user configuration
let user_config = get_user_config()?;
// get some channels to join from the environment
let channels = channels_to_join()?;
// connect and join the provided channels
let runner = connect(&user_config, &channels).await?;
// you can get a handle to shutdown the runner
let quit_handle = runner.quit_handle();
// you can get a clonable writer
let mut writer = runner.writer();
// spawn something off in the background that'll exit in 10 seconds
tokio::spawn({
let mut writer = writer.clone();
let channels = channels.clone();
async move {
for channel in &channels {
println!("> SENDING HELLO");
let cmd = commands::privmsg(&channel, "hello! testing from Rust");
writer.encode(cmd).await.unwrap();
println!("> SENDING HELLO #2");
let cmd = commands::me(&channel, "hello! testing from Rust");
writer.encode(cmd).await.unwrap();
}
println!("in 100 seconds we'll exit");
tokio::time::delay_for(std::time::Duration::from_secs(100)).await;
// send one final message to all channels
for channel in &channels {
let cmd = commands::privmsg(&channel, "goodbye, world");
writer.encode(cmd).await.unwrap();
}
println!("sending quit signal");
quit_handle.notify().await;
}
});
// you can encode all sorts of 'commands'
for channel in &channels {
println!("> SENDING HELLO WORLD");
writer
.encode(commands::privmsg(channel, "hello world!"))
.await?;
}
for channel in &channels {
println!("> SENDING HELLO AGAIN");
writer
.encode(commands::privmsg(channel, "hello world again!"))
.await?;
}
println!("starting main loop");
// your 'main loop'. you'll just call next_message() until you're done
main_loop(runner).await
}
File renamed without changes.