use twitch_api2::{ TwitchClient, types::UserId, twitch_oauth2::UserToken, }; use irc::client::prelude::Message; use irc::proto::{Command, Response}; use crate::app::State; use crate::app::config::CommandExecutor; use crate::app::rhai_tools::ExecutorState; use std::sync::Arc; use tokio_tungstenite::tungstenite::Message as WsMessage; pub struct Twitch { pub client: TwitchClient<'static, reqwest::Client>, pub bot_token: UserToken, pub user_token: UserToken, } pub async fn handle_irc_event(state: Arc, event: Message) -> anyhow::Result<()> { let channel_name = format!("#{}", state.channel_name); match &event.command { Command::Response(resp, _) if *resp == Response::RPL_WELCOME => { state.irc.send_join(&channel_name)?; }, // FIXME: do correct checking here Command::PRIVMSG(channel, message) if *channel == channel_name => { on_privmsg(state, &event, &message).await?; }, _ => { // eprintln!("{:#?}", c); }, } Ok(()) } async fn on_privmsg(state: Arc, event: &Message, message: &str) -> anyhow::Result<()> { let initiator = event.source_nickname() .ok_or_else(|| anyhow::anyhow!("missing source"))? .to_string(); let initiator_id = event.tags .as_ref() .ok_or_else(|| anyhow::anyhow!("missing tags"))? .iter() .find(|tag| tag.0 == "user-id") .and_then(|tag| tag.1.clone()) .ok_or_else(|| anyhow::anyhow!("missing user id"))?; let words: Vec<&str> = message.split(' ').collect(); let command_name = words[0]; let args = words[1..].iter() .map(ToString::to_string) .map(rhai::Dynamic::from) .collect(); let command = state.config.read() .await .commands .iter().find(|command| command.name == command_name || command.aliases.iter().any(|alias| alias == command_name)) .map(Clone::clone); let command = match command { Some(c) => c, None => return Ok(()) }; match &command.executor { CommandExecutor::Text(t) => { state.irc_queue.send(t.to_string()).ok(); }, CommandExecutor::Rhai(t) => { let fn_state = ExecutorState { twitch: Arc::clone(&state.twitch), initiator_id: UserId::new(initiator_id), initiator, args, runtime: state.runtime.clone(), }; crate::app::run_rhai(Arc::clone(&state), t, fn_state); }, } Ok(()) } use twitch_api2::pubsub::{ Response as PubSubResponse, TopicData, channel_points::ChannelPointsChannelV1Reply, }; pub async fn handle_pubsub(state: Arc, event: WsMessage) -> anyhow::Result<()> { let json = match event { WsMessage::Text(json) => json, _ => return Ok(()), }; let response = twitch_api2::pubsub::Response::parse(&json)?; let reply = match response { PubSubResponse::Message { data: TopicData::ChannelPointsChannelV1 { reply, .. } } => reply, _ => return Ok(()), }; let redemption = match *reply { ChannelPointsChannelV1Reply::RewardRedeemed { redemption, .. } => redemption, _ => return Ok(()), }; let action = match state.config.read().await.redemptions.iter().find(|re| re.twitch_id == redemption.reward.id).map(Clone::clone) { Some(a) => a, None => return Ok(()), }; let args = redemption.user_input .map(|input| input.split(' ').map(ToOwned::to_owned).map(rhai::Dynamic::from).collect()) .unwrap_or_default(); let fn_state = ExecutorState { twitch: Arc::clone(&state.twitch), initiator: redemption.user.login.to_string(), initiator_id: redemption.user.id, args, runtime: state.runtime.clone(), }; crate::app::run_rhai(Arc::clone(&state), &action.rhai, fn_state); Ok(()) }