hanabot 0.2.7

A Slack bot that lets you play Hanabi
Documentation
use eyre::Context;
use hanabot::{Hanabi, MessageProxy};
use slack_morphism::SlackUserId;
use std::collections::HashMap;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> eyre::Result<()> {
    let mut hanabi = Hanabi::resume()
        .await
        .context("resume from saved game states")?
        .unwrap_or_default();

    let stdin = tokio::io::BufReader::new(tokio::io::stdin());
    let mut lines = stdin.lines();

    let mut msgs = StdoutMessageProxy::default();
    while let Some(line) = lines.next_line().await.context("read line from stdin")? {
        let (user, dm) = line
            .split_once(':')
            .ok_or_else(|| eyre::eyre!("line did not start with `user:`"))?;
        let dm = dm.trim();
        hanabi
            .on_dm_recv(dm, SlackUserId(user.to_string()), &mut msgs)
            .await
            .context("handle command as received dm")?;
        msgs.flush().await.context("flush responses")?;
    }

    hanabi.save().await
}

#[derive(Debug, Default)]
struct StdoutMessageProxy {
    msgs: HashMap<String, Vec<String>>,
}

impl StdoutMessageProxy {
    async fn flush(&mut self) -> eyre::Result<()> {
        let mut stdout = tokio::io::BufWriter::new(tokio::io::stdout());
        for (user, msgs) in self.msgs.drain() {
            for msg in msgs {
                for line in msg.lines() {
                    stdout
                        .write(format!("@{user} {line}\n").as_bytes())
                        .await
                        .with_context(|| format!("write out line of {user}'s responses"))?;
                }
            }
        }

        stdout
            .write(b"\n")
            .await
            .context("write separator after output")?;
        stdout.flush().await.context("flush bufwriter")?;

        Ok(())
    }
}

impl MessageProxy for StdoutMessageProxy {
    fn send(&mut self, user: &str, text: &str) {
        self.msgs
            .entry(user.to_string())
            .or_default()
            .push(text.to_owned());
    }
}