mod commands;
mod constants;
pub use commands::Commands;
use frankenstein::{
client_reqwest::Bot, methods::{DeleteMyCommandsParams, SetMyCommandsParams, SetMyDefaultAdministratorRightsParams}, types::BotCommandScope, AsyncTelegramApi, Error
};
use rand::{Rng, seq::index::sample};
use serde::Deserialize;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Deserialize)]
pub struct Config {
pub token: String,
#[serde(default)]
pub game: RouletteConfig,
#[serde(default)]
pub groups: Vec<GroupConfig>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct RouletteConfig {
#[serde(default = "constants::chambers")]
chambers: usize,
#[serde(default = "constants::bullets")]
bullets: usize,
#[serde(default = "constants::min_mute_time")]
min_mute_time: u32,
#[serde(default = "constants::max_mute_time")]
max_mute_time: u32,
}
impl RouletteConfig {
pub fn start(self) -> Result<Roulette, &'static str> {
if self.chambers <= 0 {
return Err("Number of chambers must be greater than 0");
}
if self.bullets <= 0 {
return Err("Number of bullets must be greater than 0");
}
if self.bullets > self.chambers {
return Err("Number of bullets must be less than or equal to number of chambers");
}
if self.min_mute_time < 30 {
return Err("Minimum mute time must be greater than or equal to 30 seconds");
}
if self.max_mute_time > 3600 {
return Err("Maximum mute time must be less than or equal to 3600 seconds");
}
if self.min_mute_time > self.max_mute_time {
return Err("Minimum mute time must be less than or equal to maximum mute time");
}
let contents = vec![false; self.chambers];
let mut roulette = Roulette {
config: self,
contents,
position: 0,
};
roulette.restart();
Ok(roulette)
}
pub fn info(&self) -> (usize, usize) {
(self.bullets, self.chambers)
}
pub fn random_mute_until(&self) -> (u64, u64) {
let mut rng = rand::rng();
let duration: u64 = rng
.random_range(self.min_mute_time..=self.max_mute_time)
.into();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
(duration, now + duration)
}
}
impl Default for RouletteConfig {
fn default() -> Self {
Self {
chambers: constants::chambers(),
bullets: constants::bullets(),
min_mute_time: constants::min_mute_time(),
max_mute_time: constants::max_mute_time(),
}
}
}
#[derive(Clone, Debug)]
pub struct Roulette {
pub config: RouletteConfig,
contents: Vec<bool>,
position: usize,
}
impl Roulette {
pub fn restart(&mut self) {
self.position = 0;
self.contents.fill(false);
let mut rng = rand::rng();
let selected = sample(&mut rng, self.contents.len(), self.config.bullets);
for i in selected {
self.contents[i] = true;
}
}
pub fn info(&self) -> (usize, usize) {
self.config.info()
}
pub fn random_mute_until(&self) -> (u64, u64) {
self.config.random_mute_until()
}
pub fn fire(&mut self) -> Option<bool> {
if self.peek().0 == 0 {
return None;
}
let result = self.contents[self.position];
self.position += 1;
Some(result)
}
pub fn peek(&self) -> (usize, usize) {
let filled = self
.contents
.iter()
.skip(self.position)
.filter(|&&x| x)
.count();
let left = self.contents.len() - self.position;
(filled, left)
}
}
#[derive(Debug, Deserialize)]
pub struct GroupConfig {
pub id: i64,
chambers: Option<usize>,
bullets: Option<usize>,
min_mute_time: Option<u32>,
max_mute_time: Option<u32>,
}
impl GroupConfig {
pub fn resolve(&self, default: &RouletteConfig) -> RouletteConfig {
let Self {
chambers,
bullets,
min_mute_time,
max_mute_time,
..
} = self;
let (chambers, bullets, min_mute_time, max_mute_time) = (
chambers.unwrap_or(default.chambers),
bullets.unwrap_or(default.bullets),
min_mute_time.unwrap_or(default.min_mute_time),
max_mute_time.unwrap_or(default.max_mute_time),
);
RouletteConfig {
chambers,
bullets,
min_mute_time,
max_mute_time,
}
}
}
pub async fn init_commands_and_rights(bot: &Bot) -> Result<(), Error> {
let delete_param = DeleteMyCommandsParams::builder().build();
bot.delete_my_commands(&delete_param).await?;
let commands_param = SetMyCommandsParams::builder()
.commands(Commands::list())
.scope(BotCommandScope::AllGroupChats)
.build();
bot.set_my_commands(&commands_param).await?;
let rights_param = SetMyDefaultAdministratorRightsParams::builder()
.rights(constants::RECOMMENDED_ADMIN_RIGHTS)
.build();
bot.set_my_default_administrator_rights(&rights_param)
.await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fire() {
let config = RouletteConfig {
chambers: 3,
bullets: 1,
min_mute_time: 60,
max_mute_time: 600,
};
let mut roulette = Roulette {
config,
contents: vec![false, true, false],
position: 0,
};
assert_eq!(roulette.fire(), Some(false));
assert_eq!(roulette.fire(), Some(true));
assert_eq!(roulette.fire(), None);
assert_eq!(roulette.fire(), None);
}
#[test]
fn test_restart() {
let mut roulette = RouletteConfig::default().start().unwrap();
for _ in 0..10 {
roulette.restart();
}
assert_eq!(roulette.contents.len(), 6);
assert_eq!(roulette.peek().0, 2);
assert_eq!(roulette.position, 0);
}
}