Skip to content

Commit

Permalink
WIP from the laptop
Browse files Browse the repository at this point in the history
  • Loading branch information
kozalosev committed Feb 25, 2024
1 parent 0f0c3f1 commit 93c99a2
Show file tree
Hide file tree
Showing 18 changed files with 441 additions and 194 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ chrono = { version = "0.4.31", features = [ "serde" ] }
tinytemplate = "1.2.1"
base64 = { package = "simple-base64", version = "0.23.2" }
byteorder = "1.5.0"
derive_more = { version = "1.0.0-beta.6", features = ["display", "error"] }
derive_more = { version = "1.0.0-beta.6", features = ["display", "error", "constructor", "add_assign"] }
num-traits = "0.2.18"

[dev-dependencies]
testcontainers = "0.15.0"
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ titles:
time_till_next_day:
none: " Come back tomorrow!"
some: "\n\nNext attempt in <b>%{hours}</b>h <b>%{minutes}</b>m."
perks:
top_line: "The following perks affected the result"
help-pussies: "Deep hole"
loan-payout: "Micro-loaner"
errors:
not_group_chat: "This bot is supposed to do its mission in group chats only!"
feature_disabled: "This feature is currently temporarily disabled."
4 changes: 4 additions & 0 deletions locales/ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ titles:
time_till_next_day:
none: " Возвращайся завтра!"
some: "\n\nСледующая попытка через <b>%{hours}</b> ч. <b>%{minutes}</b> мин."
perks:
top_line: "На результат повлияли следующие перки"
help-pussies: "Глубокая нора"
loan-payout: "Микрозаймер"
errors:
not_group_chat: "Бот выполняет свою миссию только в групповых чатах!"
feature_disabled: "Данная функция пока временно отключена."
44 changes: 16 additions & 28 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
use std::error::Error;
use std::fmt::Display;
use std::ops::RangeInclusive;
use std::str::FromStr;
use anyhow::anyhow;
use reqwest::Url;
use teloxide::types::Me;
use crate::handlers::utils::Incrementor;
use crate::help;

#[derive(Clone)]
pub struct AppConfig {
pub features: FeatureToggles,
pub growth_range: RangeInclusive<i32>,
pub grow_shrink_ratio: f32,
pub dod_bonus_range: RangeInclusive<u32>,
pub newcomers_grace_days: u32,
pub top_limit: u32,
}

