use std::{
pin::Pin,
task::{Context, Poll}
};
use futures_util::{Stream, StreamExt};
use irc::{
client::{Client, ClientStream, prelude::Config},
proto::Capability
};
pub mod identity;
pub use self::identity::{Anonymous, Authenticated, TwitchIdentity};
mod event;
pub use self::event::{ChatEvent, MessageSegment, User, UserRole};
const TWITCH_SECURE_IRC: (&str, u16) = ("irc.chat.twitch.tv", 6697);
const TWITCH_CAPABILITY_TAGS: Capability = Capability::Custom("twitch.tv/tags");
const TWITCH_CAPABILITY_MEMBERSHIP: Capability = Capability::Custom("twitch.tv/membership");
const TWITCH_CAPABILITY_COMMANDS: Capability = Capability::Custom("twitch.tv/commands");
#[derive(Debug)]
pub struct Chat {
stream: ClientStream
}
impl Chat {
pub async fn new(channel: impl AsRef<str>, auth: impl TwitchIdentity) -> irc::error::Result<Self> {
let (username, password) = auth.as_identity();
let mut client = Client::from_config(Config {
server: Some(TWITCH_SECURE_IRC.0.to_string()),
port: Some(TWITCH_SECURE_IRC.1),
nickname: Some(username.to_string()),
password: password.map(|c| format!("oauth:{c}")),
channels: vec![format!("#{}", channel.as_ref())],
..Default::default()
})
.await?;
client.send_cap_req(&[TWITCH_CAPABILITY_COMMANDS, TWITCH_CAPABILITY_MEMBERSHIP, TWITCH_CAPABILITY_TAGS])?;
client.identify()?;
Ok(Self { stream: client.stream()? })
}
}
impl Stream for Chat {
type Item = irc::error::Result<ChatEvent>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let next = self.stream.poll_next_unpin(cx);
match next {
Poll::Ready(Some(Ok(r))) => match self::event::to_chat_event(r) {
Some(ev) => Poll::Ready(Some(Ok(ev))),
None => {
cx.waker().wake_by_ref();
Poll::Pending
}
},
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending
}
}
}