use async_trait::async_trait; use inbox::*; use lazy_static::lazy_static; use serenity::client::Client; use serenity::framework::standard::{ macros::{command, group}, CommandResult, StandardFramework, }; use serenity::model::channel::{Reaction, ReactionType}; use serenity::model::{ channel::Message, id::{ChannelId, GuildId, UserId}, prelude::User, }; use serenity::prelude::*; use std::fs::File; use std::io::{self, BufRead, BufReader}; use time::Duration; mod inbox; const APPLICATION_NAME: &str = "didgeridoo"; const INBOX_OWNER: UserId = UserId(354694403798990848); lazy_static! { static ref INBOX: Inbox = Inbox(redis::Client::open("redis://127.0.0.1/").unwrap()); } macro_rules! send_or_log { ($e: expr) => { if let Err(e) = $e { eprintln!("Couldn’t send message because {:?}", e); } }; } #[group] #[commands(name, message, question, inbox)] struct Fluff; struct Handler; #[async_trait] impl EventHandler for Handler { async fn guild_ban_addition(&self, ctx: Context, guild_id: GuildId, _: User) { if guild_id == 427456811973738498 { send_or_log!({ ChannelId(562731470423064587) .say(&ctx, "Dies ist eine flauschige Diktatur!") .await }); } } async fn reaction_add(&self, ctx: Context, reaction: Reaction) { if reaction.message_id == 911630315376738384 && reaction.emoji == ReactionType::Unicode(String::from("🧪")) { reaction .guild_id .unwrap() .member(&ctx, reaction.user_id.unwrap()) .await .unwrap() .add_role(&ctx, 356421827708321795) .await .unwrap() } } async fn reaction_remove(&self, ctx: Context, reaction: Reaction) { if reaction.message_id == 911630315376738384 && reaction.emoji == ReactionType::Unicode(String::from("🧪")) { reaction .guild_id .unwrap() .member(&ctx, reaction.user_id.unwrap()) .await .unwrap() .remove_role(&ctx, 356421827708321795) .await .unwrap() } } } fn read_token() -> io::Result { let reader = BufReader::new(File::open("secret")?); reader.lines().next().unwrap() } #[tokio::main] async fn main() { let mut client = Client::builder(&read_token().expect("no secret file")) .event_handler(Handler) .framework( StandardFramework::new() .configure(|c| c.prefix("!")) .group(&FLUFF_GROUP), ) .await .expect("Error creating client"); INBOX.count_messages("test"); // initialize the lazy static so we know if redis is unavailable if let Err(why) = client.start().await { println!("An error occurred while running the client: {:?}", why); } } #[command] async fn name(ctx: &Context, msg: &Message) -> CommandResult { if let Some((_, name)) = msg.content.split_once(' ') { let (name, adjustment) = name.split_once(" -").unwrap_or((name, "0")); let adjustment = adjustment.strip_suffix("h").unwrap_or(adjustment); msg.guild(&ctx) .await .unwrap() .edit_member(&ctx, msg.author.id, |m| m.nickname(name)) .await?; let now = time::OffsetDateTime::now_utc() - adjustment .parse::() .map(|a| (a * 3600.0) as i64) .map(Duration::seconds) .ok() .unwrap_or_else(Duration::zero); println!( "{},{},{},{},{}", now.format("%d.%m.%Y %H:%M"), name, now.format("%y%m"), msg.author.id, now.unix_timestamp() ); if msg.author.id == INBOX_OWNER { let msg_count = INBOX.count_messages(name); if msg_count > 0 { send_or_log!( msg.reply(&ctx, format!("{} neue Nachrichten für {}", msg_count, name)) .await ); } else { send_or_log!( msg.reply(&ctx, format!("Keine neuen Nachrichten für {}", name)) .await ); } } } else { send_or_log!(msg.reply(&ctx, "Please specify a new name.").await); } Ok(()) } #[command] async fn inbox(ctx: &Context, msg: &Message) -> CommandResult { if msg.author.id != INBOX_OWNER || !msg.is_private() { send_or_log!( msg.reply(&ctx, "You don’t have the permission to do that") .await ); return Ok(()); } if let Some((_, name)) = msg.content.split_once(' ') { let messages = INBOX.fetch_messages(name); if messages.is_empty() { send_or_log!( msg.reply(&ctx, format!("Keine neuen Nachrichten für {}.", name)) .await ); return Ok(()); } for message in &messages { let content = message.to_string(); // Since we’re prepending a name (and later a time) to the message, // it could go over the limit of 2000 characters. // There is some leniency in the numbers here because unicode codepoints are meh. if content.len() > 1900 { let (first, second) = content.split_at(1500); send_or_log!(msg.channel_id.say(&ctx, &first).await); send_or_log!(msg.channel_id.say(&ctx, &second).await); } else { send_or_log!(msg.channel_id.say(&ctx, &content).await); } } println!("{:?}", messages); // paranoia for now so the messages aren’t lost if let Err(e) = INBOX.clear_messages(name) { eprintln!("Could not clear inbox of user {} because {}", name, e); } } else { send_or_log!(msg.reply(&ctx, "Kein Name für die Abfrage.").await); } Ok(()) } #[command] async fn message(ctx: &Context, msg: &Message) -> CommandResult { message_internal(ctx, msg, &msg.content).await } #[command] async fn question(ctx: &Context, msg: &Message) -> CommandResult { message_internal( ctx, msg, &msg.content.replacen("!question", "!message Stream:", 1), ) .await } async fn message_internal(ctx: &Context, msg: &Message, content: &str) -> CommandResult { let result = InboxMessage::parse(content, msg.author.id) .ok_or_else(|| { String::from( "Nachricht konnte nicht gesendet werden. Bitte achte auf die richtige Formulierung: “!message : ”, z.B. “!message Lana: Hawwu!”", ) }) .and_then(|m| INBOX.queue_message(&m)); match result { Ok(name) => send_or_log!( msg.reply( &ctx, format!("Deine Nachricht wurde erfolgreich an {} gesendet", name), ) .await ), Err(e) => send_or_log!(msg.reply(&ctx, e).await), }; Ok(()) }