pikadick/commands/
urban.rs1use crate::{
2 checks::ENABLED_CHECK,
3 client_data::{
4 CacheStatsBuilder,
5 CacheStatsProvider,
6 },
7 util::{
8 LoadingReaction,
9 TimedCache,
10 TimedCacheEntry,
11 },
12 ClientDataKey,
13};
14use anyhow::Context as _;
15use serenity::{
16 builder::{
17 CreateEmbed,
18 CreateMessage,
19 },
20 framework::standard::{
21 macros::command,
22 Args,
23 CommandResult,
24 },
25 model::{
26 prelude::*,
27 timestamp::Timestamp,
28 },
29 prelude::*,
30};
31use std::sync::Arc;
32use tracing::error;
33
34#[derive(Clone, Default, Debug)]
37pub struct UrbanClient {
38 client: urban_dictionary::Client,
39 search_cache: TimedCache<String, urban_dictionary::DefinitionList>,
40}
41
42impl UrbanClient {
43 pub fn new() -> UrbanClient {
46 Default::default()
47 }
48
49 pub async fn search(
52 &self,
53 query: &str,
54 ) -> Result<Arc<TimedCacheEntry<urban_dictionary::DefinitionList>>, urban_dictionary::Error>
55 {
56 if let Some(entry) = self.search_cache.get_if_fresh(query) {
57 return Ok(entry);
58 }
59
60 let results = self.client.lookup(query).await?;
61 self.search_cache.insert(String::from(query), results);
62
63 Ok(self
64 .search_cache
65 .get_if_fresh(query)
66 .expect("recently acquired entry expired"))
67 }
68}
69
70impl CacheStatsProvider for UrbanClient {
71 fn publish_cache_stats(&self, cache_stats_builder: &mut CacheStatsBuilder) {
72 cache_stats_builder.publish_stat("urban", "search_cache", self.search_cache.len() as f32);
73 }
74}
75
76#[command]
77#[description("Get the top definition from UrbanDictionary.com")]
78#[usage("\"<query>\"")]
79#[example("\"test\"")]
80#[min_args(1)]
81#[max_args(1)]
82#[checks(Enabled)]
83#[bucket("default")]
84pub async fn urban(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
85 let data_lock = ctx.data.read().await;
86 let client_data = data_lock.get::<ClientDataKey>().unwrap();
87 let client = client_data.urban_client.clone();
88 drop(data_lock);
89
90 let mut loading = LoadingReaction::new(ctx.http.clone(), msg);
91 let query = args.quoted().trimmed().current().expect("missing arg");
92
93 match client
94 .search(query)
95 .await
96 .context("failed to search urban dictionary")
97 {
98 Ok(entry) => {
99 if let Some(entry) = entry.data().list.first() {
100 let mut thumbs_down_buf = itoa::Buffer::new();
101
102 let mut embed_builder = CreateEmbed::new()
103 .title(&entry.word)
104 .url(entry.permalink.as_str())
105 .field("Definition", entry.get_raw_definition(), false)
106 .field("Example", entry.get_raw_example(), false)
107 .field("👍", entry.thumbs_up.to_string(), true)
108 .field("👎", thumbs_down_buf.format(entry.thumbs_down), true);
109
110 match Timestamp::parse(entry.written_on.as_str())
111 .context("failed to parse timestamp")
112 {
113 Ok(timestamp) => {
114 embed_builder = embed_builder.timestamp(timestamp);
115 }
116 Err(error) => {
117 error!("{error}");
118 }
119 }
120
121 let message_builder = CreateMessage::new().embed(embed_builder);
122
123 msg.channel_id
124 .send_message(&ctx.http, message_builder)
125 .await?;
126
127 loading.send_ok();
128 } else {
129 msg.channel_id.say(&ctx.http, "No results").await?;
130 }
131 }
132
133 Err(error) => {
134 error!("{error:?}");
135 msg.channel_id.say(&ctx.http, format!("{error:?}")).await?;
136 }
137 }
138
139 client.search_cache.trim();
140
141 Ok(())
142}