Skip to content

Commit 8cd11aa

Browse files
authored
[joke] Initial plugin (#81)
1 parent dd1b99d commit 8cd11aa

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

src/plugin.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub async fn load(bot: Arc<Client>) -> Result<Vec<PluginMetadata>> {
4747
"barista",
4848
"chance",
4949
"forecast",
50+
"joke",
5051
"karma",
5152
"mention",
5253
"minecraft",
@@ -154,6 +155,10 @@ pub async fn load(bot: Arc<Client>) -> Result<Vec<PluginMetadata>> {
154155
ret.push(start_plugin::<plugins::IntrospectionPlugin>(&bot)?);
155156
}
156157

158+
if config.plugin_enabled("joke") {
159+
ret.push(start_plugin::<plugins::JokePlugin>(&bot)?);
160+
}
161+
157162
if config.plugin_enabled("help") {
158163
ret.push(start_plugin::<plugins::HelpPlugin>(&bot)?);
159164
}

src/plugins/joke.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use std::time::Duration;
2+
3+
use serde::Deserialize;
4+
5+
use crate::prelude::*;
6+
7+
const API_BASE: &str = "https://v2.jokeapi.dev/joke";
8+
const CATEGORIES: &[&str] = &["any", "misc", "programming", "pun", "spooky", "christmas"];
9+
10+
pub struct JokePlugin {
11+
client: reqwest::Client,
12+
}
13+
14+
#[derive(Deserialize)]
15+
struct JokeResponse {
16+
error: bool,
17+
#[serde(rename = "type")]
18+
joke_type: String,
19+
joke: Option<String>,
20+
setup: Option<String>,
21+
delivery: Option<String>,
22+
message: Option<String>,
23+
}
24+
25+
impl JokePlugin {
26+
async fn fetch_joke(&self, category: &str) -> Result<JokeResponse> {
27+
let url = format!("{}/{}?safe-mode", API_BASE, category);
28+
29+
let resp: JokeResponse = self
30+
.client
31+
.get(&url)
32+
.send()
33+
.await?
34+
.error_for_status()?
35+
.json()
36+
.await?;
37+
38+
if resp.error {
39+
return Err(format_err!(
40+
"{}",
41+
resp.message
42+
.unwrap_or_else(|| "Unknown API error".to_string())
43+
));
44+
}
45+
46+
Ok(resp)
47+
}
48+
49+
async fn handle_joke(&self, ctx: &Arc<Context>, arg: Option<&str>) -> Result<()> {
50+
let category = arg.unwrap_or("Any");
51+
52+
let joke = self.fetch_joke(category).await?;
53+
54+
match joke.joke_type.as_str() {
55+
"single" => {
56+
if let Some(text) = joke.joke {
57+
ctx.reply(&text).await?;
58+
}
59+
}
60+
"twopart" => {
61+
if let (Some(setup), Some(delivery)) = (joke.setup, joke.delivery) {
62+
ctx.reply(&setup).await?;
63+
tokio::time::sleep(Duration::from_secs(3)).await;
64+
ctx.reply(&delivery).await?;
65+
}
66+
}
67+
joke_type => return Err(format_err!("unexpected joke type {}", joke_type)),
68+
}
69+
70+
Ok(())
71+
}
72+
}
73+
74+
#[async_trait]
75+
impl Plugin for JokePlugin {
76+
fn new_from_env() -> Result<Self> {
77+
let client = reqwest::Client::builder()
78+
.timeout(Duration::from_secs(5))
79+
.build()?;
80+
Ok(JokePlugin { client })
81+
}
82+
83+
fn command_metadata(&self) -> Vec<CommandMetadata> {
84+
vec![CommandMetadata {
85+
name: "joke".to_string(),
86+
short_help: format!(
87+
"usage: joke [category]. Categories: {}",
88+
CATEGORIES.join(", ")
89+
),
90+
full_help: format!("Gets a random joke. Categories: {}", CATEGORIES.join(", ")),
91+
}]
92+
}
93+
94+
async fn run(self, bot: Arc<Client>) -> Result<()> {
95+
let mut stream = bot.subscribe();
96+
97+
while let Ok(ctx) = stream.recv().await {
98+
let res = match ctx.as_event() {
99+
Ok(Event::Command("joke", arg)) => self.handle_joke(&ctx, arg).await,
100+
_ => Ok(()),
101+
};
102+
103+
crate::check_err(&ctx, res).await;
104+
}
105+
106+
Err(format_err!("joke plugin lagged"))
107+
}
108+
}

src/plugins/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pub use self::scryfall::ScryfallPlugin;
3131
mod introspection;
3232
pub use self::introspection::IntrospectionPlugin;
3333

34+
mod joke;
35+
pub use self::joke::JokePlugin;
36+
3437
mod quotes;
3538
pub use self::quotes::QuotesPlugin;
3639

0 commit comments

Comments
 (0)