Expand Down Expand Up @@ -48,15 +44,10 @@ pub struct BattlesFeatureToggles {

impl AppConfig {
pub fn from_env() -> Self {
let min = get_value_or_default("GROWTH_MIN", -5);
let max = get_value_or_default("GROWTH_MAX", 10);
let grow_shrink_ratio = get_value_or_default("GROW_SHRINK_RATIO", 0.5);
let max_dod_bonus = get_value_or_default("GROWTH_DOD_BONUS_MAX", 5);
let newcomers_grace_days = get_value_or_default("NEWCOMERS_GRACE_DAYS", 7);
let top_limit = get_value_or_default("TOP_LIMIT", 10);
let chats_merging = get_value_or_default("CHATS_MERGING_ENABLED", false);
let top_unlimited = get_value_or_default("TOP_UNLIMITED_ENABLED", false);
let check_acceptor_length = get_value_or_default("PVP_CHECK_ACCEPTOR_LENGTH", false);
let top_limit = get_env_value_or_default("TOP_LIMIT", 10);
let chats_merging = get_env_value_or_default("CHATS_MERGING_ENABLED", false);
let top_unlimited = get_env_value_or_default("TOP_UNLIMITED_ENABLED", false);
let check_acceptor_length = get_env_value_or_default("PVP_CHECK_ACCEPTOR_LENGTH", false);
Self {
features: FeatureToggles {
chats_merging,
Expand All @@ -65,10 +56,6 @@ impl AppConfig {
check_acceptor_length,
}
},
growth_range: min..=max,
grow_shrink_ratio,
dod_bonus_range: 1..=max_dod_bonus,
newcomers_grace_days,
top_limit,
}
}
Expand All @@ -77,31 +64,32 @@ impl AppConfig {
impl DatabaseConfig {
pub fn from_env() -> anyhow::Result<Self> {
Ok(Self {
url: get_mandatory_value("DATABASE_URL")?,
max_connections: get_value_or_default("DATABASE_MAX_CONNECTIONS", 10)
url: get_env_mandatory_value("DATABASE_URL")?,
max_connections: get_env_value_or_default("DATABASE_MAX_CONNECTIONS", 10)
})
}
}

pub fn build_context_for_help_messages(me: Me, app_config: &AppConfig, competitor_bots: &[&str]) -> anyhow::Result<help::Context> {
pub fn build_context_for_help_messages(me: Me, incr: &Incrementor, competitor_bots: &[&str]) -> anyhow::Result<help::Context> {
let other_bots = competitor_bots
.iter()
.map(|username| ensure_starts_with_at_sign(username.to_string()))
.collect::<Vec<String>>()
.join(", ");
let incr_cfg = incr.get_config();

Ok(help::Context {
bot_name: me.username().to_owned(),
grow_min: app_config.growth_range.clone().min().ok_or(anyhow!("growth_range must have min"))?.to_string(),
grow_max: app_config.growth_range.clone().max().ok_or(anyhow!("growth_range must have max"))?.to_string(),
grow_min: incr_cfg.growth_range_min().to_string(),
grow_max: incr_cfg.growth_range_max().to_string(),
other_bots,
admin_username: ensure_starts_with_at_sign(get_mandatory_value("HELP_ADMIN_USERNAME")?),
admin_channel: ensure_starts_with_at_sign(get_mandatory_value("HELP_ADMIN_CHANNEL")?),
git_repo: get_mandatory_value("HELP_GIT_REPO")?,
admin_username: ensure_starts_with_at_sign(get_env_mandatory_value("HELP_ADMIN_USERNAME")?),
admin_channel: ensure_starts_with_at_sign(get_env_mandatory_value("HELP_ADMIN_CHANNEL")?),
git_repo: get_env_mandatory_value("HELP_GIT_REPO")?,
})
}

fn get_mandatory_value<T, E>(key: &str) -> anyhow::Result<T>
pub(crate) fn get_env_mandatory_value<T, E>(key: &str) -> anyhow::Result<T>
where
T: FromStr<Err = E>,
E: Error + Send + Sync + 'static
Expand All @@ -111,7 +99,7 @@ where
.map_err(|e: E| anyhow!(e))
}

fn get_value_or_default<T, E>(key: &str, default: T) -> T
pub(crate) fn get_env_value_or_default<T, E>(key: &str, default: T) -> T
where
T: FromStr<Err = E> + Display,
E: Error + Send + Sync + 'static
Expand Down
75 changes: 11 additions & 64 deletions src/handlers/dick.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::future::IntoFuture;
use std::ops::RangeInclusive;
use anyhow::anyhow;
use chrono::{Datelike, Utc};
use futures::future::join;
use futures::TryFutureExt;
use rand::Rng;
use rand::rngs::OsRng;
use rust_i18n::t;
use teloxide::Bot;
use teloxide::macros::BotCommands;
Expand All @@ -14,7 +11,7 @@ use teloxide::types::{CallbackQuery, ChatId, InlineKeyboardButton, InlineKeyboar
use page::{InvalidPage, Page};
use crate::handlers::{ensure_lang_code, HandlerResult, reply_html, utils};
use crate::{config, metrics, repo};
use crate::handlers::utils::page;
use crate::handlers::utils::{Incrementor, page};
use crate::repo::{ChatIdKind, ChatIdPartiality};

const TOMORROW_SQL_CODE: &str = "GD0E1";
Expand All @@ -31,14 +28,15 @@ pub enum DickCommands {
}

pub async fn dick_cmd_handler(bot: Bot, msg: Message, cmd: DickCommands,
repos: repo::Repositories, config: config::AppConfig) -> HandlerResult {
repos: repo::Repositories, incr: Incrementor,
config: config::AppConfig) -> HandlerResult {
let from = msg.from().ok_or(anyhow!("unexpected absence of a FROM field"))?;
let chat_id = msg.chat.id.into();
let from_refs = FromRefs(from, &chat_id);
match cmd {
DickCommands::Grow => {
metrics::CMD_GROW_COUNTER.chat.inc();
let answer = grow_impl(&repos, config, from_refs).await?;
let answer = grow_impl(&repos, incr, from_refs).await?;
reply_html(bot, msg, answer)
},
DickCommands::Top => {
Expand All @@ -57,26 +55,21 @@ pub async fn dick_cmd_handler(bot: Bot, msg: Message, cmd: DickCommands,

pub struct FromRefs<'a>(pub &'a User, pub &'a ChatIdPartiality);

pub(crate) async fn grow_impl(repos: &repo::Repositories, config: config::AppConfig, from_refs: FromRefs<'_>) -> anyhow::Result<String> {
pub(crate) async fn grow_impl(repos: &repo::Repositories, incr: Incrementor, from_refs: FromRefs<'_>) -> anyhow::Result<String> {
let (from, chat_id) = (from_refs.0, from_refs.1);
let name = utils::get_full_name(from);
let user = repos.users.create_or_update(from.id, &name).await?;
let days_since_registration = (Utc::now() - user.created_at).num_days() as u32;
let grow_shrink_ratio = if days_since_registration > config.newcomers_grace_days {
config.grow_shrink_ratio
} else {
1.0
};
let increment = gen_increment(config.growth_range, grow_shrink_ratio);
let grow_result = repos.dicks.create_or_grow(from.id, chat_id, increment).await;
let increment = incr.growth_increment(from.id, chat_id.kind(), days_since_registration).await;
let grow_result = repos.dicks.create_or_grow(from.id, chat_id, increment.total).await;
let lang_code = ensure_lang_code(Some(from));

let main_part = match grow_result {
Ok(repo::GrowthResult { new_length, pos_in_top }) => {
let event_key = if increment.is_negative() { "shrunk" } else { "grown" };
let event_key = if increment.total.is_negative() { "shrunk" } else { "grown" };
let event = t!(&format!("commands.grow.direction.{event_key}"), locale = &lang_code);
let answer = t!("commands.grow.result", locale = &lang_code,
event = event, incr = increment.abs(), length = new_length);
event = event, incr = increment.total.abs(), length = new_length);
if let Some(pos) = pos_in_top {
let position = t!("commands.grow.position", locale = &lang_code, pos = pos);
format!("{answer}\n{position}")
Expand All @@ -96,8 +89,9 @@ pub(crate) async fn grow_impl(repos: &repo::Repositories, config: config::AppCon
}
}
};
let perks_part = increment.perks_part_of_answer(&lang_code);
let time_left_part = utils::date::get_time_till_next_day_string(&lang_code);
Ok(format!("{main_part}{time_left_part}"))
Ok(format!("{main_part}{perks_part}{time_left_part}"))
}

pub(crate) struct Top {
Expand Down Expand Up @@ -249,27 +243,6 @@ pub fn build_pagination_keyboard(page: Page, has_more_pages: bool) -> InlineKeyb
InlineKeyboardMarkup::new(vec![buttons])
}

fn gen_increment(range: RangeInclusive<i32>, sign_ratio: f32) -> i32 {
let sign_ratio_percent = match (sign_ratio * 100.0).round() as u32 {
..=0 => 0,
100.. => 100,
x => x
};
let mut rng = OsRng;
if range.start() > &0 {
return rng.gen_range(range)
}
let positive = rng.gen_ratio(sign_ratio_percent, 100);
if positive {
let end = *range.end();
rng.gen_range(1..=end)
} else {
let start = *range.start();
rng.gen_range(start..=-1)
}

}

async fn answer_callback_feature_disabled(bot: Bot, q: CallbackQuery, edit_msg_req_params: EditMessageReqParamsKind) -> HandlerResult {
let lang_code = ensure_lang_code(Some(&q.from));

Expand All @@ -288,29 +261,3 @@ async fn answer_callback_feature_disabled(bot: Bot, q: CallbackQuery, edit_msg_r
};
Ok(())
}

#[cfg(test)]
mod test {
use super::gen_increment;

#[test]
fn test_gen_increment() {
let increments: Vec<i32> = (0..100)
.map(|_| gen_increment(-5..=10, 0.5))
.collect();
assert!(increments.iter().any(|n| n > &0));
assert!(increments.iter().any(|n| n < &0));
assert!(increments.iter().all(|n| n != &0));
assert!(increments.iter().all(|n| n <= &10));
assert!(increments.iter().all(|n| n >= &-5));
}

#[test]
fn test_gen_increment_with_positive_range() {
let increments: Vec<i32> = (0..100)
.map(|_| gen_increment(5..=10, 0.5))
.collect();
assert!(increments.iter().all(|n| n <= &10));
assert!(increments.iter().all(|n| n >= &5));
}
}
17 changes: 9 additions & 8 deletions src/handlers/dod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::borrow::Cow;
use anyhow::anyhow;
use rand::Rng;
use rand::rngs::OsRng;
use rust_i18n::t;
use teloxide::Bot;
use teloxide::macros::BotCommands;
use teloxide::types::{Message, UserId};
use crate::{config, metrics, repo};
use crate::{metrics, repo};
use crate::handlers::{ensure_lang_code, FromRefs, HandlerResult, reply_html, utils};
use crate::handlers::utils::Incrementor;

const DOD_ALREADY_CHOSEN_SQL_CODE: &str = "GD0E2";

Expand All @@ -20,23 +19,24 @@ pub enum DickOfDayCommands {
}

pub async fn dod_cmd_handler(bot: Bot, msg: Message,
repos: repo::Repositories, config: config::AppConfig) -> HandlerResult {
repos: repo::Repositories, incr: Incrementor) -> HandlerResult {
metrics::CMD_DOD_COUNTER.chat.inc();
let from = msg.from().ok_or(anyhow!("unexpected absence of a FROM field"))?;
let chat_id = msg.chat.id.into();
let from_refs = FromRefs(from, &chat_id);
let answer = dick_of_day_impl(&repos, config, from_refs).await?;
let answer = dick_of_day_impl(&repos, incr, from_refs).await?;
reply_html(bot, msg, answer).await?;
Ok(())
}

pub(crate) async fn dick_of_day_impl(repos: &repo::Repositories, config: config::AppConfig, from_refs: FromRefs<'_>) -> anyhow::Result<String> {
pub(crate) async fn dick_of_day_impl(repos: &repo::Repositories, incr: Incrementor, from_refs: FromRefs<'_>) -> anyhow::Result<String> {
let (from, chat_id) = (from_refs.0, from_refs.1);
let lang_code = ensure_lang_code(Some(from));
let winner = repos.users.get_random_active_member(&chat_id.kind()).await?;
let answer = match winner {
Some(winner) => {
let bonus: u32 = OsRng.gen_range(config.dod_bonus_range);
let increment = incr.dod_increment(from.id, chat_id.kind()).await;
let bonus = increment.total as u32;
let dod_result = repos.dicks.set_dod_winner(chat_id, UserId(winner.uid as u64), bonus).await;
let main_part = match dod_result {
Ok(Some(repo::GrowthResult{ new_length, pos_in_top })) => {
Expand Down Expand Up @@ -64,8 +64,9 @@ pub(crate) async fn dick_of_day_impl(repos: &repo::Repositories, config: config:
}
}
};
let perks_part = increment.perks_part_of_answer(&lang_code);
let time_left_part = utils::date::get_time_till_next_day_string(&lang_code);
format!("{main_part}{time_left_part}")
format!("{main_part}{perks_part}{time_left_part}")
},
None => t!("commands.dod.no_candidates", locale = &lang_code)
};
Expand Down
Loading

0 comments on commit 93c99a2

Please sign in to comment.