1use crate::{
2 checks::ENABLED_CHECK,
3 client_data::{
4 CacheStatsBuilder,
5 CacheStatsProvider,
6 },
7 util::LoadingReaction,
8 ClientDataKey,
9};
10use crossbeam::queue::SegQueue;
11use fml::{
12 types::Article,
13 FmlResult,
14};
15use serenity::{
16 builder::{
17 CreateEmbed,
18 CreateMessage,
19 },
20 client::Context,
21 framework::standard::{
22 macros::command,
23 Args,
24 CommandResult,
25 },
26 model::channel::Message,
27};
28use std::sync::Arc;
29use tracing::error;
30
31#[derive(Clone, Debug)]
33pub struct FmlClient {
34 client: fml::Client,
35 cache: Arc<SegQueue<Article>>,
36}
37
38impl FmlClient {
39 pub fn new(key: String) -> Self {
41 Self {
42 client: fml::Client::new(key),
43 cache: Arc::new(SegQueue::new()),
44 }
45 }
46
47 async fn repopulate(&self) -> FmlResult<()> {
49 let articles = self.client.list_random(100).await?;
50 for article in articles.into_iter() {
51 self.cache.push(article);
52 }
53
54 Ok(())
55 }
56
57 fn should_repopulate(&self) -> bool {
58 self.cache.len() < 50
59 }
60
61 fn get_entry(&self) -> Option<Article> {
62 self.cache.pop()
63 }
64}
65
66impl CacheStatsProvider for FmlClient {
67 fn publish_cache_stats(&self, cache_stats_builder: &mut CacheStatsBuilder) {
68 cache_stats_builder.publish_stat("fml", "cache", self.cache.len() as f32);
69 }
70}
71
72#[command]
74#[description("Get a random story from fmylife.com")]
75#[checks(Enabled)]
76#[bucket("default")]
77async fn fml(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
78 let data_lock = ctx.data.read().await;
79 let client_data = data_lock.get::<ClientDataKey>().unwrap();
80 let client = client_data.fml_client.clone();
81 drop(data_lock);
82
83 let mut loading = LoadingReaction::new(ctx.http.clone(), msg);
84
85 if client.should_repopulate() {
86 if let Err(error) = client.repopulate().await {
87 error!("Failed to repopulate fml cache: {error}");
88
89 msg.channel_id
90 .say(&ctx.http, format!("Failed to get fml entry: {error}"))
91 .await?;
92
93 return Ok(());
94 }
95 }
96
97 if let Some(entry) = client.get_entry() {
98 let mut votes_up_buf = itoa::Buffer::new();
99 let mut votes_down_buf = itoa::Buffer::new();
100
101 let embed_builder = CreateEmbed::new()
102 .title("FML Story")
103 .description(entry.content_hidden)
104 .field(
105 "I agree, your life sucks",
106 votes_up_buf.format(entry.metrics.votes_up),
107 true,
108 )
109 .field(
110 "You deserved it",
111 votes_down_buf.format(entry.metrics.votes_down),
112 true,
113 )
114 .field("\u{200B}", "\u{200B}", false)
115 .field(
116 "Reactions",
117 format!(
118 "š {}\n\nš {}\n\nš² {}\n\nš {}",
119 entry.metrics.smiley_amusing,
120 entry.metrics.smiley_funny,
121 entry.metrics.smiley_weird,
122 entry.metrics.smiley_hilarious
123 ),
124 true,
125 );
126 let message_builder = CreateMessage::new().embed(embed_builder);
127
128 msg.channel_id
129 .send_message(&ctx.http, message_builder)
130 .await?;
131
132 loading.send_ok();
133 } else {
134 msg.channel_id
136 .say(&ctx.http, "Failed to get fml entry: Cache Empty")
137 .await?;
138 };
139
140 Ok(())
141}