use crate::{color::Color, IntoChannel};
use std::io::Write;
type Result = std::result::Result<(), crate::Error>;
struct ByteWriter<'a, W: Write> {
inner: &'a mut W,
}
impl<'a, W: Write> ByteWriter<'a, W> {
fn new(inner: &'a mut W) -> Self {
Self { inner }
}
fn jtv_command(self, parts: &[&dyn AsRef<str>]) -> Result {
self.inner.write_all(b"PRIVMSG jtv :")?;
self.parts(parts)
}
fn command(self, channel: &crate::Channel, parts: &[&dyn AsRef<str>]) -> Result {
self.inner.write_all(b"PRIVMSG ")?;
self.inner.write_all(channel.as_ref().as_bytes())?;
self.inner.write_all(b" :")?;
self.parts(parts)
}
fn parts(self, parts: &[&dyn AsRef<str>]) -> Result {
for (i, part) in parts.iter().enumerate() {
if i > 0 {
self.inner.write_all(b" ")?;
}
self.inner.write_all(part.as_ref().as_bytes())?;
}
self.end()
}
fn parts_term(self, parts: &[&dyn AsRef<str>]) -> Result {
for part in parts.iter() {
self.inner.write_all(part.as_ref().as_bytes())?;
}
self.end()
}
fn write_bytes(self, data: impl AsRef<[u8]>) -> Result {
self.inner.write_all(data.as_ref())?;
self.end()
}
fn end(self) -> Result {
self.inner.write_all(b"\r\n")?;
self.inner.flush()?;
Ok(())
}
}
pub struct Encoder<W> {
pub(crate) writer: W,
}
impl<W: Write> Encoder<W> {
pub fn into_inner(self) -> W {
self.writer
}
pub fn inner_mut(&mut self) -> &mut W {
&mut self.writer
}
pub fn inner(&self) -> &W {
&self.writer
}
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl<W: Write> Encoder<W> {
pub fn ban<'a>(
&mut self,
channel: impl IntoChannel,
username: &str,
reason: impl Into<Option<&'a str>>,
) -> Result {
let channel = channel.into_channel()?;
let writer = ByteWriter::new(&mut self.writer);
match reason.into() {
Some(reason) => writer.command(&channel, &[&"/ban", &username, &reason]),
None => writer.command(&channel, &[&"/ban", &username]),
}
}
pub fn clear(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/clear"])
}
pub fn color(&mut self, color: Color) -> Result {
ByteWriter::new(&mut self.writer).jtv_command(&[&"/color", &color.to_string()])
}
pub fn command(&mut self, channel: impl IntoChannel, data: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&data])
}
pub fn jtv_command(&mut self, data: &str) -> Result {
ByteWriter::new(&mut self.writer).jtv_command(&[&data])
}
pub fn commercial(
&mut self,
channel: impl IntoChannel,
length: impl Into<Option<usize>>,
) -> Result {
let channel = channel.into_channel()?;
let writer = ByteWriter::new(&mut self.writer);
match length.into() {
Some(length) => writer.command(&channel, &[&"/commercial", &length.to_string()]),
None => writer.command(&channel, &[&"/commercial"]),
}
}
pub fn disconnect(&mut self) -> Result {
ByteWriter::new(&mut self.writer).jtv_command(&[&"/disconnect"])
}
pub fn emote_only(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/emoteonly"])
}
pub fn emote_only_off(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/emoteonlyoff"])
}
pub fn followers(&mut self, channel: impl IntoChannel, duration: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/followers", &duration])
}
pub fn followers_off(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/followersoff"])
}
pub fn give_mod(&mut self, channel: impl IntoChannel, username: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/mod", &username])
}
pub fn help(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/help"])
}
pub fn host(&mut self, source: impl IntoChannel, target: impl IntoChannel) -> Result {
let source = source.into_channel()?;
let target = target.into_channel()?;
ByteWriter::new(&mut self.writer).command(&source, &[&"/host", &target])
}
pub fn join(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).parts(&[&"JOIN", &channel])
}
pub fn marker<'a>(
&mut self,
channel: impl IntoChannel,
comment: impl Into<Option<&'a str>>,
) -> Result {
let channel = channel.into_channel()?;
let writer = ByteWriter::new(&mut self.writer);
match comment.into() {
None => {
writer.command(&channel, &[&"/marker"])
}
Some(marker) if marker.len() <= 140 => writer.command(&channel, &[&"/marker", &marker]),
Some(marker) => writer.command(&channel, &[&"/marker", &&marker[..140]]),
}
}
pub fn me(&mut self, channel: impl IntoChannel, message: impl AsRef<str>) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/me", &message])
}
pub fn mods(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/mods"])
}
pub fn part(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).parts(&[&"PART", &channel])
}
pub fn ping(&mut self, token: impl AsRef<str>) -> Result {
ByteWriter::new(&mut self.writer).parts(&[&"PING", &token])
}
pub fn pong(&mut self, token: impl AsRef<str>) -> Result {
ByteWriter::new(&mut self.writer).parts_term(&[&"PONG", &" :", &token])
}
pub fn privmsg(&mut self, channel: impl IntoChannel, data: impl AsRef<str>) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).parts_term(&[&"PRIVMSG ", &channel, &" :", &data])
}
pub fn r9k_beta(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/r9kbeta"])
}
pub fn r9k_beta_off(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/r9kbetaoff"])
}
pub fn raid(&mut self, source: impl IntoChannel, target: impl IntoChannel) -> Result {
let source = source.into_channel()?;
let target = target.into_channel()?;
ByteWriter::new(&mut self.writer).command(&source, &[&"/raid", &target])
}
pub fn raw(&mut self, raw: impl AsRef<[u8]>) -> Result {
ByteWriter::new(&mut self.writer).write_bytes(raw)
}
pub fn slow(
&mut self,
channel: impl IntoChannel,
duration: impl Into<Option<usize>>,
) -> Result {
let channel = channel.into_channel()?;
let dur = duration.into().unwrap_or_else(|| 120).to_string();
ByteWriter::new(&mut self.writer).command(&channel, &[&"/slow", &dur])
}
pub fn slow_off(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/slowoff"])
}
pub fn subscribers(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/subscribers"])
}
pub fn subscribers_off(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/subscribersoff"])
}
pub fn timeout<'a, 'b>(
&mut self,
channel: impl IntoChannel,
username: &str,
duration: impl Into<Option<&'a str>>,
message: impl Into<Option<&'b str>>,
) -> Result {
let channel = channel.into_channel()?;
let writer = ByteWriter::new(&mut self.writer);
match (duration.into(), message.into()) {
(Some(dur), Some(reason)) => {
writer.command(&channel, &[&"/timeout", &username, &dur, &reason])
}
(None, Some(reason)) => writer.command(&channel, &[&"/timeout", &username, &reason]),
(Some(dur), None) => writer.command(&channel, &[&"/timeout", &username, &dur]),
(None, None) => {
writer
.command(&channel, &[&"/timeout", &username])
}
}
}
pub fn unban(&mut self, channel: impl IntoChannel, username: impl AsRef<str>) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/unban", &username])
}
pub fn unhost(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/unhost"])
}
pub fn unmod(&mut self, channel: impl IntoChannel, username: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/unmod", &username])
}
pub fn unraid(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/unraid"])
}
pub fn untimeout(&mut self, channel: impl IntoChannel, username: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/untimeout", &username])
}
pub fn unvip(&mut self, channel: impl IntoChannel, username: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/unvip", &username])
}
pub fn vip(&mut self, channel: impl IntoChannel, username: &str) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/vip", &username])
}
pub fn vips(&mut self, channel: impl IntoChannel) -> Result {
let channel = channel.into_channel()?;
ByteWriter::new(&mut self.writer).command(&channel, &[&"/vips"])
}
pub fn whisper(&mut self, username: impl AsRef<str>, message: impl AsRef<str>) -> Result {
ByteWriter::new(&mut self.writer).jtv_command(&[&"/w", &username, &message])
}
}
impl<W: Write + Default> Default for Encoder<W> {
fn default() -> Self {
Self {
writer: Default::default(),
}
}
}
impl<W> std::fmt::Debug for Encoder<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Encoder").finish()
}
}
impl<W: Write + Clone> Clone for Encoder<W> {
fn clone(&self) -> Self {
Self {
writer: self.writer.clone(),
}
}
}