rustacean_roulette/
lib.rs1mod commands;
2mod constants;
3
4pub use commands::Commands;
5use frankenstein::{
6 client_reqwest::Bot, methods::{DeleteMyCommandsParams, SetMyCommandsParams, SetMyDefaultAdministratorRightsParams}, types::BotCommandScope, AsyncTelegramApi, Error
7};
8use rand::{Rng, seq::index::sample};
9use serde::Deserialize;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[derive(Deserialize)]
14pub struct Config {
15 pub token: String,
17 #[serde(default)]
19 pub game: RouletteConfig,
20 #[serde(default)]
22 pub groups: Vec<GroupConfig>,
23}
24
25#[derive(Clone, Debug, Deserialize)]
27pub struct RouletteConfig {
28 #[serde(default = "constants::chambers")]
30 chambers: usize,
31 #[serde(default = "constants::bullets")]
33 bullets: usize,
34 #[serde(default = "constants::min_mute_time")]
36 min_mute_time: u32,
37 #[serde(default = "constants::max_mute_time")]
39 max_mute_time: u32,
40}
41
42impl RouletteConfig {
43 pub fn start(self) -> Result<Roulette, &'static str> {
45 if self.chambers <= 0 {
47 return Err("Number of chambers must be greater than 0");
48 }
49 if self.bullets <= 0 {
50 return Err("Number of bullets must be greater than 0");
51 }
52 if self.bullets > self.chambers {
53 return Err("Number of bullets must be less than or equal to number of chambers");
54 }
55 if self.min_mute_time < 30 {
56 return Err("Minimum mute time must be greater than or equal to 30 seconds");
57 }
58 if self.max_mute_time > 3600 {
59 return Err("Maximum mute time must be less than or equal to 3600 seconds");
61 }
62 if self.min_mute_time > self.max_mute_time {
63 return Err("Minimum mute time must be less than or equal to maximum mute time");
64 }
65
66 let contents = vec![false; self.chambers];
68 let mut roulette = Roulette {
69 config: self,
70 contents,
71 position: 0,
72 };
73 roulette.restart();
74
75 Ok(roulette)
76 }
77
78 pub fn info(&self) -> (usize, usize) {
80 (self.bullets, self.chambers)
81 }
82
83 pub fn random_mute_until(&self) -> (u64, u64) {
85 let mut rng = rand::rng();
87 let duration: u64 = rng
88 .random_range(self.min_mute_time..=self.max_mute_time)
89 .into();
90 let now = SystemTime::now()
92 .duration_since(UNIX_EPOCH)
93 .expect("Time went backwards")
94 .as_secs();
95 (duration, now + duration)
96 }
97}
98
99impl Default for RouletteConfig {
100 fn default() -> Self {
101 Self {
102 chambers: constants::chambers(),
103 bullets: constants::bullets(),
104 min_mute_time: constants::min_mute_time(),
105 max_mute_time: constants::max_mute_time(),
106 }
107 }
108}
109
110#[derive(Clone, Debug)]
112pub struct Roulette {
113 pub config: RouletteConfig,
115 contents: Vec<bool>,
117 position: usize,
119}
120
121impl Roulette {
122 pub fn restart(&mut self) {
124 self.position = 0;
125 self.contents.fill(false);
126
127 let mut rng = rand::rng();
129 let selected = sample(&mut rng, self.contents.len(), self.config.bullets);
130 for i in selected {
131 self.contents[i] = true;
132 }
133 }
134
135 pub fn info(&self) -> (usize, usize) {
137 self.config.info()
138 }
139
140 pub fn random_mute_until(&self) -> (u64, u64) {
142 self.config.random_mute_until()
143 }
144
145 pub fn fire(&mut self) -> Option<bool> {
151 if self.peek().0 == 0 {
152 return None;
154 }
155
156 let result = self.contents[self.position];
157 self.position += 1;
158
159 Some(result)
160 }
161
162 pub fn peek(&self) -> (usize, usize) {
164 let filled = self
165 .contents
166 .iter()
167 .skip(self.position)
168 .filter(|&&x| x)
169 .count();
170 let left = self.contents.len() - self.position;
171 (filled, left)
172 }
173}
174
175#[derive(Debug, Deserialize)]
177pub struct GroupConfig {
178 pub id: i64,
180 chambers: Option<usize>,
182 bullets: Option<usize>,
184 min_mute_time: Option<u32>,
186 max_mute_time: Option<u32>,
188}
189
190impl GroupConfig {
191 pub fn resolve(&self, default: &RouletteConfig) -> RouletteConfig {
193 let Self {
194 chambers,
195 bullets,
196 min_mute_time,
197 max_mute_time,
198 ..
199 } = self;
200 let (chambers, bullets, min_mute_time, max_mute_time) = (
201 chambers.unwrap_or(default.chambers),
202 bullets.unwrap_or(default.bullets),
203 min_mute_time.unwrap_or(default.min_mute_time),
204 max_mute_time.unwrap_or(default.max_mute_time),
205 );
206 RouletteConfig {
207 chambers,
208 bullets,
209 min_mute_time,
210 max_mute_time,
211 }
212 }
213}
214
215pub async fn init_commands_and_rights(bot: &Bot) -> Result<(), Error> {
217 let delete_param = DeleteMyCommandsParams::builder().build();
218 bot.delete_my_commands(&delete_param).await?;
219
220 let commands_param = SetMyCommandsParams::builder()
221 .commands(Commands::list())
222 .scope(BotCommandScope::AllGroupChats)
223 .build();
224 bot.set_my_commands(&commands_param).await?;
225
226 let rights_param = SetMyDefaultAdministratorRightsParams::builder()
227 .rights(constants::RECOMMENDED_ADMIN_RIGHTS)
228 .build();
229 bot.set_my_default_administrator_rights(&rights_param)
230 .await?;
231
232 Ok(())
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_fire() {
241 let config = RouletteConfig {
242 chambers: 3,
243 bullets: 1,
244 min_mute_time: 60,
245 max_mute_time: 600,
246 };
247 let mut roulette = Roulette {
249 config,
250 contents: vec![false, true, false],
251 position: 0,
252 };
253
254 assert_eq!(roulette.fire(), Some(false));
255 assert_eq!(roulette.fire(), Some(true));
256 assert_eq!(roulette.fire(), None);
257 assert_eq!(roulette.fire(), None);
258 }
259
260 #[test]
261 fn test_restart() {
262 let mut roulette = RouletteConfig::default().start().unwrap();
263
264 for _ in 0..10 {
265 roulette.restart();
266 }
267
268 assert_eq!(roulette.contents.len(), 6);
269 assert_eq!(roulette.peek().0, 2);
270 assert_eq!(roulette.position, 0);
271 }
272}