Skip to content

Commit

Permalink
Merge pull request #4 from kozalosev/bugfix/0.1.3
Browse files Browse the repository at this point in the history
Bugfix/0.1.3
  • Loading branch information
Leonid Kozarin authored Oct 21, 2023
2 parents bc2593a + 33d402c commit 9b57e61
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 45 deletions.
11 changes: 7 additions & 4 deletions locales/en.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
commands:
grow:
result: "Your dick has been grown by <b>%{incr} cm</b> and now it is <b>%{length} cm</b> long"
tomorrow: "Your dick has been already grown today. Come back tomorrow!"
result: "Your dick has been grown by <b>%{incr} cm</b> and now it is <b>%{length} cm</b> long.\nYour position in the top is <b>%{pos}</b>."
tomorrow: "Your dick has been already grown today."
top:
title: "Top of the biggest dicks:"
line: "%{n}|<b>%{name}</b> — <b>%{length}</b> cm"
empty: "No one is in the game yet :("
dod:
result: "The Dick of the Day is <b>%{name}</b>!\n\nHis dick has become longer for <b>%{growth} cm</b> and is <b>%{length} cm</b> long now"
already_chosen: "The Dick of the Day has been already chosen for today! It's <b>%{name}</b>"
result: "The Dick of the Day is <b>%{name}</b>!\n\nHis dick has become longer for <b>%{growth} cm</b> and is <b>%{length}</b> cm long now.\nHis position in the top is <b>%{pos}</b>."
already_chosen: "The Dick of the Day has been already chosen for today! It's <b>%{name}</b>."
no_candidates: "There is no candidates for election. In this chat nobody is in the game yet 😢"
import:
result:
Expand Down Expand Up @@ -37,5 +37,8 @@ commands:
no_dicks: "It seems you don't have any dicks yet. 🤔 Right now is the time to add me into a chat and execute the <code>/grow</code> command!"
titles:
greeting: "Hello"
time_till_next_day:
none: " Come back tomorrow!"
some: "\n\nThe next attempt in <b>%{hours}</b>h <b>%{minutes}</b>m."
errors:
not_group_chat: "This bot is supposed to do its mission in group chats only! <i>(for the testing period: in a specific list of chats)</i>"
11 changes: 7 additions & 4 deletions locales/ru.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
commands:
grow:
result: "Ваша пися выросла на %{incr} см и теперь её длина составляет %{length} см"
tomorrow: "Вы уже растили пиписю сегодня. Возвращайтесь завтра!"
result: "Твоя пися выросла на %{incr} см и теперь её длина составляет %{length} см.\nТы занимаешь %{pos} место в топе."
tomorrow: "Ты уже растил пиписю сегодня."
top:
title: "Топ самых больших пиписек:"
line: "%{n}|<b>%{name}</b> — <b>%{length}</b> см"
empty: "Никто пока не участвует в игре :("
dod:
result: "Пам-пам-пам! Писюн Дня — <b>%{name}</b>!\n\nЕго пиписик вырос на <b>%{growth} см</b> и теперь длиной <b>%{length} см</b>"
already_chosen: "Писюн Дня уже был выбран на сегодня! Это <b>%{name}</b>"
result: "Пам-пам-пам! Писюн Дня — <b>%{name}</b>!\n\nЕго пиписик вырос на <b>%{growth} см</b> и теперь длиной <b>%{length}</b> см.\nОн занимает %{pos} место в топе."
already_chosen: "Писюн Дня уже был выбран на сегодня! Это <b>%{name}</b>."
no_candidates: "Не из кого выбирать: в этом чате ещё никто не участвует в игре 😢"
import:
result:
Expand Down Expand Up @@ -37,5 +37,8 @@ commands:
no_dicks: "Кажется, ты ещё не начал растить ни одного писюна? 🤔 Сейчас самое время добавить меня в какой-либо чат и выполнить команду <code>/grow</code>!"
titles:
greeting: "Приветствую"
time_till_next_day:
none: " Возвращайся завтра!"
some: "\n\nСледующая попытка через <b>%{hours}</b> ч. <b>%{minutes}</b> мин."
errors:
not_group_chat: "Бот выполняет свою миссию только в групповых чатах! <i>(на время тестового периода: только в строго определённом списке чатов)</i>"
25 changes: 14 additions & 11 deletions src/handlers/dick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rust_i18n::t;
use teloxide::Bot;
use teloxide::macros::BotCommands;
use teloxide::types::Message;
use crate::handlers::{ensure_lang_code, HandlerResult, reply_html};
use crate::handlers::{ensure_lang_code, HandlerResult, reply_html, utils};
use crate::{config, metrics, repo};

