2022-07-29 19:32:35 +00:00
|
|
|
mod lib;
|
2022-08-01 20:06:45 +00:00
|
|
|
use crate::lib::{text_is_leet, time_is_leet};
|
2022-07-29 19:32:35 +00:00
|
|
|
|
2022-08-04 13:52:08 +00:00
|
|
|
use chrono::{NaiveDateTime, NaiveTime, Local};
|
2022-07-29 19:32:35 +00:00
|
|
|
use sqlx::AnyPool;
|
|
|
|
|
use sqlx::any::AnyPoolOptions;
|
|
|
|
|
use teloxide::{prelude::*, RequestError, dispatching::UpdateFilterExt, utils::command::BotCommands};
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
|
|
// DB
|
|
|
|
|
async fn init_db_pool(url: &str) -> Result<AnyPool, sqlx::Error> {
|
|
|
|
|
let pool = AnyPoolOptions::new()
|
|
|
|
|
.max_connections(5)
|
|
|
|
|
.connect(url).await?;
|
|
|
|
|
|
|
|
|
|
Ok(pool)
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
async fn setup_tables(pool: &sqlx::AnyPool) -> Result<(), sqlx::Error> {
|
|
|
|
|
|
|
|
|
|
let query_sql = match pool.any_kind() {
|
|
|
|
|
sqlx::any::AnyKind::Postgres => "create table if not exists leet_log (
|
|
|
|
|
id int generated always as identity,
|
|
|
|
|
username varchar(255) not null,
|
|
|
|
|
log_time timestamp not null
|
|
|
|
|
)" ,
|
|
|
|
|
sqlx::any::AnyKind::Sqlite => "create table if not exists leet_log (
|
|
|
|
|
id integer primary key autoincrement not null,
|
|
|
|
|
username varchar(255) not null,
|
|
|
|
|
log_time timestamp not null
|
|
|
|
|
)"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
sqlx::query(query_sql)
|
|
|
|
|
.execute(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
#[derive(Debug,Error)]
|
|
|
|
|
enum LeetError {
|
|
|
|
|
#[error("It's not leet right now")]
|
|
|
|
|
NotLeet,
|
|
|
|
|
#[error("Already hit leet for today")]
|
|
|
|
|
AlreadyHitLeet,
|
|
|
|
|
#[error("Sql error while checking for leet status: {0}")]
|
|
|
|
|
DbError(#[from] sqlx::Error)
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-08-01 20:06:45 +00:00
|
|
|
async fn check_leet(username: &str, sent_time: NaiveDateTime, pool: &AnyPool) -> Result<(), LeetError> {
|
|
|
|
|
if ! time_is_leet(sent_time.time()) {
|
2022-07-29 19:32:35 +00:00
|
|
|
return Err(LeetError::NotLeet);
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let latest_leet : Option<(i32, String, NaiveDateTime)> = sqlx::query_as("select * from leet_log where username = $1 order by log_time desc")
|
|
|
|
|
.bind(username)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
if let Some(latest_leet) = latest_leet {
|
2022-08-01 20:06:45 +00:00
|
|
|
let distance = sent_time - latest_leet.2;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-08-04 13:52:08 +00:00
|
|
|
if distance.num_hours() < 1 {
|
2022-07-29 19:32:35 +00:00
|
|
|
return Err(LeetError::AlreadyHitLeet);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-08-01 20:06:45 +00:00
|
|
|
async fn log_leet(username: &str, sent_time: NaiveDateTime, pool: &AnyPool) -> Result<(), sqlx::Error> {
|
|
|
|
|
sqlx::query("insert into leet_log (username, log_time) values ($1, $2)")
|
2022-07-29 19:32:35 +00:00
|
|
|
.bind(username)
|
2022-08-01 20:06:45 +00:00
|
|
|
.bind(sent_time)
|
2022-07-29 19:32:35 +00:00
|
|
|
.execute(pool)
|
|
|
|
|
.await?;
|
2019-05-07 18:54:42 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2019-05-07 18:54:42 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
async fn get_scores(pool: &AnyPool) -> Result<Vec<(String,i64)>, sqlx::Error> {
|
|
|
|
|
let score: Vec<_> = sqlx::query_as("select username, count(username) as score from leet_log group by username")
|
|
|
|
|
.fetch_all(pool)
|
|
|
|
|
.await?;
|
2019-05-07 18:54:42 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
Ok(score)
|
|
|
|
|
}
|
2019-05-07 18:54:42 +00:00
|
|
|
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
// Bot
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
fn message_ok() -> Result<(), MessageHandlerError> {
|
|
|
|
|
Ok(respond(())?)
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
|
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
#[derive(BotCommands, Clone)]
|
|
|
|
|
#[command(rename = "lowercase", description = "score commands")]
|
|
|
|
|
enum LeetCommand {
|
|
|
|
|
#[command(description = "get scores")]
|
2022-08-04 13:52:08 +00:00
|
|
|
Scores,
|
|
|
|
|
#[command(description = "get current time info")]
|
2022-08-04 16:30:09 +00:00
|
|
|
Time,
|
|
|
|
|
#[command(description = "show help about leetbot")]
|
|
|
|
|
Help
|
2022-07-29 19:32:35 +00:00
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
#[derive(Error,Debug)]
|
|
|
|
|
enum MessageHandlerError {
|
|
|
|
|
#[error("Telegram request error {0}")]
|
|
|
|
|
Request(#[from] RequestError),
|
|
|
|
|
#[error("No sender for message!")]
|
|
|
|
|
NoFrom,
|
|
|
|
|
#[error("Sender has no username!")]
|
|
|
|
|
NoUsername,
|
|
|
|
|
#[error("Internal sql error {0}")]
|
|
|
|
|
Db(#[from] sqlx::Error),
|
|
|
|
|
#[error("No score for user in db")]
|
|
|
|
|
NoScore,
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
fn filter_leet(message: Message) -> bool {
|
|
|
|
|
let text = match message.text() {
|
|
|
|
|
Some(t) => t,
|
|
|
|
|
None => { return false; }
|
|
|
|
|
};
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
text_is_leet(text)
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
async fn leet_message_handler(message: Message, bot: AutoSend<Bot>, pool: AnyPool) -> Result<(), MessageHandlerError> {
|
|
|
|
|
|
|
|
|
|
let username = message.from()
|
|
|
|
|
.ok_or(MessageHandlerError::NoFrom)?
|
|
|
|
|
.username.clone()
|
|
|
|
|
.ok_or(MessageHandlerError::NoUsername)?;
|
|
|
|
|
|
2022-08-04 13:52:08 +00:00
|
|
|
let message_sent_time = message.date
|
|
|
|
|
.with_timezone(&Local)
|
|
|
|
|
.naive_local();
|
2022-08-01 20:06:45 +00:00
|
|
|
|
|
|
|
|
let leet_check = check_leet(&username, message_sent_time, &pool).await;
|
2022-07-29 19:32:35 +00:00
|
|
|
|
|
|
|
|
if let Err(e) = leet_check {
|
|
|
|
|
match e {
|
|
|
|
|
LeetError::NotLeet => bot.send_message(message.chat.id, "it is not leet right now").await?,
|
|
|
|
|
LeetError::AlreadyHitLeet => bot.send_message(message.chat.id, "you already hit leet today!").await?,
|
|
|
|
|
LeetError::DbError(e) => {
|
|
|
|
|
bot.send_message(message.chat.id, "internal sql error").await?;
|
|
|
|
|
return Err(MessageHandlerError::Db(e));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
return message_ok();
|
|
|
|
|
}
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-08-01 20:06:45 +00:00
|
|
|
log_leet(&username, message_sent_time, &pool).await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let scores = get_scores(&pool).await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let user_score = scores.iter().find(|(u,_)| u == &username).ok_or(MessageHandlerError::NoScore)?.1;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let msg = format!("{} just hit leet! New score: {}", username, user_score);
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
bot.send_message(message.chat.id, msg).await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
message_ok()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn leet_command_handler(message: Message, cmd: LeetCommand, bot: AutoSend<Bot>, pool: AnyPool) -> Result<(), MessageHandlerError> {
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
match cmd {
|
|
|
|
|
LeetCommand::Scores => {
|
|
|
|
|
let scores = get_scores(&pool).await?;
|
2019-05-14 12:26:57 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let scores_str = scores.into_iter().map(|(name,score)| format!(" {}: {}\n", name, score)).collect::<String>();
|
2019-05-14 12:26:57 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let msg = format!("Leet scores:\n{}", scores_str);
|
2019-05-14 12:26:57 +00:00
|
|
|
|
2022-08-04 13:52:08 +00:00
|
|
|
bot.send_message(message.chat.id, msg).await?;
|
|
|
|
|
},
|
|
|
|
|
LeetCommand::Time => {
|
|
|
|
|
let message_time = message.date
|
|
|
|
|
.with_timezone(&Local)
|
|
|
|
|
.naive_local();
|
|
|
|
|
let current_time = Local::now().time();
|
|
|
|
|
let leet_time = NaiveTime::from_hms(13,37,30);
|
|
|
|
|
let distance_to_leet = leet_time - current_time;
|
|
|
|
|
|
|
|
|
|
let msg = format!("My current time is: {}
|
|
|
|
|
message was sent at: {}
|
|
|
|
|
leet is at: {}, in {} seconds",
|
|
|
|
|
current_time,
|
|
|
|
|
message_time,
|
|
|
|
|
leet_time,
|
|
|
|
|
distance_to_leet.num_seconds()
|
|
|
|
|
);
|
|
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
bot.send_message(message.chat.id, msg).await?;
|
2022-08-04 16:30:09 +00:00
|
|
|
},
|
|
|
|
|
LeetCommand::Help => {
|
|
|
|
|
bot.send_message(message.chat.id, LeetCommand::descriptions().to_string()).await?;
|
2022-07-29 19:32:35 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-05-14 12:26:57 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
message_ok()
|
|
|
|
|
}
|
2019-07-02 19:09:10 +00:00
|
|
|
|
|
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
pretty_env_logger::init();
|
2019-07-02 19:09:10 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let db_url = std::env::var("DB_URL")
|
|
|
|
|
.unwrap_or_else(|_| "sqlite::memory:".to_string());
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let db_pool = init_db_pool(&db_url).await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
setup_tables(&db_pool).await?;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let telegram_token = std::env::var("TELEGRAM_BOT_TOKEN")
|
|
|
|
|
.expect("TELEGRAM_TOKEN env not set!");
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let bot = Bot::new(&telegram_token).auto_send();
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
let handler = Update::filter_message()
|
|
|
|
|
.branch(dptree::entry()
|
|
|
|
|
.filter_command::<LeetCommand>()
|
|
|
|
|
.endpoint(leet_command_handler))
|
|
|
|
|
.branch(dptree::entry()
|
|
|
|
|
.filter(filter_leet)
|
|
|
|
|
.endpoint(leet_message_handler))
|
|
|
|
|
;
|
|
|
|
|
|
|
|
|
|
Dispatcher::builder(bot, handler)
|
|
|
|
|
.dependencies(dptree::deps![db_pool])
|
2022-09-15 19:34:52 +00:00
|
|
|
.default_handler(|_upd| async move {
|
2022-07-29 19:32:35 +00:00
|
|
|
})
|
|
|
|
|
.error_handler(LoggingErrorHandler::with_custom_text("error during dispatch"))
|
|
|
|
|
.enable_ctrlc_handler()
|
|
|
|
|
.build()
|
|
|
|
|
.dispatch()
|
|
|
|
|
.await;
|
2019-05-07 18:00:23 +00:00
|
|
|
|
2022-07-29 19:32:35 +00:00
|
|
|
Ok(())
|
2019-05-07 18:00:23 +00:00
|
|
|
}
|