use std::sync::Arc; use anyhow::Result; use chrono::prelude::*; use evtclib::Log; use serenity::client::bridge::gateway::ShardManager; use serenity::model::id::*; use serenity::prelude::*; use log::info; use super::categories::Categorizable; const MAX_HOURS: i64 = 5; const MAX_MESSAGE_LENGTH: usize = 2000; struct ShardManagerContainer; impl TypeMapKey for ShardManagerContainer { type Value = Arc>; } struct PostLinkResult; impl TypeMapKey for PostLinkResult { type Value = Result<()>; } #[derive(Debug, Clone)] struct Handler { channel_id: u64, log: Log, link: String, } impl Handler { fn do_link_update(&self, ctx: &Context) -> Result<()> { let mut messages = ChannelId(self.channel_id).messages(&ctx, |r| r.limit(25))?; messages.sort_by_key(|m| m.timestamp); messages.retain(|m| { m.is_own(ctx) && Utc::now().signed_duration_since(m.timestamp) < chrono::Duration::hours(MAX_HOURS) }); if let Some(mut m) = messages.pop() { let new_text = insert_link(&m.content, &self.log, &self.link); if new_text.len() <= MAX_MESSAGE_LENGTH { m.edit(ctx, |m| m.content(new_text))?; return Ok(()); } } let new_text = insert_link("", &self.log, &self.link); ChannelId(self.channel_id).say(ctx, new_text)?; Ok(()) } } impl EventHandler for Handler { fn ready(&self, ctx: Context, _ready: serenity::model::gateway::Ready) { info!("Discord client is ready"); let result = self.do_link_update(&ctx); let mut data = ctx.data.write(); data.insert::(result); if let Some(manager) = data.get::() { manager.lock().shutdown_all(); } } } pub fn post_link(discord_token: &str, channel_id: u64, log: Log, link: String) -> Result<()> { let mut client = Client::new( discord_token, Handler { channel_id, log, link, }, )?; { let mut data = client.data.write(); data.insert::(Arc::clone(&client.shard_manager)); } client.start()?; let mut data = client.data.write(); data.remove::().unwrap_or(Ok(())) } fn find_insertion(text: &str, category: &str) -> Option { let cat_pos = text.find(&format!("**{}**", category))?; let empty_line = text[cat_pos..].find("\n\n")?; Some(cat_pos + empty_line + 1) } fn insert_link(text: &str, log: &Log, link: &str) -> String { let mut text = format!("\n\n{}\n\n", text); let point = find_insertion(&text, log.category()); let link_line = format!("{} {}\n", state_emoji(log), link); if let Some(i) = point { text.insert_str(i, &link_line); } else { text.push_str(&format!("**{}**\n{}", log.category(), link_line)); } text.trim().into() } fn state_emoji(log: &Log) -> &'static str { if log.was_rewarded() { "✅" } else { "❌" } }