pikadick/commands/
shift.rs1use crate::{
2 checks::ENABLED_CHECK,
3 client_data::{
4 CacheStatsBuilder,
5 CacheStatsProvider,
6 },
7 util::TimedCache,
8 ClientDataKey,
9};
10use rand::seq::SliceRandom;
11use serenity::{
12 framework::standard::{
13 macros::command,
14 ArgError,
15 Args,
16 CommandResult,
17 },
18 model::prelude::*,
19 prelude::*,
20};
21use shift_orcz::{
22 Client as OrczClient,
23 Game,
24 ShiftCode,
25};
26use std::{
27 str::FromStr,
28 sync::Arc,
29};
30
31#[derive(Debug)]
32struct GameParseError(String);
33
34struct GameArg(Game);
35
36impl FromStr for GameArg {
37 type Err = GameParseError;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 match s {
41 "bl" => Ok(Self(Game::Borderlands)),
42 "bl2" => Ok(Self(Game::Borderlands2)),
43 "blps" => Ok(Self(Game::BorderlandsPreSequel)),
44 "bl3" => Ok(Self(Game::Borderlands3)),
45 _ => Err(GameParseError(s.into())),
46 }
47 }
48}
49
50#[derive(Default, Clone)]
51pub struct ShiftClient {
52 orcz_client: OrczClient,
53 cache: TimedCache<Game, Vec<Arc<ShiftCode>>>,
54}
55
56impl ShiftClient {
57 pub fn new() -> Self {
58 ShiftClient {
59 orcz_client: OrczClient::new(),
60 cache: TimedCache::new(),
61 }
62 }
63
64 pub async fn get_rand(
66 &self,
67 game: Game,
68 ) -> Result<Option<Arc<ShiftCode>>, shift_orcz::OrczError> {
69 if let Some(entry) = self.cache.get_if_fresh(&game) {
70 return Ok(entry.data().choose(&mut rand::thread_rng()).cloned());
71 }
72
73 let codes = self
74 .orcz_client
75 .get_shift_codes(game)
76 .await?
77 .into_iter()
78 .filter(|e| e.pc.is_valid())
79 .map(Arc::new)
80 .collect();
81
82 self.cache.insert(game, codes);
83
84 Ok(self
85 .cache
86 .get_if_fresh(&game)
87 .and_then(|entry| entry.data().choose(&mut rand::thread_rng()).cloned()))
88 }
89}
90
91impl CacheStatsProvider for ShiftClient {
92 fn publish_cache_stats(&self, cache_stats_builder: &mut CacheStatsBuilder) {
93 cache_stats_builder.publish_stat(
94 "shift",
95 "bl_cache",
96 self.cache
97 .get_if_fresh(&Game::Borderlands)
98 .map(|el| el.data().len())
99 .unwrap_or(0) as f32,
100 );
101
102 cache_stats_builder.publish_stat(
103 "shift",
104 "bl2_cache",
105 self.cache
106 .get_if_fresh(&Game::Borderlands2)
107 .map(|el| el.data().len())
108 .unwrap_or(0) as f32,
109 );
110
111 cache_stats_builder.publish_stat(
112 "shift",
113 "blps_cache",
114 self.cache
115 .get_if_fresh(&Game::BorderlandsPreSequel)
116 .map(|el| el.data().len())
117 .unwrap_or(0) as f32,
118 );
119
120 cache_stats_builder.publish_stat(
121 "shift",
122 "bl3_cache",
123 self.cache
124 .get_if_fresh(&Game::Borderlands3)
125 .map(|el| el.data().len())
126 .unwrap_or(0) as f32,
127 );
128 }
129}
130
131impl std::fmt::Debug for ShiftClient {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 f.debug_struct("ShiftClient")
135 .field("cache", &self.cache)
136 .finish()
137 }
138}
139
140#[command]
141#[description("Get a random shift code for a Borderlands game")]
142#[min_args(1)]
143#[usage("<bl, bl2, blps, or bl3>")]
144#[example("blps")]
145#[checks(Enabled)]
146#[bucket("default")]
147async fn shift(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
148 let data_lock = ctx.data.read().await;
149 let client_data = data_lock.get::<ClientDataKey>().unwrap();
150 let client = client_data.shift_client.clone();
151 drop(data_lock);
152
153 let game = match args.single::<GameArg>().map(|el| el.0) {
154 Ok(g) => g,
155 Err(ArgError::Parse(e)) => {
156 msg.channel_id
157 .say(
158 &ctx.http,
159 format!("Invalid arg '{}'. Valid: bl, bl2, blps, bl3", e.0),
160 )
161 .await?;
162 return Ok(());
163 }
164 Err(_) => {
165 msg.channel_id
166 .say(&ctx.http, "Need arg. Valid: bl, bl2, blps, bl3")
167 .await?;
168 return Ok(());
169 }
170 };
171
172 match client.get_rand(game).await {
173 Ok(Some(code)) => {
174 msg.channel_id
175 .say(
176 &ctx.http,
177 format!(
178 "Source: {}\nIssue Date: {}\nReward: {}\nCode: {}",
179 code.source,
180 code.issue_date
181 .map(|d| d.to_string())
182 .unwrap_or_else(|| "unknown".to_string()),
183 code.rewards,
184 code.pc
185 ),
186 )
187 .await?;
188 }
189 Ok(None) => {
190 msg.channel_id
191 .say(&ctx.http, format!("No valid codes for {:?}", game))
192 .await?;
193 }
194 Err(e) => {
195 msg.channel_id
196 .say(&ctx.http, format!("Failed to get shift code: {:#?}", e))
197 .await?;
198 }
199 }
200
201 client.cache.trim();
202
203 Ok(())
204}