didgeridoo/src/main.rs

231 lines
7.1 KiB
Rust

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<String> {
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::<f32>()
.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 <name>: <text>”,
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(())
}