const TOMORROW_SQL_CODE: &str = "GD0E1";
Expand Down Expand Up @@ -38,42 +38,45 @@ pub async fn dick_cmd_handler(bot: Bot, msg: Message, cmd: DickCommands,
};
let increment = gen_increment(config.growth_range, grow_shrink_ratio);
let grow_result = repos.dicks.create_or_grow(from.id, msg.chat.id, increment).await;
let lang_code = ensure_lang_code(Some(from));

match grow_result {
Ok(new_length) => {
let lang_code = ensure_lang_code(Some(from));
t!("commands.grow.result", locale = lang_code.as_str(), incr = increment, length = new_length)
let main_part = match grow_result {
Ok(repo::GrowthResult { new_length, pos_in_top }) => {
t!("commands.grow.result", locale = &lang_code,
incr = increment, length = new_length, pos = pos_in_top)
},
Err(e) => {
let db_err = e.downcast::<sqlx::Error>()?;
if let sqlx::Error::Database(e) = db_err {
e.code()
.filter(|c| c == TOMORROW_SQL_CODE)
.map(|_| t!("commands.grow.tomorrow"))
.map(|_| t!("commands.grow.tomorrow", locale = &lang_code))
.ok_or(anyhow!(e))?
} else {
Err(db_err)?
}
}
}
};
let time_left_part = utils::date::get_time_till_next_day_string(&lang_code);
format!("{main_part}{time_left_part}")
},
DickCommands::Grow => Err("unexpected absence of a FROM field for the /grow command")?,
DickCommands::Top => {
metrics::CMD_TOP_COUNTER.inc();

let lang_code = ensure_lang_code(msg.from());
let title = t!("commands.top.title", locale = lang_code.as_str());
let title = t!("commands.top.title", locale = &lang_code);
let lines = repos.dicks.get_top(msg.chat.id)
.await?
.iter().enumerate()
.map(|(i, d)| {
let name = teloxide::utils::html::escape(d.owner_name.as_str());
t!("commands.top.line", locale = lang_code.as_str(), n = i+1, name = name, length = d.length)
let name = teloxide::utils::html::escape(&d.owner_name);
t!("commands.top.line", locale = &lang_code, n = i+1, name = name, length = d.length)
})
.collect::<Vec<String>>();

if lines.is_empty() {
t!("commands.top.empty", locale = lang_code.as_str())
t!("commands.top.empty", locale = &lang_code)
} else {
format!("{}\n\n{}", title, lines.join("\n"))
}
Expand Down
14 changes: 9 additions & 5 deletions src/handlers/dod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use teloxide::Bot;
use teloxide::macros::BotCommands;
use teloxide::types::Message;
use crate::{config, metrics, repo};
use crate::handlers::{ensure_lang_code, HandlerResult, reply_html};
use crate::handlers::{ensure_lang_code, HandlerResult, reply_html, utils};

const DOD_ALREADY_CHOSEN_SQL_CODE: &str = "GD0E2";

Expand All @@ -27,9 +27,11 @@ pub async fn dod_cmd_handler(bot: Bot, msg: Message,
Some(winner) => {
let bonus: u32 = OsRng::default().gen_range(config.dod_bonus_range);
let dod_result = repos.dicks.set_dod_winner(chat_id, repo::UID(winner.uid), bonus).await;
match dod_result {
Ok(new_length) => t!("commands.dod.result", locale = &lang_code,
name = winner.name, growth = bonus, length = new_length),
let main_part = match dod_result {
Ok(repo::GrowthResult{ new_length, pos_in_top }) => {
t!("commands.dod.result", locale = &lang_code,
name = winner.name, growth = bonus, length = new_length, pos = pos_in_top)
},
Err(e) => {
match e.downcast::<sqlx::Error>()? {
sqlx::Error::Database(e)
Expand All @@ -39,7 +41,9 @@ pub async fn dod_cmd_handler(bot: Bot, msg: Message,
e => Err(e)?
}
}
}
};
let time_left_part = utils::date::get_time_till_next_day_string(&lang_code);
format!("{main_part}{time_left_part}")
},
None => t!("commands.dod.no_candidates", locale = &lang_code)
};
Expand Down
57 changes: 46 additions & 11 deletions src/handlers/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum ImportCommands {
Import
}

#[cfg_attr(test, derive(PartialEq, Eq))]
enum OriginalBotKind {
PIPISA,
KRAFT28,
Expand All @@ -33,22 +34,20 @@ impl OriginalBotKind {
fn convert_name(&self, name: &str) -> String {
match self {
OriginalBotKind::PIPISA => {
if name.len() > 13 {
&name[0..13]
} else {
name
}
name.chars()
.take(13)
.collect()
},
OriginalBotKind::KRAFT28 => name
}.to_owned()
OriginalBotKind::KRAFT28 => name.to_owned()
}
}
}

impl TryFrom<&String> for OriginalBotKind {
impl TryFrom<&str> for OriginalBotKind {
type Error = String;

fn try_from(value: &String) -> Result<Self, Self::Error> {
match value.as_str().trim_start_matches('@') {
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.trim_start_matches('@') {
"pipisabot" => Ok(OriginalBotKind::PIPISA),
"kraft28_bot" => Ok(OriginalBotKind::KRAFT28),
_ => Err("Unknown OriginalBotKind".to_owned())
Expand Down Expand Up @@ -205,7 +204,7 @@ fn check_reply_source_and_text(reply: &Message) -> Option<ParseResult> {
.and_then(|u| u.username.as_ref())
.filter(|name| ORIGINAL_BOT_USERNAMES.contains(&name.as_ref()))
.and_then(|name| {
let name = name.try_into()
let name = name.as_str().try_into()
.map_err(|name| log::error!("couldn't convert name: {name}"))
.ok();
if let (Some(name), Some(text)) = (name, reply.text()) {
Expand Down Expand Up @@ -296,3 +295,39 @@ fn map_user(pos: Captures) -> Option<OriginalUser> {
}
None
}

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

#[test]
fn original_bot_kind_convert_name_pipisa() {
let (p, short) = (OriginalBotKind::PIPISA, "SadBot #incel".to_owned());
assert_eq!(p.convert_name("SadBot #incel..."), short);
assert_eq!(p.convert_name("SadBot #incel>suicide"), short);
}

#[test]
fn original_bot_kind_try_from() {
let check = |variant: &str, kind| {
let second_variant = variant.strip_prefix("@").expect("no '@' prefix");
let valid_variants = [variant, &second_variant];
assert!(valid_variants.into_iter()
.all(|v| OriginalBotKind::try_from(v)
.is_ok_and(|k| k == kind)));

let invalid_variants = valid_variants.into_iter()
.map(|v| {
v.strip_suffix("_bot")
.or_else (|| v.strip_suffix("bot"))
.expect("no 'bot' suffix")
});
assert!(invalid_variants.into_iter()
.all(|v| OriginalBotKind::try_from(v)
.is_err()));
} ;

check("@pipisabot", OriginalBotKind::PIPISA);
check("@kraft28_bot", OriginalBotKind::KRAFT28);
}
}
14 changes: 10 additions & 4 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ mod help;
mod dod;
mod import;
mod promo;
mod utils;

use std::borrow::ToOwned;
use teloxide::Bot;
use teloxide::requests::Requester;
use teloxide::types::{Message, User};
Expand All @@ -19,15 +21,19 @@ pub type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;

pub fn ensure_lang_code(user: Option<&User>) -> String {
user
.map(|u| {
u.language_code.clone()
.and_then(|u| {
u.language_code.as_ref()
.or_else(|| {
log::warn!("no language_code for {}, using the default", u.id);
None
})
})
.flatten()
.unwrap_or("en".to_owned())
.map(|code| match &code[..2] {
"uk" | "be" => "ru",
_ => code
})
.unwrap_or("en")
.to_owned()
}

pub async fn reply_html(bot: Bot, msg: Message, answer: String) -> HandlerResult {
Expand Down
39 changes: 39 additions & 0 deletions src/handlers/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
pub mod date {
use chrono::{DateTime, Duration, Timelike, Utc};
use rust_i18n::t;

pub fn get_time_till_next_day_string(lang_code: &str) -> String {
let now = if cfg!(test) {
DateTime::parse_from_rfc3339("2023-10-21T22:10:57+00:00")
.expect("invalid datetime string")
.into()
} else {
Utc::now()
};
Some(now + Duration::days(1))
.and_then(|d| d.with_hour(0))
.and_then(|d| d.with_minute(0))
.and_then(|d| d.with_second(0))
.map(|tomorrow| tomorrow - now)
.map(|time_left| {
let hrs = time_left.num_hours();
let mins = time_left.num_minutes() - hrs * 60;
t!("titles.time_till_next_day.some", locale = lang_code,
hours = hrs, minutes = mins)
})
.unwrap_or(t!("titles.time_till_next_day.none", locale = lang_code))
}
}

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

#[test]
fn get_time_till_next_day_string() {
let expected = "<b>1</b>h <b>49</b>m.";
let actual = date::get_time_till_next_day_string("en");
let actual = &actual[expected.len()+1..];
assert_eq!(expected, actual)
}
}
37 changes: 31 additions & 6 deletions src/repo/dicks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ pub struct Dick {
pub owner_name: String,
}

pub struct GrowthResult {
pub new_length: i32,
pub pos_in_top: u64,
}

repository!(Dicks,
pub async fn create_or_grow(&self, uid: UserId, chat_id: ChatId, increment: i32) -> anyhow::Result<i32> {
pub async fn create_or_grow(&self, uid: UserId, chat_id: ChatId, increment: i32) -> anyhow::Result<GrowthResult> {
let uid: i64 = uid.0.try_into()?;
sqlx::query("INSERT INTO dicks(uid, chat_id, length) VALUES ($1, $2, $3) ON CONFLICT (uid, chat_id) DO UPDATE SET length = (dicks.length + $3) RETURNING length")
let new_length = sqlx::query("INSERT INTO dicks(uid, chat_id, length) VALUES ($1, $2, $3)
ON CONFLICT (uid, chat_id) DO UPDATE SET length = (dicks.length + $3)
RETURNING length")
.bind(uid)
.bind(chat_id.0)
.bind(increment)
.fetch_one(&self.pool)
.await?
.try_get("length")
.map_err(|e| e.into())
.try_get("length")?;
let pos_in_top = self.get_position_in_top(chat_id, uid).await? as u64;
Ok(GrowthResult { new_length, pos_in_top })
}
,
pub async fn get_top(&self, chat_id: ChatId) -> anyhow::Result<Vec<Dick>, sqlx::Error> {
Expand All @@ -29,12 +37,29 @@ repository!(Dicks,
.await
}
,
pub async fn set_dod_winner(&self, chat_id: ChatId, user_id: UID, bonus: u32) -> anyhow::Result<i32> {
pub async fn set_dod_winner(&self, chat_id: ChatId, user_id: UID, bonus: u32) -> anyhow::Result<GrowthResult> {
let mut tx = self.pool.begin().await?;
let new_length = Self::grow_dods_dick(&mut tx, chat_id, user_id, bonus.try_into()?).await?;
Self::insert_to_dod_table(&mut tx, chat_id, user_id).await?;
let pos_in_top = self.get_position_in_top(chat_id, user_id.0).await? as u64;
tx.commit().await?;
Ok(new_length)
Ok(GrowthResult { new_length, pos_in_top })
}
,
async fn get_position_in_top(&self, chat_id: ChatId, uid: i64) -> anyhow::Result<i64> {
sqlx::query("
WITH top AS (
SELECT chat_id, uid, length FROM dicks WHERE chat_id = $1
) SELECT
ROW_NUMBER() OVER(ORDER BY top.length DESC) as position
FROM top
WHERE uid = $2")
.bind(chat_id.0)
.bind(uid)
.fetch_one(&self.pool)
.await?
.try_get::<i64, _>("position")
.map_err(|e| e.into())
}
,
async fn grow_dods_dick(tx: &mut Transaction<'_, Postgres>, chat_id: ChatId, user_id: UID, bonus: i32) -> anyhow::Result<i32> {
Expand Down

0 comments on commit 9b57e61

Please sign in to comment.