1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-24 20:14:19 +00:00

Run "Reformat code..." IDE feature

This commit is contained in:
Steffo 2024-08-19 07:54:41 +02:00
parent b84b70080f
commit ec3f5daff7
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
34 changed files with 67626 additions and 67621 deletions

View file

@ -4,7 +4,7 @@
#[cfg(feature = "interface_database")] #[cfg(feature = "interface_database")]
pub mod interface_database { pub mod interface_database {
use micronfig::config; use micronfig::config;
config! { config! {
DATABASE_AUTOMIGRATE: String > bool, DATABASE_AUTOMIGRATE: String > bool,
DATABASE_URL: String, DATABASE_URL: String,
@ -14,7 +14,7 @@ pub mod interface_database {
#[cfg(feature = "service_telegram")] #[cfg(feature = "service_telegram")]
pub mod service_telegram { pub mod service_telegram {
use micronfig::config; use micronfig::config;
config! { config! {
TELEGRAM_DATABASE_URL: String, TELEGRAM_DATABASE_URL: String,
TELEGRAM_BOT_TOKEN: String, TELEGRAM_BOT_TOKEN: String,
@ -25,7 +25,7 @@ pub mod service_telegram {
#[cfg(feature = "service_brooch")] #[cfg(feature = "service_brooch")]
pub mod brooch { pub mod brooch {
use micronfig::config; use micronfig::config;
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
config! { config! {
BROOCH_DATABASE_URL: String, BROOCH_DATABASE_URL: String,
@ -66,7 +66,7 @@ impl From<i64> for TimeDeltaConversionHack {
impl TryFrom<TimeDeltaConversionHack> for chrono::TimeDelta { impl TryFrom<TimeDeltaConversionHack> for chrono::TimeDelta {
type Error = (); type Error = ();
fn try_from(value: TimeDeltaConversionHack) -> Result<Self, Self::Error> { fn try_from(value: TimeDeltaConversionHack) -> Result<Self, Self::Error> {
Self::new(value.0, 0).ok_or(()) Self::new(value.0, 0).ok_or(())
} }

View file

@ -9,13 +9,13 @@ pub(self) mod config;
pub struct RoyalnetInstance { pub struct RoyalnetInstance {
#[cfg(feature = "service_telegram")] #[cfg(feature = "service_telegram")]
service_telegram: crate::services::telegram::TelegramService, service_telegram: crate::services::telegram::TelegramService,
#[cfg(not(feature = "service_telegram"))] #[cfg(not(feature = "service_telegram"))]
service_telegram: (), service_telegram: (),
#[cfg(feature = "service_brooch")] #[cfg(feature = "service_brooch")]
service_brooch: crate::services::brooch::BroochService, service_brooch: crate::services::brooch::BroochService,
#[cfg(not(feature = "service_brooch"))] #[cfg(not(feature = "service_brooch"))]
service_brooch: (), service_brooch: (),
} }
@ -27,83 +27,83 @@ impl RoyalnetInstance {
service_brooch: Self::setup_brooch_service().await, service_brooch: Self::setup_brooch_service().await,
} }
} }
pub async fn run(mut self) { pub async fn run(mut self) {
Self::run_pending_migrations(); Self::run_pending_migrations();
let future_telegram = async move { let future_telegram = async move {
Self::get_telegram_future(&mut self.service_telegram).await; Self::get_telegram_future(&mut self.service_telegram).await;
}; };
let future_brooch = async move { let future_brooch = async move {
Self::get_brooch_future(&mut self.service_brooch).await; Self::get_brooch_future(&mut self.service_brooch).await;
}; };
let task_telegram = tokio::spawn(future_telegram); let task_telegram = tokio::spawn(future_telegram);
let task_brooch = tokio::spawn(future_brooch); let task_brooch = tokio::spawn(future_brooch);
let _ = tokio::join!( let _ = tokio::join!(
task_telegram, task_telegram,
task_brooch, task_brooch,
); );
} }
#[cfg(feature = "interface_database")] #[cfg(feature = "interface_database")]
fn run_pending_migrations() { fn run_pending_migrations() {
if !config::interface_database::DATABASE_AUTOMIGRATE() { if !config::interface_database::DATABASE_AUTOMIGRATE() {
log::warn!("Database automigration is disabled."); log::warn!("Database automigration is disabled.");
return return;
} }
log::debug!("Automatically applying database migrations..."); log::debug!("Automatically applying database migrations...");
log::trace!("Connecting to the database..."); log::trace!("Connecting to the database...");
let mut db = crate::interfaces::database::connect( let mut db = crate::interfaces::database::connect(
config::interface_database::DATABASE_URL() config::interface_database::DATABASE_URL()
).expect("Unable to connect to the database to apply migrations."); ).expect("Unable to connect to the database to apply migrations.");
log::trace!("Applying migrations..."); log::trace!("Applying migrations...");
crate::interfaces::database::migrate(&mut db) crate::interfaces::database::migrate(&mut db)
.expect("Failed to automatically apply migrations to the database."); .expect("Failed to automatically apply migrations to the database.");
log::trace!("Migration successful!"); log::trace!("Migration successful!");
} }
#[cfg(not(feature = "interface_database"))] #[cfg(not(feature = "interface_database"))]
fn run_pending_migrations() { fn run_pending_migrations() {
log::warn!("Database automigration is not compiled in."); log::warn!("Database automigration is not compiled in.");
} }
#[cfg(feature = "service_telegram")] #[cfg(feature = "service_telegram")]
async fn setup_telegram_service() -> crate::services::telegram::TelegramService { async fn setup_telegram_service() -> crate::services::telegram::TelegramService {
log::debug!("Setting up Telegram service..."); log::debug!("Setting up Telegram service...");
crate::services::telegram::TelegramService::new( crate::services::telegram::TelegramService::new(
config::service_telegram::TELEGRAM_DATABASE_URL().clone(), config::service_telegram::TELEGRAM_DATABASE_URL().clone(),
config::service_telegram::TELEGRAM_BOT_TOKEN().clone(), config::service_telegram::TELEGRAM_BOT_TOKEN().clone(),
*config::service_telegram::TELEGRAM_NOTIFICATION_CHATID(), *config::service_telegram::TELEGRAM_NOTIFICATION_CHATID(),
).await.expect("Unable to setup Telegram service.") ).await.expect("Unable to setup Telegram service.")
} }
#[cfg(not(feature = "service_telegram"))] #[cfg(not(feature = "service_telegram"))]
async fn setup_telegram_service() { async fn setup_telegram_service() {
log::warn!("Telegram service is not compiled in."); log::warn!("Telegram service is not compiled in.");
} }
#[cfg(feature = "service_telegram")] #[cfg(feature = "service_telegram")]
fn get_telegram_future(service: &mut crate::services::telegram::TelegramService) -> impl Future<Output = ()> + '_ { fn get_telegram_future(service: &mut crate::services::telegram::TelegramService) -> impl Future<Output=()> + '_ {
service.run_loop() service.run_loop()
} }
#[cfg(not(feature = "service_telegram"))] #[cfg(not(feature = "service_telegram"))]
#[allow(clippy::manual_async_fn)] #[allow(clippy::manual_async_fn)]
fn get_telegram_future(_service: &mut ()) -> impl Future<Output = ()> + '_ { fn get_telegram_future(_service: &mut ()) -> impl Future<Output=()> + '_ {
async {} async {}
} }
#[cfg(feature = "service_brooch")] #[cfg(feature = "service_brooch")]
async fn setup_brooch_service() -> crate::services::brooch::BroochService { async fn setup_brooch_service() -> crate::services::brooch::BroochService {
log::debug!("Setting up Brooch service..."); log::debug!("Setting up Brooch service...");
crate::services::brooch::BroochService::new( crate::services::brooch::BroochService::new(
config::brooch::BROOCH_DATABASE_URL().clone(), config::brooch::BROOCH_DATABASE_URL().clone(),
config::brooch::BROOCH_GRAPHQL_URL(), config::brooch::BROOCH_GRAPHQL_URL(),
@ -115,20 +115,20 @@ impl RoyalnetInstance {
*config::brooch::BROOCH_MAX_IMP_WAIT_SECS(), *config::brooch::BROOCH_MAX_IMP_WAIT_SECS(),
).expect("Unable to setup Brooch service.") ).expect("Unable to setup Brooch service.")
} }
#[cfg(not(feature = "service_brooch"))] #[cfg(not(feature = "service_brooch"))]
async fn setup_brooch_service() { async fn setup_brooch_service() {
log::warn!("Brooch service is not compiled in."); log::warn!("Brooch service is not compiled in.");
} }
#[cfg(feature = "service_brooch")] #[cfg(feature = "service_brooch")]
fn get_brooch_future(service: &mut crate::services::brooch::BroochService) -> impl Future<Output = ()> + '_ { fn get_brooch_future(service: &mut crate::services::brooch::BroochService) -> impl Future<Output=()> + '_ {
service.run_loop() service.run_loop()
} }
#[cfg(not(feature = "service_brooch"))] #[cfg(not(feature = "service_brooch"))]
#[allow(clippy::manual_async_fn)] #[allow(clippy::manual_async_fn)]
fn get_brooch_future(_service: &mut ()) -> impl Future<Output = ()> + '_ { fn get_brooch_future(_service: &mut ()) -> impl Future<Output=()> + '_ {
async {} async {}
} }
} }

View file

@ -20,9 +20,9 @@ impl BroochMatch {
pub fn is_flagged(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<bool> { pub fn is_flagged(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<bool> {
use crate::interfaces::database::query_prelude::*; use crate::interfaces::database::query_prelude::*;
use schema::brooch_match; use schema::brooch_match;
log::trace!("Checking if {match_id:?} is flagged..."); log::trace!("Checking if {match_id:?} is flagged...");
Ok( Ok(
brooch_match::table brooch_match::table
.find(match_id) .find(match_id)
@ -32,13 +32,13 @@ impl BroochMatch {
.gt(&0usize) .gt(&0usize)
) )
} }
pub fn flag(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Self> { pub fn flag(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Self> {
use crate::interfaces::database::query_prelude::*; use crate::interfaces::database::query_prelude::*;
use schema::brooch_match; use schema::brooch_match;
log::debug!("Flagging {match_id:?} as parsed..."); log::debug!("Flagging {match_id:?} as parsed...");
diesel::insert_into(brooch_match::table) diesel::insert_into(brooch_match::table)
.values(brooch_match::id.eq(match_id)) .values(brooch_match::id.eq(match_id))
.on_conflict_do_nothing() .on_conflict_do_nothing()

View file

@ -29,7 +29,7 @@ impl MatchmakingEvent {
.get_result::<Self>(database) .get_result::<Self>(database)
.context("Non è stato possibile aggiungere il matchmaking al database RYG.") .context("Non è stato possibile aggiungere il matchmaking al database RYG.")
} }
/// Retrieve a [MatchmakingEvent] from the database, given its [MatchmakingId]. /// Retrieve a [MatchmakingEvent] from the database, given its [MatchmakingId].
pub fn get(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Self> { pub fn get(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Self> {
matchmaking_events::table matchmaking_events::table
@ -37,7 +37,7 @@ impl MatchmakingEvent {
.get_result::<Self>(database) .get_result::<Self>(database)
.context("Non è stato possibile recuperare il matchmaking dal database RYG.") .context("Non è stato possibile recuperare il matchmaking dal database RYG.")
} }
pub fn has_started(&self) -> bool { pub fn has_started(&self) -> bool {
self.starts_at.lt(&chrono::Local::now().naive_utc()) self.starts_at.lt(&chrono::Local::now().naive_utc())
} }

View file

@ -51,26 +51,26 @@ pub(crate) mod telegram_ext {
Cant, Cant,
Wont, Wont,
} }
impl MatchmakingTelegramKeyboardCallback { impl MatchmakingTelegramKeyboardCallback {
/// Create callback data representing the [MatchmakingTelegramKeyboardCallback] in the given [MatchmakingId]. /// Create callback data representing the [MatchmakingTelegramKeyboardCallback] in the given [MatchmakingId].
pub fn callback_data(self, matchmaking_id: MatchmakingId) -> String { pub fn callback_data(self, matchmaking_id: MatchmakingId) -> String {
matchmaking_id.callback_data(self.into()) matchmaking_id.callback_data(self.into())
} }
pub fn inline_button(self, matchmaking_id: MatchmakingId, text: &str) -> teloxide::types::InlineKeyboardButton { pub fn inline_button(self, matchmaking_id: MatchmakingId, text: &str) -> teloxide::types::InlineKeyboardButton {
teloxide::types::InlineKeyboardButton::new( teloxide::types::InlineKeyboardButton::new(
text, text,
teloxide::types::InlineKeyboardButtonKind::CallbackData( teloxide::types::InlineKeyboardButtonKind::CallbackData(
self.callback_data(matchmaking_id) self.callback_data(matchmaking_id)
) ),
) )
} }
} }
impl FromStr for MatchmakingTelegramKeyboardCallback { impl FromStr for MatchmakingTelegramKeyboardCallback {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok( Ok(
match s { match s {
@ -87,7 +87,7 @@ pub(crate) mod telegram_ext {
) )
} }
} }
impl From<MatchmakingTelegramKeyboardCallback> for &'static str { impl From<MatchmakingTelegramKeyboardCallback> for &'static str {
fn from(value: MatchmakingTelegramKeyboardCallback) -> Self { fn from(value: MatchmakingTelegramKeyboardCallback) -> Self {
match value { match value {
@ -102,22 +102,22 @@ pub(crate) mod telegram_ext {
} }
} }
} }
impl MatchmakingMessageTelegram { impl MatchmakingMessageTelegram {
/// Get all the [MatchmakingMessageTelegram] for a specific [MatchmakingId]. /// Get all the [MatchmakingMessageTelegram] for a specific [MatchmakingId].
pub fn get_all(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<Self>> { pub fn get_all(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<Self>> {
use diesel::prelude::*; use diesel::prelude::*;
use crate::interfaces::database::schema::matchmaking_messages_telegram; use crate::interfaces::database::schema::matchmaking_messages_telegram;
matchmaking_messages_telegram::table matchmaking_messages_telegram::table
.filter(matchmaking_messages_telegram::matchmaking_id.eq(matchmaking_id.0)) .filter(matchmaking_messages_telegram::matchmaking_id.eq(matchmaking_id.0))
.get_results::<MatchmakingMessageTelegram>(database) .get_results::<MatchmakingMessageTelegram>(database)
.context("La query al database RYG è fallita.") .context("La query al database RYG è fallita.")
} }
fn reply_markup(matchmaking_id: MatchmakingId) -> teloxide::types::InlineKeyboardMarkup { fn reply_markup(matchmaking_id: MatchmakingId) -> teloxide::types::InlineKeyboardMarkup {
use MatchmakingTelegramKeyboardCallback::*; use MatchmakingTelegramKeyboardCallback::*;
let button_yes = Yes.inline_button(matchmaking_id, "🔵 Ci sarò!"); let button_yes = Yes.inline_button(matchmaking_id, "🔵 Ci sarò!");
let button_5min = Plus5Min.inline_button(matchmaking_id, "🕐 +5 min"); let button_5min = Plus5Min.inline_button(matchmaking_id, "🕐 +5 min");
let button_15min = Plus15Min.inline_button(matchmaking_id, "🕒 +15 min"); let button_15min = Plus15Min.inline_button(matchmaking_id, "🕒 +15 min");
@ -126,7 +126,7 @@ pub(crate) mod telegram_ext {
let button_dontw = DontWait.inline_button(matchmaking_id, "❓ Non aspettatemi."); let button_dontw = DontWait.inline_button(matchmaking_id, "❓ Non aspettatemi.");
let button_cant = Cant.inline_button(matchmaking_id, "🔺 Non posso..."); let button_cant = Cant.inline_button(matchmaking_id, "🔺 Non posso...");
let button_wont = Wont.inline_button(matchmaking_id, "🔻 Non mi interessa."); let button_wont = Wont.inline_button(matchmaking_id, "🔻 Non mi interessa.");
teloxide::types::InlineKeyboardMarkup::new(vec![ teloxide::types::InlineKeyboardMarkup::new(vec![
vec![button_yes], vec![button_yes],
vec![button_5min, button_15min, button_60min], vec![button_5min, button_15min, button_60min],
@ -134,20 +134,20 @@ pub(crate) mod telegram_ext {
vec![button_cant, button_wont], vec![button_cant, button_wont],
]) ])
} }
fn text(event: &MatchmakingEvent, replies: &Vec<(MatchmakingReply, RoyalnetUser, TelegramUser)>) -> String { fn text(event: &MatchmakingEvent, replies: &Vec<(MatchmakingReply, RoyalnetUser, TelegramUser)>) -> String {
use std::fmt::Write; use std::fmt::Write;
let mut result = String::new(); let mut result = String::new();
let emoji = match event.has_started() { let emoji = match event.has_started() {
false => "🚩", false => "🚩",
true => "🔔", true => "🔔",
}; };
let text = event.text.as_str().escape_telegram_html(); let text = event.text.as_str().escape_telegram_html();
writeln!(result, "{emoji} <b>{text}</b>").unwrap(); writeln!(result, "{emoji} <b>{text}</b>").unwrap();
let start = event.starts_at let start = event.starts_at
.and_utc() .and_utc()
.with_timezone(&Local) .with_timezone(&Local)
@ -155,12 +155,12 @@ pub(crate) mod telegram_ext {
.to_string() .to_string()
.escape_telegram_html(); .escape_telegram_html();
writeln!(result, "<i>{start}</i>").unwrap(); writeln!(result, "<i>{start}</i>").unwrap();
writeln!(result).unwrap(); writeln!(result).unwrap();
for (reply, royalnet, telegram) in replies { for (reply, royalnet, telegram) in replies {
use MatchmakingChoice::*; use MatchmakingChoice::*;
let emoji = match reply.choice { let emoji = match reply.choice {
Yes => "🔵", Yes => "🔵",
Late => match reply.late_mins { Late => match reply.late_mins {
@ -182,24 +182,24 @@ pub(crate) mod telegram_ext {
Cant => "🔺", Cant => "🔺",
Wont => "🔻", Wont => "🔻",
}; };
let telegram_id = telegram.telegram_id.0; let telegram_id = telegram.telegram_id.0;
let username = &royalnet.username; let username = &royalnet.username;
write!(result, "{emoji} <a href=\"tg://user?id={telegram_id}\">{username}</a>").unwrap(); write!(result, "{emoji} <a href=\"tg://user?id={telegram_id}\">{username}</a>").unwrap();
if reply.choice == Late { if reply.choice == Late {
let late_mins = reply.late_mins; let late_mins = reply.late_mins;
write!(result, " (+{late_mins} mins)").unwrap(); write!(result, " (+{late_mins} mins)").unwrap();
} }
writeln!(result).unwrap(); writeln!(result).unwrap();
} }
result result
} }
async fn send_new( async fn send_new(
database: &mut PgConnection, database: &mut PgConnection,
matchmaking_id: MatchmakingId, matchmaking_id: MatchmakingId,
@ -209,32 +209,32 @@ pub(crate) mod telegram_ext {
) -> AnyResult<teloxide::types::Message> { ) -> AnyResult<teloxide::types::Message> {
let event = MatchmakingEvent::get(database, matchmaking_id) let event = MatchmakingEvent::get(database, matchmaking_id)
.context("Non è stato possibile recuperare il matchmaking dal database RYG.")?; .context("Non è stato possibile recuperare il matchmaking dal database RYG.")?;
let replies = MatchmakingReply::get_all_telegram(database, matchmaking_id) let replies = MatchmakingReply::get_all_telegram(database, matchmaking_id)
.context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?; .context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?;
let text = Self::text(&event, &replies); let text = Self::text(&event, &replies);
let mut request = bot.send_message(chat_id, text) let mut request = bot.send_message(chat_id, text)
.parse_mode(ParseMode::Html); .parse_mode(ParseMode::Html);
if !event.has_started() { if !event.has_started() {
request = request.reply_markup( request = request.reply_markup(
Self::reply_markup(matchmaking_id) Self::reply_markup(matchmaking_id)
) )
} }
if let Some(reply_to) = reply_to { if let Some(reply_to) = reply_to {
request = request.reply_parameters( request = request.reply_parameters(
teloxide::types::ReplyParameters::new(reply_to) teloxide::types::ReplyParameters::new(reply_to)
); );
} }
request request
.await .await
.context("La richiesta di invio messaggio alla Bot API di Telegram è fallita.") .context("La richiesta di invio messaggio alla Bot API di Telegram è fallita.")
} }
fn create( fn create(
database: &mut PgConnection, database: &mut PgConnection,
matchmaking_id: MatchmakingId, matchmaking_id: MatchmakingId,
@ -245,7 +245,7 @@ pub(crate) mod telegram_ext {
use diesel::prelude::*; use diesel::prelude::*;
use diesel::dsl::*; use diesel::dsl::*;
use crate::interfaces::database::schema::matchmaking_messages_telegram; use crate::interfaces::database::schema::matchmaking_messages_telegram;
insert_into(matchmaking_messages_telegram::table) insert_into(matchmaking_messages_telegram::table)
.values(&MatchmakingMessageTelegram { .values(&MatchmakingMessageTelegram {
matchmaking_id, matchmaking_id,
@ -256,7 +256,7 @@ pub(crate) mod telegram_ext {
.get_result::<MatchmakingMessageTelegram>(database) .get_result::<MatchmakingMessageTelegram>(database)
.context("L'inserimento nel database RYG è fallito.") .context("L'inserimento nel database RYG è fallito.")
} }
pub async fn send_new_and_create( pub async fn send_new_and_create(
database: &mut PgConnection, database: &mut PgConnection,
matchmaking_id: MatchmakingId, matchmaking_id: MatchmakingId,
@ -269,13 +269,13 @@ pub(crate) mod telegram_ext {
let reply = Self::send_new(database, matchmaking_id, bot, chat_id, reply_to) let reply = Self::send_new(database, matchmaking_id, bot, chat_id, reply_to)
.await .await
.context("Non è stato possibile inviare il messaggio Telegram del matchmaking.")?; .context("Non è stato possibile inviare il messaggio Telegram del matchmaking.")?;
let this = Self::create(database, matchmaking_id, &reply) let this = Self::create(database, matchmaking_id, &reply)
.context("Non è stato possibile aggiungere il messaggio Telegram al database RYG.")?; .context("Non è stato possibile aggiungere il messaggio Telegram al database RYG.")?;
Ok(this) Ok(this)
} }
async fn send_edit( async fn send_edit(
&self, &self,
bot: &teloxide::Bot, bot: &teloxide::Bot,
@ -285,21 +285,21 @@ pub(crate) mod telegram_ext {
-> AnyResult<teloxide::types::Message> -> AnyResult<teloxide::types::Message>
{ {
let telegram_chat_id: teloxide::types::ChatId = self.telegram_chat_id.into(); let telegram_chat_id: teloxide::types::ChatId = self.telegram_chat_id.into();
let mut request = bot.edit_message_text(telegram_chat_id, self.telegram_message_id.into(), text) let mut request = bot.edit_message_text(telegram_chat_id, self.telegram_message_id.into(), text)
.parse_mode(ParseMode::Html); .parse_mode(ParseMode::Html);
if with_keyboard { if with_keyboard {
request = request.reply_markup( request = request.reply_markup(
Self::reply_markup(self.matchmaking_id) Self::reply_markup(self.matchmaking_id)
) )
} }
request request
.await .await
.context("La richiesta di modifica messaggio alla Bot API di Telegram è fallita.") .context("La richiesta di modifica messaggio alla Bot API di Telegram è fallita.")
} }
pub async fn make_text_and_send_edit( pub async fn make_text_and_send_edit(
&self, &self,
database: &mut PgConnection, database: &mut PgConnection,
@ -309,19 +309,19 @@ pub(crate) mod telegram_ext {
{ {
let event = MatchmakingEvent::get(database, self.matchmaking_id) let event = MatchmakingEvent::get(database, self.matchmaking_id)
.context("Non è stato possibile recuperare il matchmaking dal database RYG.")?; .context("Non è stato possibile recuperare il matchmaking dal database RYG.")?;
let replies = MatchmakingReply::get_all_telegram(database, self.matchmaking_id) let replies = MatchmakingReply::get_all_telegram(database, self.matchmaking_id)
.context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?; .context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?;
let text = Self::text(&event, &replies); let text = Self::text(&event, &replies);
self.send_edit(bot, &text, !event.has_started()) self.send_edit(bot, &text, !event.has_started())
.await .await
.context("Non è stato possibile modificare il messaggio Telegram del matchmaking.")?; .context("Non è stato possibile modificare il messaggio Telegram del matchmaking.")?;
Ok(()) Ok(())
} }
async fn send_delete( async fn send_delete(
&self, &self,
bot: &teloxide::Bot, bot: &teloxide::Bot,
@ -333,7 +333,7 @@ pub(crate) mod telegram_ext {
.await .await
.context("La richiesta di eliminazione messaggio alla Bot API di Telegram è fallita.") .context("La richiesta di eliminazione messaggio alla Bot API di Telegram è fallita.")
} }
fn destroy( fn destroy(
&self, &self,
database: &mut PgConnection, database: &mut PgConnection,
@ -343,7 +343,7 @@ pub(crate) mod telegram_ext {
use diesel::prelude::*; use diesel::prelude::*;
use diesel::dsl::*; use diesel::dsl::*;
use crate::interfaces::database::schema::matchmaking_messages_telegram; use crate::interfaces::database::schema::matchmaking_messages_telegram;
delete(matchmaking_messages_telegram::table) delete(matchmaking_messages_telegram::table)
.filter(matchmaking_messages_telegram::matchmaking_id.eq(self.matchmaking_id)) .filter(matchmaking_messages_telegram::matchmaking_id.eq(self.matchmaking_id))
.filter(matchmaking_messages_telegram::telegram_chat_id.eq(self.telegram_chat_id)) .filter(matchmaking_messages_telegram::telegram_chat_id.eq(self.telegram_chat_id))
@ -351,21 +351,21 @@ pub(crate) mod telegram_ext {
.execute(database) .execute(database)
.context("La rimozione dal database RYG è fallita.") .context("La rimozione dal database RYG è fallita.")
} }
pub async fn destroy_and_send_delete( pub async fn destroy_and_send_delete(
self, self,
database: &mut PgConnection, database: &mut PgConnection,
bot: &teloxide::Bot bot: &teloxide::Bot,
) )
-> AnyResult<()> -> AnyResult<()>
{ {
self.destroy(database) self.destroy(database)
.context("Non è stato possibile eliminare il messaggio Telegram dal database RYG.")?; .context("Non è stato possibile eliminare il messaggio Telegram dal database RYG.")?;
self.send_delete(bot) self.send_delete(bot)
.await .await
.context("Non è stato possibile eliminare il messaggio Telegram del matchmaking.")?; .context("Non è stato possibile eliminare il messaggio Telegram del matchmaking.")?;
Ok(()) Ok(())
} }
} }

View file

@ -28,7 +28,7 @@ pub struct MatchmakingReply {
impl MatchmakingReply { impl MatchmakingReply {
pub fn get_all_telegram(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<(Self, RoyalnetUser, TelegramUser)>> { pub fn get_all_telegram(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<(Self, RoyalnetUser, TelegramUser)>> {
use schema::{matchmaking_replies, telegram, users}; use schema::{matchmaking_replies, telegram, users};
matchmaking_replies::table matchmaking_replies::table
.filter(matchmaking_replies::matchmaking_id.eq(matchmaking_id)) .filter(matchmaking_replies::matchmaking_id.eq(matchmaking_id))
.inner_join(users::table.on(matchmaking_replies::user_id.eq(users::id))) .inner_join(users::table.on(matchmaking_replies::user_id.eq(users::id)))
@ -36,10 +36,10 @@ impl MatchmakingReply {
.get_results::<(Self, RoyalnetUser, TelegramUser)>(database) .get_results::<(Self, RoyalnetUser, TelegramUser)>(database)
.context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.") .context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")
} }
pub fn set(database: &mut PgConnection, matchmaking_id: MatchmakingId, user_id: RoyalnetUserId, choice: MatchmakingChoice) -> AnyResult<Self> { pub fn set(database: &mut PgConnection, matchmaking_id: MatchmakingId, user_id: RoyalnetUserId, choice: MatchmakingChoice) -> AnyResult<Self> {
use schema::matchmaking_replies; use schema::matchmaking_replies;
insert_into(matchmaking_replies::table) insert_into(matchmaking_replies::table)
.values(&Self { .values(&Self {
matchmaking_id, matchmaking_id,
@ -56,10 +56,10 @@ impl MatchmakingReply {
.get_result::<Self>(database) .get_result::<Self>(database)
.context("Non è stato possibile inserire la risposta al matchmaking nel database RYG.") .context("Non è stato possibile inserire la risposta al matchmaking nel database RYG.")
} }
pub fn add_late_minutes(database: &mut PgConnection, matchmaking_id: MatchmakingId, user_id: RoyalnetUserId, increase_by: i32) -> AnyResult<Self> { pub fn add_late_minutes(database: &mut PgConnection, matchmaking_id: MatchmakingId, user_id: RoyalnetUserId, increase_by: i32) -> AnyResult<Self> {
use schema::matchmaking_replies; use schema::matchmaking_replies;
insert_into(matchmaking_replies::table) insert_into(matchmaking_replies::table)
.values(&Self { .values(&Self {
matchmaking_id, matchmaking_id,

View file

@ -29,33 +29,33 @@ mod telegram_ext {
Self(value.0) Self(value.0)
} }
} }
impl From<TelegramChatId> for teloxide::types::ChatId { impl From<TelegramChatId> for teloxide::types::ChatId {
fn from(value: TelegramChatId) -> Self { fn from(value: TelegramChatId) -> Self {
Self(value.0) Self(value.0)
} }
} }
impl From<teloxide::types::UserId> for TelegramUserId { impl From<teloxide::types::UserId> for TelegramUserId {
fn from(value: teloxide::types::UserId) -> Self { fn from(value: teloxide::types::UserId) -> Self {
// FIXME: this surely seems like a great idea // FIXME: this surely seems like a great idea
Self(value.0 as i64) Self(value.0 as i64)
} }
} }
impl From<TelegramUserId> for teloxide::types::UserId { impl From<TelegramUserId> for teloxide::types::UserId {
fn from(value: TelegramUserId) -> Self { fn from(value: TelegramUserId) -> Self {
// FIXME: this surely seems like a great idea // FIXME: this surely seems like a great idea
Self(value.0 as u64) Self(value.0 as u64)
} }
} }
impl From<teloxide::types::MessageId> for TelegramMessageId { impl From<teloxide::types::MessageId> for TelegramMessageId {
fn from(value: teloxide::types::MessageId) -> Self { fn from(value: teloxide::types::MessageId) -> Self {
Self(value.0) Self(value.0)
} }
} }
impl From<TelegramMessageId> for teloxide::types::MessageId { impl From<TelegramMessageId> for teloxide::types::MessageId {
fn from(value: TelegramMessageId) -> Self { fn from(value: TelegramMessageId) -> Self {
Self(value.0) Self(value.0)

View file

@ -1,9 +1,9 @@
// @generated automatically by Diesel CLI. // @generated automatically by Diesel CLI.
pub mod sql_types { pub mod sql_types {
#[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)] #[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "matchmaking_choice"))] #[diesel(postgres_type(name = "matchmaking_choice"))]
pub struct MatchmakingChoice; pub struct MatchmakingChoice;
} }
diesel::table! { diesel::table! {

View file

@ -34,13 +34,13 @@ pub async fn query(client: &reqwest::Client, url: Url, guild_id: i64) -> QueryRe
log::debug!("Querying guild_matches of guild {guild_id}..."); log::debug!("Querying guild_matches of guild {guild_id}...");
log::trace!("Using client: {client:?}"); log::trace!("Using client: {client:?}");
log::trace!("Using API at: {url:?}"); log::trace!("Using API at: {url:?}");
log::trace!("Configuring query variables..."); log::trace!("Configuring query variables...");
let vars = query::Variables { guild_id }; let vars = query::Variables { guild_id };
log::trace!("Building query..."); log::trace!("Building query...");
let body = Query::build_query(vars); let body = Query::build_query(vars);
log::trace!("Making request..."); log::trace!("Making request...");
let response = client.post(url) let response = client.post(url)
.json(&body) .json(&body)
@ -50,6 +50,6 @@ pub async fn query(client: &reqwest::Client, url: Url, guild_id: i64) -> QueryRe
.json::<QueryResponse>() .json::<QueryResponse>()
.await .await
.map_err(|_| Error::Parsing)?; .map_err(|_| Error::Parsing)?;
Ok(response) Ok(response)
} }

View file

@ -1,37 +1,37 @@
query Query($guild_id: Int!) { query Query($guild_id: Int!) {
guild(id: $guild_id) { guild(id: $guild_id) {
id id
matches(take: 10) { matches(take: 10) {
id id
lobbyType lobbyType
gameMode gameMode
durationSeconds durationSeconds
endDateTime endDateTime
players(steamAccountId: null) { players(steamAccountId: null) {
isRadiant isRadiant
isVictory isVictory
imp imp
kills kills
deaths deaths
assists assists
lane lane
role role
hero { hero {
displayName displayName
} }
steamAccount { steamAccount {
id id
name name
} }
stats { stats {
matchPlayerBuffEvent { matchPlayerBuffEvent {
time time
itemId itemId
abilityId abilityId
stackCount stackCount
} }
} }
} }
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -7,15 +7,15 @@ pub(crate) mod utils;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Logging setup // Logging setup
pretty_env_logger::init(); pretty_env_logger::init();
log::debug!("Logging initialized successfully!"); log::debug!("Logging initialized successfully!");
// Create instance // Create instance
let instance = RoyalnetInstance::new().await; let instance = RoyalnetInstance::new().await;
log::trace!("Starting {instance:?}!"); log::trace!("Starting {instance:?}!");
instance.run().await; instance.run().await;
log::error!("No services configured."); log::error!("No services configured.");
} }

View file

@ -40,33 +40,33 @@ impl BroochService {
min_players_to_process: usize, min_players_to_process: usize,
telegram_bot_token: String, telegram_bot_token: String,
notification_chat_id: ChatId, notification_chat_id: ChatId,
max_imp_wait: TimeDelta max_imp_wait: TimeDelta,
) )
-> AnyResult<Self> -> AnyResult<Self>
{ {
log::info!("Initializing a new Brooch service..."); log::info!("Initializing a new Brooch service...");
let mut graphql_url = Url::parse(graphql_base_url) let mut graphql_url = Url::parse(graphql_base_url)
.context("URL GraphQL non valido.")?; .context("URL GraphQL non valido.")?;
{ {
let mut graphql_url_params = graphql_url.query_pairs_mut(); let mut graphql_url_params = graphql_url.query_pairs_mut();
graphql_url_params.append_pair("jwt", stratz_token); graphql_url_params.append_pair("jwt", stratz_token);
} }
log::trace!("Using GraphQL API URL: {graphql_url:?}"); log::trace!("Using GraphQL API URL: {graphql_url:?}");
if min_players_to_process == 0 { if min_players_to_process == 0 {
anyhow::bail!("min_players_to_progress devono essere almeno 1."); anyhow::bail!("min_players_to_progress devono essere almeno 1.");
} }
log::trace!("Processing only matches with at least {min_players_to_process} players."); log::trace!("Processing only matches with at least {min_players_to_process} players.");
let telegram_bot = Bot::new(telegram_bot_token); let telegram_bot = Bot::new(telegram_bot_token);
log::trace!("Using bot: {telegram_bot:#?}"); log::trace!("Using bot: {telegram_bot:#?}");
log::trace!("Max IMP wait is: {max_imp_wait:?}"); log::trace!("Max IMP wait is: {max_imp_wait:?}");
Ok( Ok(
BroochService { BroochService {
database_url, database_url,
@ -79,143 +79,143 @@ impl BroochService {
} }
) )
} }
fn create_http_client(&self) -> AnyResult<reqwest::Client> { fn create_http_client(&self) -> AnyResult<reqwest::Client> {
log::debug!("Creating HTTP client..."); log::debug!("Creating HTTP client...");
reqwest::Client::builder() reqwest::Client::builder()
.build() .build()
.context("Impossibile creare un client HTTP appropriato a fare richieste all'API.") .context("Impossibile creare un client HTTP appropriato a fare richieste all'API.")
} }
fn create_postgres_connection(&self) -> AnyResult<PgConnection> { fn create_postgres_connection(&self) -> AnyResult<PgConnection> {
log::debug!("Creating PostgreSQL connection..."); log::debug!("Creating PostgreSQL connection...");
database::connect(&self.database_url) database::connect(&self.database_url)
.context("Non è stato possibile connettersi al database RYG.") .context("Non è stato possibile connettersi al database RYG.")
} }
async fn query_guild_matches(&self, client: &reqwest::Client) -> AnyResult<guild_matches::QueryResponse> { async fn query_guild_matches(&self, client: &reqwest::Client) -> AnyResult<guild_matches::QueryResponse> {
log::debug!("Querying for guild matches..."); log::debug!("Querying for guild matches...");
guild_matches::query(client, self.graphql_url.clone(), self.watched_guild_id) guild_matches::query(client, self.graphql_url.clone(), self.watched_guild_id)
.await .await
.context("Non è stato possibile recuperare le ultime partite di Dota da STRATZ.") .context("Non è stato possibile recuperare le ultime partite di Dota da STRATZ.")
} }
fn process_guild_data(&self, data: guild_matches::QueryResponse) -> AnyResult<guild_matches::Guild> { fn process_guild_data(&self, data: guild_matches::QueryResponse) -> AnyResult<guild_matches::Guild> {
log::debug!("Processing guild data..."); log::debug!("Processing guild data...");
let data = data.data let data = data.data
.context("La richiesta è riuscita, ma la risposta ricevuta da STRATZ era vuota.")?; .context("La richiesta è riuscita, ma la risposta ricevuta da STRATZ era vuota.")?;
let guild = data.guild let guild = data.guild
.context("La richiesta è riuscita, ma non sono state ricevute gilde da STRATZ.")?; .context("La richiesta è riuscita, ma non sono state ricevute gilde da STRATZ.")?;
let guild_id: i64 = guild.id let guild_id: i64 = guild.id
.context("La richiesta è riuscita, ma non è stato ricevuto l'ID della gilda da STRATZ.")?; .context("La richiesta è riuscita, ma non è stato ricevuto l'ID della gilda da STRATZ.")?;
log::trace!("Guild id is: {guild_id}"); log::trace!("Guild id is: {guild_id}");
if guild_id != self.watched_guild_id { if guild_id != self.watched_guild_id {
anyhow::bail!("La richiesta è riuscita, ma STRATZ ha risposto con le informazioni della gilda sbagliata."); anyhow::bail!("La richiesta è riuscita, ma STRATZ ha risposto con le informazioni della gilda sbagliata.");
} }
log::trace!("Guild id matches watched guild."); log::trace!("Guild id matches watched guild.");
Ok(guild) Ok(guild)
} }
fn process_matches_data(&self, guild: guild_matches::Guild) -> AnyResult<Vec<Match>> { fn process_matches_data(&self, guild: guild_matches::Guild) -> AnyResult<Vec<Match>> {
log::debug!("Processing matches data..."); log::debug!("Processing matches data...");
let mut matches = guild.matches let mut matches = guild.matches
.context("La richiesta è riuscita, ma non sono state ricevute informazioni sulle partite della gilda da STRATZ.")? .context("La richiesta è riuscita, ma non sono state ricevute informazioni sulle partite della gilda da STRATZ.")?
.into_iter() .into_iter()
.flatten() .flatten()
.collect::<Vec<Match>>(); .collect::<Vec<Match>>();
log::trace!("Received {} matches.", matches.len()); log::trace!("Received {} matches.", matches.len());
log::trace!("Sorting matches by datetime..."); log::trace!("Sorting matches by datetime...");
// Sort matches chronologically // Sort matches chronologically
matches.sort_unstable_by_key(|o| o matches.sort_unstable_by_key(|o| o
.end_date_time .end_date_time
.unwrap_or(0) .unwrap_or(0)
); );
log::trace!("Sorted matches by datetime!"); log::trace!("Sorted matches by datetime!");
Ok(matches) Ok(matches)
} }
fn get_match_id(&self, r#match: &Match) -> AnyResult<DotaMatchId> { fn get_match_id(&self, r#match: &Match) -> AnyResult<DotaMatchId> {
log::trace!("Getting match id..."); log::trace!("Getting match id...");
Ok( Ok(
r#match.id r#match.id
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")? .context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")?
.into() .into()
) )
} }
fn get_database_match(&self, database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Option<BroochMatch>> { fn get_database_match(&self, database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Option<BroochMatch>> {
use crate::interfaces::database::query_prelude::*; use crate::interfaces::database::query_prelude::*;
use crate::interfaces::database::schema::brooch_match; use crate::interfaces::database::schema::brooch_match;
log::trace!("Getting {match_id:?} from the database..."); log::trace!("Getting {match_id:?} from the database...");
brooch_match::table brooch_match::table
.filter(brooch_match::id.eq(match_id)) .filter(brooch_match::id.eq(match_id))
.get_result::<BroochMatch>(database) .get_result::<BroochMatch>(database)
.optional() .optional()
.context("Non è stato possibile recuperare la partita restituita da STRATZ dal database RYG.") .context("Non è stato possibile recuperare la partita restituita da STRATZ dal database RYG.")
} }
fn should_process_match_exists(&self, database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<bool> { fn should_process_match_exists(&self, database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<bool> {
log::trace!("Determining whether {match_id:?} should be processed..."); log::trace!("Determining whether {match_id:?} should be processed...");
self.get_database_match(database, match_id) self.get_database_match(database, match_id)
.map(|m| m.is_none()) .map(|m| m.is_none())
.context("Non è stato possibile determinare se la partita restituita da STRATZ fosse stata già processata.") .context("Non è stato possibile determinare se la partita restituita da STRATZ fosse stata già processata.")
} }
fn get_match_datetime(&self, r#match: &Match) -> AnyResult<DateTime<Local>> { fn get_match_datetime(&self, r#match: &Match) -> AnyResult<DateTime<Local>> {
log::trace!("Getting match datetime..."); log::trace!("Getting match datetime...");
let match_date = r#match.end_date_time let match_date = r#match.end_date_time
.context("Non è stato ricevuto da STRATZ il momento di termine della partita.")?; .context("Non è stato ricevuto da STRATZ il momento di termine della partita.")?;
log::trace!("Converting match datetime to local datetime..."); log::trace!("Converting match datetime to local datetime...");
Local.timestamp_opt(match_date, 0) Local.timestamp_opt(match_date, 0)
.earliest() .earliest()
.context("È stato ricevuto da STRATZ un momento di termine della partita non valido.") .context("È stato ricevuto da STRATZ un momento di termine della partita non valido.")
} }
fn get_match_timedelta(&self, datetime: &DateTime<Local>) -> TimeDelta { fn get_match_timedelta(&self, datetime: &DateTime<Local>) -> TimeDelta {
log::trace!("Getting current time..."); log::trace!("Getting current time...");
let now = Local::now(); let now = Local::now();
log::trace!("Getting match timedelta..."); log::trace!("Getting match timedelta...");
now - *datetime now - *datetime
} }
fn get_match_type(&self, r#match: &Match) -> AnyResult<LobbyType> { fn get_match_type(&self, r#match: &Match) -> AnyResult<LobbyType> {
log::trace!("Getting match type..."); log::trace!("Getting match type...");
r#match.lobby_type.clone() r#match.lobby_type.clone()
.context("Non è stato ricevuta da STRATZ il tipo della partita.") .context("Non è stato ricevuta da STRATZ il tipo della partita.")
} }
fn stringify_type(&self, r#type: LobbyType) -> String { fn stringify_type(&self, r#type: LobbyType) -> String {
use LobbyType::*; use LobbyType::*;
log::trace!("Stringifying match type: {:?}", r#type); log::trace!("Stringifying match type: {:?}", r#type);
match r#type { match r#type {
UNRANKED => String::from("Normale"), UNRANKED => String::from("Normale"),
PRACTICE => String::from("Torneo"), PRACTICE => String::from("Torneo"),
@ -232,19 +232,19 @@ impl BroochService {
Other(t) => t.clone(), Other(t) => t.clone(),
} }
} }
fn get_match_mode(&self, r#match: &Match) -> AnyResult<GameMode> { fn get_match_mode(&self, r#match: &Match) -> AnyResult<GameMode> {
log::trace!("Getting match mode..."); log::trace!("Getting match mode...");
r#match.game_mode.clone() r#match.game_mode.clone()
.context("Non è stata ricevuta da STRATZ la modalità della partita.") .context("Non è stata ricevuta da STRATZ la modalità della partita.")
} }
fn stringify_mode(&self, mode: GameMode) -> String { fn stringify_mode(&self, mode: GameMode) -> String {
use GameMode::*; use GameMode::*;
log::trace!("Stringifying match mode: {:?}", mode); log::trace!("Stringifying match mode: {:?}", mode);
match mode { match mode {
NONE => String::from("Sandbox"), NONE => String::from("Sandbox"),
ALL_PICK => String::from("All Pick"), ALL_PICK => String::from("All Pick"),
@ -275,185 +275,185 @@ impl BroochService {
Other(t) => t.clone(), Other(t) => t.clone(),
} }
} }
fn stringify_duration(&self, duration: TimeDelta) -> String { fn stringify_duration(&self, duration: TimeDelta) -> String {
let minutes = duration.num_minutes(); let minutes = duration.num_minutes();
let seconds = duration.num_seconds() % 60; let seconds = duration.num_seconds() % 60;
format!("{minutes:02}:{seconds:02}") format!("{minutes:02}:{seconds:02}")
} }
fn get_match_duration(&self, r#match: &Match) -> AnyResult<TimeDelta> { fn get_match_duration(&self, r#match: &Match) -> AnyResult<TimeDelta> {
log::trace!("Getting match duration..."); log::trace!("Getting match duration...");
let secs = r#match.duration_seconds let secs = r#match.duration_seconds
.context("Non è stata ricevuta da STRATZ la durata della partita.")?; .context("Non è stata ricevuta da STRATZ la durata della partita.")?;
log::trace!("Getting match duration timedelta..."); log::trace!("Getting match duration timedelta...");
let delta = TimeDelta::new(secs, 0) let delta = TimeDelta::new(secs, 0)
.context("Non è stato possibile rappresentare la durata della partita ricevuta da STRATZ.")?; .context("Non è stato possibile rappresentare la durata della partita ricevuta da STRATZ.")?;
Ok(delta) Ok(delta)
} }
fn get_match_players(&self, r#match: Match) -> AnyResult<Vec<Player>> { fn get_match_players(&self, r#match: Match) -> AnyResult<Vec<Player>> {
log::debug!("Getting match players..."); log::debug!("Getting match players...");
let mut players: Vec<Player> = r#match.players let mut players: Vec<Player> = r#match.players
.context("Non è stato ricevuto da STRATZ l'elenco dei giocatori della partita.")? .context("Non è stato ricevuto da STRATZ l'elenco dei giocatori della partita.")?
.iter() .iter()
.filter_map(|o| o.to_owned()) .filter_map(|o| o.to_owned())
.collect(); .collect();
log::trace!("Sorting match players..."); log::trace!("Sorting match players...");
players.sort_unstable_by_key(|o| match o.is_radiant.unwrap() { players.sort_unstable_by_key(|o| match o.is_radiant.unwrap() {
true => 1, true => 1,
false => 2, false => 2,
}); });
log::trace!("Sorted match players!"); log::trace!("Sorted match players!");
Ok(players) Ok(players)
} }
fn should_process_match_players(&self, players: &[Player]) -> bool { fn should_process_match_players(&self, players: &[Player]) -> bool {
let players_len = players.len(); let players_len = players.len();
log::trace!("Determining whether {players_len} are enough for the match to be processed..."); log::trace!("Determining whether {players_len} are enough for the match to be processed...");
players_len >= self.min_players_to_process players_len >= self.min_players_to_process
} }
fn should_process_match_imp(&self, players: &[Player], timedelta: &TimeDelta) -> bool { fn should_process_match_imp(&self, players: &[Player], timedelta: &TimeDelta) -> bool {
log::trace!("Determining whether IMP is available for all players..."); log::trace!("Determining whether IMP is available for all players...");
let imp_available_for_everyone = players.iter() let imp_available_for_everyone = players.iter()
.map(|o| o.imp) .map(|o| o.imp)
.map(|o| o.is_some()) .map(|o| o.is_some())
.all(|o| o); .all(|o| o);
log::trace!("Determining whether enough time has passed for IMP to be ignored..."); log::trace!("Determining whether enough time has passed for IMP to be ignored...");
let imp_waited_too_long = *timedelta > self.max_imp_wait; let imp_waited_too_long = *timedelta > self.max_imp_wait;
imp_available_for_everyone || imp_waited_too_long imp_available_for_everyone || imp_waited_too_long
} }
fn get_match_side(&self, players: &[Player]) -> AnyResult<MatchSide> { fn get_match_side(&self, players: &[Player]) -> AnyResult<MatchSide> {
use MatchSide::*; use MatchSide::*;
log::debug!("Getting match side..."); log::debug!("Getting match side...");
let mut side = None; let mut side = None;
for player in players.iter() { for player in players.iter() {
side = match (side, player.is_radiant) { side = match (side, player.is_radiant) {
(_, None) => { (_, None) => {
anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.") anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.")
}, }
(None, Some(true)) => { (None, Some(true)) => {
Some(Radiant) Some(Radiant)
}, }
(None, Some(false)) => { (None, Some(false)) => {
Some(Dire) Some(Dire)
}, }
(Some(Radiant), Some(true)) | (Some(Radiant), Some(true)) |
(Some(Dire), Some(false)) => { (Some(Dire), Some(false)) => {
side side
}, }
(Some(Radiant), Some(false)) | (Some(Radiant), Some(false)) |
(Some(Dire), Some(true)) => { (Some(Dire), Some(true)) => {
Some(Both) Some(Both)
}, }
(Some(Both), _) => { (Some(Both), _) => {
break break
} }
} }
} }
let side = side.unwrap(); let side = side.unwrap();
log::trace!("Match side is: {side:?}"); log::trace!("Match side is: {side:?}");
Ok(side) Ok(side)
} }
fn get_match_outcome(&self, players: &[Player]) -> AnyResult<MatchOutcome> { fn get_match_outcome(&self, players: &[Player]) -> AnyResult<MatchOutcome> {
use MatchOutcome::*; use MatchOutcome::*;
log::debug!("Getting match outcome..."); log::debug!("Getting match outcome...");
let mut outcome = None; let mut outcome = None;
for player in players.iter() { for player in players.iter() {
outcome = match (outcome, player.is_victory) { outcome = match (outcome, player.is_victory) {
(_, None) => { (_, None) => {
anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.") anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.")
}, }
(None, Some(true)) => { (None, Some(true)) => {
Some(Victory) Some(Victory)
}, }
(None, Some(false)) => { (None, Some(false)) => {
Some(Defeat) Some(Defeat)
}, }
(Some(Victory), Some(true)) | (Some(Victory), Some(true)) |
(Some(Defeat), Some(false)) => { (Some(Defeat), Some(false)) => {
outcome outcome
}, }
(Some(Victory), Some(false)) | (Some(Victory), Some(false)) |
(Some(Defeat), Some(true)) => { (Some(Defeat), Some(true)) => {
Some(Clash) Some(Clash)
}, }
(Some(Clash), _) => { (Some(Clash), _) => {
break break
} }
} }
} }
let outcome = outcome.unwrap(); let outcome = outcome.unwrap();
log::trace!("Match outcome is: {outcome:?}"); log::trace!("Match outcome is: {outcome:?}");
Ok(outcome) Ok(outcome)
} }
fn get_player_steam(&self, player: &Player) -> AnyResult<Steam> { fn get_player_steam(&self, player: &Player) -> AnyResult<Steam> {
log::trace!("Getting player's Steam account..."); log::trace!("Getting player's Steam account...");
player.steam_account.clone() player.steam_account.clone()
.context("Non è stato ricevuto da STRATZ l'account Steam di almeno uno dei giocatori della partita.") .context("Non è stato ricevuto da STRATZ l'account Steam di almeno uno dei giocatori della partita.")
} }
fn get_player_name(&self, steam: &Steam) -> AnyResult<String> { fn get_player_name(&self, steam: &Steam) -> AnyResult<String> {
log::trace!("Getting player's Steam name..."); log::trace!("Getting player's Steam name...");
steam.name.clone() steam.name.clone()
.context("Non è stato ricevuto da STRATZ il display name di almeno uno dei giocatori della partita.") .context("Non è stato ricevuto da STRATZ il display name di almeno uno dei giocatori della partita.")
} }
fn get_player_telegram_id(&self, database: &mut PgConnection, player_steam: Steam) -> AnyResult<Option<TelegramUserId>> { fn get_player_telegram_id(&self, database: &mut PgConnection, player_steam: Steam) -> AnyResult<Option<TelegramUserId>> {
use diesel::prelude::*; use diesel::prelude::*;
use diesel::{ExpressionMethods, QueryDsl}; use diesel::{ExpressionMethods, QueryDsl};
use crate::interfaces::database::schema::{steam, telegram, users}; use crate::interfaces::database::schema::{steam, telegram, users};
use crate::interfaces::database::models::TelegramUser; use crate::interfaces::database::models::TelegramUser;
log::trace!("Getting player's Steam name..."); log::trace!("Getting player's Steam name...");
let player_steam_id = player_steam.id let player_steam_id = player_steam.id
.context("Non è stato ricevuto da STRATZ lo SteamID di almeno uno dei giocatori della partita.")?; .context("Non è stato ricevuto da STRATZ lo SteamID di almeno uno dei giocatori della partita.")?;
log::trace!("Computing the two possible SteamIDs..."); log::trace!("Computing the two possible SteamIDs...");
let player_steam_id_y0 = 0x_0110_0001_0000_0000 + player_steam_id; let player_steam_id_y0 = 0x_0110_0001_0000_0000 + player_steam_id;
log::trace!("SteamID Y0 is: {player_steam_id_y0}"); log::trace!("SteamID Y0 is: {player_steam_id_y0}");
let player_steam_id_y1 = 0x_0110_0001_0000_0001 + player_steam_id; let player_steam_id_y1 = 0x_0110_0001_0000_0001 + player_steam_id;
log::trace!("SteamID Y1 is: {player_steam_id_y1}"); log::trace!("SteamID Y1 is: {player_steam_id_y1}");
Ok( Ok(
steam::table steam::table
.inner_join( .inner_join(
@ -479,24 +479,24 @@ impl BroochService {
.map(|t| t.telegram_id) .map(|t| t.telegram_id)
) )
} }
fn get_player_hero_name(&self, player: &Player) -> AnyResult<String> { fn get_player_hero_name(&self, player: &Player) -> AnyResult<String> {
log::trace!("Getting player's hero name..."); log::trace!("Getting player's hero name...");
player.hero.clone() player.hero.clone()
.context("Non è stato ricevuto da STRATZ l'eroe giocato da almeno uno dei giocatori della partita.")? .context("Non è stato ricevuto da STRATZ l'eroe giocato da almeno uno dei giocatori della partita.")?
.display_name .display_name
.context("Non è stato ricevuto da STRATZ il nome dell'eroe giocato da almeno uno dei giocatori della partita.") .context("Non è stato ricevuto da STRATZ il nome dell'eroe giocato da almeno uno dei giocatori della partita.")
} }
fn get_player_outcome(&self, player: &Player) -> AnyResult<MatchOutcome> { fn get_player_outcome(&self, player: &Player) -> AnyResult<MatchOutcome> {
use MatchOutcome::*; use MatchOutcome::*;
log::trace!("Getting player's match outcome..."); log::trace!("Getting player's match outcome...");
let is_victory = &player.is_victory let is_victory = &player.is_victory
.context("Non è stato ricevuto da STRATZ il risultato della partita per almeno uno dei giocatori.")?; .context("Non è stato ricevuto da STRATZ il risultato della partita per almeno uno dei giocatori.")?;
Ok( Ok(
match is_victory { match is_victory {
true => Victory, true => Victory,
@ -504,68 +504,68 @@ impl BroochService {
} }
) )
} }
fn emojify_outcome(&self, outcome: MatchOutcome) -> &'static str { fn emojify_outcome(&self, outcome: MatchOutcome) -> &'static str {
use MatchOutcome::*; use MatchOutcome::*;
log::trace!("Emojifying match outcome..."); log::trace!("Emojifying match outcome...");
match outcome { match outcome {
Victory => "🟩", Victory => "🟩",
Defeat => "🔴", Defeat => "🔴",
Clash => "💛", Clash => "💛",
} }
} }
fn stringify_outcome(&self, outcome: MatchOutcome) -> &'static str { fn stringify_outcome(&self, outcome: MatchOutcome) -> &'static str {
use MatchOutcome::*; use MatchOutcome::*;
log::trace!("Stringifying match outcome..."); log::trace!("Stringifying match outcome...");
match outcome { match outcome {
Victory => "Vittoria!", Victory => "Vittoria!",
Defeat => "Sconfitta...", Defeat => "Sconfitta...",
Clash => "Derby", Clash => "Derby",
} }
} }
fn numberify_role_lane(role: &Option<Role>, lane: &Option<Lane>) -> u8 { fn numberify_role_lane(role: &Option<Role>, lane: &Option<Lane>) -> u8 {
use Role::*; use Role::*;
use Lane::*; use Lane::*;
match (role, lane) { match (role, lane) {
( Some(CORE), Some(SAFE_LANE)) => 1, (Some(CORE), Some(SAFE_LANE)) => 1,
( Some(CORE), Some(MID_LANE)) => 2, (Some(CORE), Some(MID_LANE)) => 2,
( Some(CORE), Some(OFF_LANE)) => 3, (Some(CORE), Some(OFF_LANE)) => 3,
( _, Some(ROAMING)) => 4, (_, Some(ROAMING)) => 4,
( _, Some(JUNGLE)) => 5, (_, Some(JUNGLE)) => 5,
(Some(LIGHT_SUPPORT), _) => 6, (Some(LIGHT_SUPPORT), _) => 6,
( Some(HARD_SUPPORT), _) => 7, (Some(HARD_SUPPORT), _) => 7,
( _, _) => 8, (_, _) => 8,
} }
} }
fn stringify_role_lane(&self, role: Role, lane: Lane) -> &'static str { fn stringify_role_lane(&self, role: Role, lane: Lane) -> &'static str {
use Role::*; use Role::*;
use Lane::*; use Lane::*;
log::trace!("Stringifying role and lane..."); log::trace!("Stringifying role and lane...");
match (role, lane) { match (role, lane) {
( CORE, SAFE_LANE) => "1⃣ Safe Carry", (CORE, SAFE_LANE) => "1⃣ Safe Carry",
( CORE, MID_LANE) => "2⃣ Mid Carry", (CORE, MID_LANE) => "2⃣ Mid Carry",
( CORE, OFF_LANE) => "3⃣ Off Tank", (CORE, OFF_LANE) => "3⃣ Off Tank",
( _, ROAMING) => "🔀 Roaming", (_, ROAMING) => "🔀 Roaming",
( _, JUNGLE) => "⏫ Jungle", (_, JUNGLE) => "⏫ Jungle",
(LIGHT_SUPPORT, _) => "4⃣ Soft Support", (LIGHT_SUPPORT, _) => "4⃣ Soft Support",
( HARD_SUPPORT, _) => "5⃣ Hard Support", (HARD_SUPPORT, _) => "5⃣ Hard Support",
( _, _) => "🆕 Sconosciuto", (_, _) => "🆕 Sconosciuto",
} }
} }
fn emojify_imp(&self, imp: Short) -> &'static str { fn emojify_imp(&self, imp: Short) -> &'static str {
log::trace!("Emojifying IMP..."); log::trace!("Emojifying IMP...");
match imp { match imp {
Short::MIN..=-49 => "🟧", Short::MIN..=-49 => "🟧",
-48..=-25 => "🔶", -48..=-25 => "🔶",
@ -575,44 +575,44 @@ impl BroochService {
49..=Short::MAX => "🟦", 49..=Short::MAX => "🟦",
} }
} }
fn emojify_kills_deaths_assists(&self, kills: Byte, deaths: Byte, assists: Byte) -> &'static str { fn emojify_kills_deaths_assists(&self, kills: Byte, deaths: Byte, assists: Byte) -> &'static str {
log::trace!("Emojifying KDA..."); log::trace!("Emojifying KDA...");
let kills = kills as i16; let kills = kills as i16;
let deaths = deaths as i16; let deaths = deaths as i16;
let assists = assists as i16; let assists = assists as i16;
let kda_score = kills + (assists / 2) - deaths; let kda_score = kills + (assists / 2) - deaths;
match kda_score { match kda_score {
i16::MIN..=-1 => "", i16::MIN..=-1 => "",
0..=i16::MAX => "", 0..=i16::MAX => "",
} }
} }
fn stringify_player(&self, database: &mut PgConnection, player: Player, show_outcome: bool) -> AnyResult<String> { fn stringify_player(&self, database: &mut PgConnection, player: Player, show_outcome: bool) -> AnyResult<String> {
log::debug!("Stringifying player..."); log::debug!("Stringifying player...");
log::trace!("Showing outcome: {show_outcome:?}"); log::trace!("Showing outcome: {show_outcome:?}");
let steam = self.get_player_steam(&player)?; let steam = self.get_player_steam(&player)?;
let name = self.get_player_name(&steam)?; let name = self.get_player_name(&steam)?;
let telegram_id = self.get_player_telegram_id(database, steam)?; let telegram_id = self.get_player_telegram_id(database, steam)?;
let hero_name = self.get_player_hero_name(&player)?; let hero_name = self.get_player_hero_name(&player)?;
let outcome = self.get_player_outcome(&player)?; let outcome = self.get_player_outcome(&player)?;
let role = player.role.clone(); let role = player.role.clone();
let lane = player.lane.clone(); let lane = player.lane.clone();
let imp = player.imp; let imp = player.imp;
let kills = player.kills; let kills = player.kills;
let deaths = player.deaths; let deaths = player.deaths;
let assists = player.assists; let assists = player.assists;
// TODO: Buffs // TODO: Buffs
let mut lines = Vec::<String>::new(); let mut lines = Vec::<String>::new();
match telegram_id { match telegram_id {
None => lines.push(format!( None => lines.push(format!(
"<u><b>{}</b> ({})</u>", "<u><b>{}</b> ({})</u>",
@ -626,102 +626,102 @@ impl BroochService {
hero_name.to_string().escape_telegram_html(), hero_name.to_string().escape_telegram_html(),
)), )),
} }
if show_outcome { if show_outcome {
lines.push(format!( lines.push(format!(
"— {}", self.stringify_outcome(outcome) "— {}", self.stringify_outcome(outcome)
)) ))
} }
if let (Some(role), Some(lane)) = (role, lane) { if let (Some(role), Some(lane)) = (role, lane) {
lines.push(format!( lines.push(format!(
"— {}", self.stringify_role_lane(role, lane) "— {}", self.stringify_role_lane(role, lane)
)) ))
} }
if let (Some(kills), Some(deaths), Some(assists)) = (kills, deaths, assists) { if let (Some(kills), Some(deaths), Some(assists)) = (kills, deaths, assists) {
lines.push(format!( lines.push(format!(
"— {} {kills} K / {deaths} D / {assists} A", self.emojify_kills_deaths_assists(kills, deaths, assists) "— {} {kills} K / {deaths} D / {assists} A", self.emojify_kills_deaths_assists(kills, deaths, assists)
)) ))
} }
if let Some(imp) = imp { if let Some(imp) = imp {
lines.push(format!( lines.push(format!(
"— {} {imp} IMP", self.emojify_imp(imp) "— {} {imp} IMP", self.emojify_imp(imp)
)) ))
} }
Ok(lines.join("\n")) Ok(lines.join("\n"))
} }
fn stringify_match(&self, database: &mut PgConnection, r#match: Match) -> AnyResult<(DotaMatchId, Option<String>)> { fn stringify_match(&self, database: &mut PgConnection, r#match: Match) -> AnyResult<(DotaMatchId, Option<String>)> {
log::debug!("Stringifying match..."); log::debug!("Stringifying match...");
let match_id = self.get_match_id(&r#match)?; let match_id = self.get_match_id(&r#match)?;
if !self.should_process_match_exists(database, match_id)? { if !self.should_process_match_exists(database, match_id)? {
log::trace!("Skipping match, already parsed."); log::trace!("Skipping match, already parsed.");
return Ok((match_id, None)) return Ok((match_id, None));
} }
let datetime = self.get_match_datetime(&r#match)?; let datetime = self.get_match_datetime(&r#match)?;
let timedelta = self.get_match_timedelta(&datetime); let timedelta = self.get_match_timedelta(&datetime);
let r#type = self.get_match_type(&r#match)?; let r#type = self.get_match_type(&r#match)?;
let mode = self.get_match_mode(&r#match)?; let mode = self.get_match_mode(&r#match)?;
let duration = self.get_match_duration(&r#match)?; let duration = self.get_match_duration(&r#match)?;
let mut players = self.get_match_players(r#match)?; let mut players = self.get_match_players(r#match)?;
if !self.should_process_match_players(&players) { if !self.should_process_match_players(&players) {
log::trace!("Skipping match, not enough players."); log::trace!("Skipping match, not enough players.");
return Ok((match_id, None)) return Ok((match_id, None));
} }
if !self.should_process_match_imp(&players, &timedelta) { if !self.should_process_match_imp(&players, &timedelta) {
log::trace!("Skipping match, IMP is not ready."); log::trace!("Skipping match, IMP is not ready.");
return Ok((match_id, None)) return Ok((match_id, None));
} }
players.sort_unstable_by_key(|a| Self::numberify_role_lane(&a.role, &a.lane)); players.sort_unstable_by_key(|a| Self::numberify_role_lane(&a.role, &a.lane));
let _side = self.get_match_side(&players)?; let _side = self.get_match_side(&players)?;
let outcome = self.get_match_outcome(&players)?; let outcome = self.get_match_outcome(&players)?;
let mut lines = Vec::<String>::new(); let mut lines = Vec::<String>::new();
lines.push(format!( lines.push(format!(
"{} <u><b>{}</b></u>", "{} <u><b>{}</b></u>",
self.emojify_outcome(outcome), self.emojify_outcome(outcome),
self.stringify_outcome(outcome), self.stringify_outcome(outcome),
)); ));
lines.push(format!( lines.push(format!(
"<b>{}</b> · {} · <i>{}</i>", "<b>{}</b> · {} · <i>{}</i>",
self.stringify_type(r#type), self.stringify_type(r#type),
self.stringify_mode(mode), self.stringify_mode(mode),
self.stringify_duration(duration), self.stringify_duration(duration),
)); ));
lines.push("".to_string()); lines.push("".to_string());
for player in players.into_iter() { for player in players.into_iter() {
let string = self.stringify_player(database, player, outcome == MatchOutcome::Clash)?; let string = self.stringify_player(database, player, outcome == MatchOutcome::Clash)?;
lines.push(string); lines.push(string);
lines.push("".to_string()); lines.push("".to_string());
} }
lines.push(format!( lines.push(format!(
"Partita <code>{}</code>", "Partita <code>{}</code>",
match_id, match_id,
)); ));
Ok((match_id, Some(lines.join("\n")))) Ok((match_id, Some(lines.join("\n"))))
} }
async fn send_notification(&self, match_id: DotaMatchId, text: &str) -> AnyResult<Message> { async fn send_notification(&self, match_id: DotaMatchId, text: &str) -> AnyResult<Message> {
log::debug!("Sending notification..."); log::debug!("Sending notification...");
self.telegram_bot.send_message(self.notification_chat_id, text) self.telegram_bot.send_message(self.notification_chat_id, text)
.parse_mode(teloxide::types::ParseMode::Html) .parse_mode(teloxide::types::ParseMode::Html)
.disable_notification(true) .disable_notification(true)
@ -735,26 +735,26 @@ impl BroochService {
.await .await
.context("Impossibile inviare la notifica di una partita.") .context("Impossibile inviare la notifica di una partita.")
} }
async fn iteration(&self) -> AnyResult<()> { async fn iteration(&self) -> AnyResult<()> {
log::debug!("Now running an iteration of brooch!"); log::debug!("Now running an iteration of brooch!");
let client = self.create_http_client()?; let client = self.create_http_client()?;
let mut database = self.create_postgres_connection()?; let mut database = self.create_postgres_connection()?;
let data = self.query_guild_matches(&client).await?; let data = self.query_guild_matches(&client).await?;
let guild = self.process_guild_data(data)?; let guild = self.process_guild_data(data)?;
let matches = self.process_matches_data(guild)?; let matches = self.process_matches_data(guild)?;
let results = matches let results = matches
.into_iter() .into_iter()
.map(|r#match| self.stringify_match(&mut database, r#match)) .map(|r#match| self.stringify_match(&mut database, r#match))
.collect::<Vec<AnyResult<(DotaMatchId, Option<String>)>>>(); .collect::<Vec<AnyResult<(DotaMatchId, Option<String>)>>>();
for result in results { for result in results {
let (match_id, message) = result?; let (match_id, message) = result?;
if let Some(message) = message { if let Some(message) = message {
self.send_notification(match_id, &message).await?; self.send_notification(match_id, &message).await?;
BroochMatch::flag(&mut database, match_id)?; BroochMatch::flag(&mut database, match_id)?;
@ -769,7 +769,7 @@ impl RoyalnetService for BroochService {
async fn run(&mut self) -> AnyResult<()> { async fn run(&mut self) -> AnyResult<()> {
loop { loop {
self.iteration().await?; self.iteration().await?;
sleep(Duration::new(60 * 15, 0)).await; sleep(Duration::new(60 * 15, 0)).await;
} }
} }

View file

@ -7,30 +7,30 @@ use crate::utils::anyhow_result::AnyResult;
#[allow(dead_code)] #[allow(dead_code)]
pub trait RoyalnetService { pub trait RoyalnetService {
async fn run(&mut self) -> AnyResult<()>; async fn run(&mut self) -> AnyResult<()>;
async fn run_loop(&mut self) { async fn run_loop(&mut self) {
let mut backoff = Duration::new(1, 0); let mut backoff = Duration::new(1, 0);
loop { loop {
let result = self.run().await; let result = self.run().await;
match result { match result {
Err(e) => { Err(e) => {
log::error!("Service exited with error: {e:?}.") log::error!("Service exited with error: {e:?}.")
}, }
_ => { _ => {
log::debug!("Service exited successfully!") log::debug!("Service exited successfully!")
}, }
} }
let backoff_secs = backoff.as_secs(); let backoff_secs = backoff.as_secs();
log::debug!("Backing off for {backoff_secs} seconds before restarting..."); log::debug!("Backing off for {backoff_secs} seconds before restarting...");
sleep(backoff).await; sleep(backoff).await;
log::trace!("Doubling backoff value..."); log::trace!("Doubling backoff value...");
backoff *= 2; backoff *= 2;
log::trace!("Backoff value is now {backoff_secs} seconds."); log::trace!("Backoff value is now {backoff_secs} seconds.");
} }
} }

View file

@ -12,83 +12,83 @@ use crate::services::telegram::commands::CommandResult;
// Se avete un'idea ma metterebbe troppe opzioni in un'unica categoria, mettetela sotto commento. // Se avete un'idea ma metterebbe troppe opzioni in un'unica categoria, mettetela sotto commento.
const ANSWERS: [&str; 60] = [ const ANSWERS: [&str; 60] = [
// risposte "sì": 20 // risposte "sì": 20
"🔵 Sì.", "🔵 Sì.",
"🔵 Decisamente sì!", "🔵 Decisamente sì!",
"🔵 Uhm, secondo me sì.", "🔵 Uhm, secondo me sì.",
"🔵 Sì! Sì! SÌ!", "🔵 Sì! Sì! SÌ!",
"🔵 Yup.", "🔵 Yup.",
"🔵 Direi proprio di sì.", "🔵 Direi proprio di sì.",
"🔵 Assolutamente sì.", "🔵 Assolutamente sì.",
"🔵 Ma certo!", "🔵 Ma certo!",
"🔵 Esatto!", "🔵 Esatto!",
"🔵 Senz'altro!", "🔵 Senz'altro!",
"🔵 Ovviamente.", "🔵 Ovviamente.",
"🔵 Questa domanda ha risposta affermativa.", "🔵 Questa domanda ha risposta affermativa.",
"🔵 Hell yeah.", "🔵 Hell yeah.",
"🔵 YES! YES! YES!", "🔵 YES! YES! YES!",
"🔵 yusssssss", "🔵 yusssssss",
"🔵 Non vedo perchè no", "🔵 Non vedo perchè no",
"🔵 Ha senso, ha perfettamente senso, nulla da obiettare, ha senso.", "🔵 Ha senso, ha perfettamente senso, nulla da obiettare, ha senso.",
"🔵 Yos!", "🔵 Yos!",
"🔵 Sì, ma tienilo segreto...", "🔵 Sì, ma tienilo segreto...",
"🔵 [RADIO] Affermativo.", "🔵 [RADIO] Affermativo.",
// risposte "no": 20 // risposte "no": 20
"❌ No.", "❌ No.",
"❌ Decisamente no!", "❌ Decisamente no!",
"❌ Uhm, secondo me sì. No, aspetta, ci ho ripensato. È un no.", "❌ Uhm, secondo me sì. No, aspetta, ci ho ripensato. È un no.",
"❌ No, no, e ancora NO!", "❌ No, no, e ancora NO!",
"❌ Nope.", "❌ Nope.",
"❌ Direi proprio di no.", "❌ Direi proprio di no.",
"❌ Assolutamente no.", "❌ Assolutamente no.",
"❌ Certo che no!", "❌ Certo che no!",
"❌ Neanche per idea!", "❌ Neanche per idea!",
"❌ Neanche per sogno!", "❌ Neanche per sogno!",
"❌ Niente affatto!", "❌ Niente affatto!",
"❌ Questa domanda ha risposta negativa.", "❌ Questa domanda ha risposta negativa.",
"❌ Hell no.", "❌ Hell no.",
"❌ NO! NO! NO!", "❌ NO! NO! NO!",
"❌ lolno", "❌ lolno",
"❌ NEIN NEIN NEIN NEIN", "❌ NEIN NEIN NEIN NEIN",
"❌ Delet dis", "❌ Delet dis",
"❌ Nopety nope!", "❌ Nopety nope!",
"❌ No, ma tienilo segreto.", "❌ No, ma tienilo segreto.",
"❌ [RADIO] Negativo.", "❌ [RADIO] Negativo.",
// risposte "boh": 20 // risposte "boh": 20
"❔ Boh.", "❔ Boh.",
"❔ E io che ne so?!", "❔ E io che ne so?!",
"❔ Non so proprio rispondere.", "❔ Non so proprio rispondere.",
"❔ Non lo so...", "❔ Non lo so...",
"❔ Mi avvalgo della facoltà di non rispondere.", "❔ Mi avvalgo della facoltà di non rispondere.",
"❔ Non parlerò senza il mio avvocato!", "❔ Non parlerò senza il mio avvocato!",
"❔ Dunno.", "❔ Dunno.",
"❔ Perché lo chiedi a me?", "❔ Perché lo chiedi a me?",
"❔ Ah, non lo so io!", "❔ Ah, non lo so io!",
r#"❔ ¯\_(ツ)_/¯"#, r#"❔ ¯\_(ツ)_/¯"#,
"❔ No idea.", "❔ No idea.",
"❔ Dunno.", "❔ Dunno.",
"❔ Boooooh!", "❔ Boooooh!",
"❔ Non ne ho la più pallida idea.", "❔ Non ne ho la più pallida idea.",
"❔ No comment.", "❔ No comment.",
"❔ maibi", "❔ maibi",
"❔ maibi not", "❔ maibi not",
"❔ idk dude", "❔ idk dude",
"❔ Non mi è permesso condividere questa informazione.", "❔ Non mi è permesso condividere questa informazione.",
"❔ [RADIO] Mantengo la posizione.", "❔ [RADIO] Mantengo la posizione.",
]; ];
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
let mut rng = rand::rngs::SmallRng::from_entropy(); let mut rng = rand::rngs::SmallRng::from_entropy();
let answer = ANSWERS.choose(&mut rng) let answer = ANSWERS.choose(&mut rng)
.context("Non è stato possibile selezionare una risposta.")?; .context("Non è stato possibile selezionare una risposta.")?;
let _reply = bot let _reply = bot
.send_message(message.chat.id, answer.to_string()) .send_message(message.chat.id, answer.to_string())
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -26,17 +26,17 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
.context("Il gatto ricevuto in risposta dall'API è indecifrabile, quindi non è stato possibile riceverlo.")? .context("Il gatto ricevuto in risposta dall'API è indecifrabile, quindi non è stato possibile riceverlo.")?
.pop() .pop()
.context("Il gatto ricevuto in risposta dall'API non esiste, quindi non è stato possibile riceverlo.")?; .context("Il gatto ricevuto in risposta dall'API non esiste, quindi non è stato possibile riceverlo.")?;
let url: Url = response.url.parse() let url: Url = response.url.parse()
.context("L'URL del gatto ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?; .context("L'URL del gatto ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?;
let input = InputFile::url(url); let input = InputFile::url(url);
let _reply = bot let _reply = bot
.send_photo(message.chat.id, input) .send_photo(message.chat.id, input)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare un gatto in risposta a questo messaggio.")?; .context("Non è stato possibile inviare un gatto in risposta a questo messaggio.")?;
Ok(()) Ok(())
} }

View file

@ -25,68 +25,69 @@ pub struct DiarioArgs {
impl FromStr for DiarioArgs { impl FromStr for DiarioArgs {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#" *(?:\[(?<warning>.+)])? *"(?<quote>.+)"[, ]*(?:[-–—]+(?<quoted>\w+)(?:, *(?<context>.+))?)?"#).unwrap()); static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#" *(?:\[(?<warning>.+)])? *"(?<quote>.+)"[, ]*(?:[-–—]+(?<quoted>\w+)(?:, *(?<context>.+))?)?"#).unwrap());
let captures = REGEX.captures(s); let captures = REGEX.captures(s);
let args = match captures { let args = match captures {
Some(captures) => { Some(captures) => {
let warning = captures.name("warning") let warning = captures.name("warning")
.map(|s| s.as_str().to_owned()); .map(|s| s.as_str().to_owned());
let quote = captures.name("quote") let quote = captures.name("quote")
.context("Citazione non specificata nel comando.")? .context("Citazione non specificata nel comando.")?
.as_str() .as_str()
.to_owned(); .to_owned();
let quoted = captures.name("quoted") let quoted = captures.name("quoted")
.map(|s| s.as_str().to_owned()); .map(|s| s.as_str().to_owned());
let context = captures.name("context") let context = captures.name("context")
.map(|s| s.as_str().to_owned()); .map(|s| s.as_str().to_owned());
DiarioArgs {warning, quote, quoted, context} DiarioArgs { warning, quote, quoted, context }
}, }
None => { None => {
let warning = None; let warning = None;
let quote = s.to_string(); let quote = s.to_string();
let quoted = None; let quoted = None;
let context = None; let context = None;
DiarioArgs {warning, quote, quoted, context} DiarioArgs { warning, quote, quoted, context }
}, }
}; };
Ok(args) Ok(args)
} }
} }
impl TelegramWrite for Diario { impl TelegramWrite for Diario {
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error> fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
where T: Write where
T: Write,
{ {
// Diario ID // Diario ID
write!(f, "<code>#{}</code>", self.id)?; write!(f, "<code>#{}</code>", self.id)?;
// Optional content warning // Optional content warning
if let Some(warning) = self.to_owned().warning { if let Some(warning) = self.to_owned().warning {
write!(f, ", <b>{}</b>", warning.escape_telegram_html())?; write!(f, ", <b>{}</b>", warning.escape_telegram_html())?;
} }
// Newline // Newline
writeln!(f)?; writeln!(f)?;
// Quote optionally covered by a spoiler tag // Quote optionally covered by a spoiler tag
match self.warning.to_owned() { match self.warning.to_owned() {
None => write!(f, "<blockquote expandable>{}</blockquote>", self.clone().quote.escape_telegram_html())?, None => write!(f, "<blockquote expandable>{}</blockquote>", self.clone().quote.escape_telegram_html())?,
Some(_) => write!(f, "<blockquote expandable><tg-spoiler>{}</tg-spoiler></blockquote>", self.clone().quote.escape_telegram_html())?, Some(_) => write!(f, "<blockquote expandable><tg-spoiler>{}</tg-spoiler></blockquote>", self.clone().quote.escape_telegram_html())?,
} }
// Newline // Newline
writeln!(f)?; writeln!(f)?;
// Optional citation with optional context // Optional citation with optional context
match (self.quoted_name.to_owned(), self.context.to_owned()) { match (self.quoted_name.to_owned(), self.context.to_owned()) {
(Some(name), Some(context)) => write!(f, "—{}, <i>{}</i>", name.escape_telegram_html(), context.escape_telegram_html())?, (Some(name), Some(context)) => write!(f, "—{}, <i>{}</i>", name.escape_telegram_html(), context.escape_telegram_html())?,
@ -94,7 +95,7 @@ impl TelegramWrite for Diario {
(None, Some(context)) => write!(f, "...<i>{}</i>", context.escape_telegram_html())?, (None, Some(context)) => write!(f, "...<i>{}</i>", context.escape_telegram_html())?,
(None, None) => write!(f, "")?, (None, None) => write!(f, "")?,
}; };
Ok(()) Ok(())
} }
} }
@ -102,16 +103,16 @@ impl TelegramWrite for Diario {
pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database: &DatabaseInterface) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database: &DatabaseInterface) -> CommandResult {
let author = message.from.as_ref() let author = message.from.as_ref()
.context("Non è stato possibile determinare chi ha inviato questo comando.")?; .context("Non è stato possibile determinare chi ha inviato questo comando.")?;
let mut database = database.connect()?; let mut database = database.connect()?;
let royalnet_user: RoyalnetUser = { let royalnet_user: RoyalnetUser = {
use diesel::prelude::*; use diesel::prelude::*;
use diesel::{ExpressionMethods, QueryDsl}; use diesel::{ExpressionMethods, QueryDsl};
use crate::interfaces::database::schema::telegram::dsl::*; use crate::interfaces::database::schema::telegram::dsl::*;
use crate::interfaces::database::schema::users::dsl::*; use crate::interfaces::database::schema::users::dsl::*;
use crate::interfaces::database::models::RoyalnetUser; use crate::interfaces::database::models::RoyalnetUser;
telegram telegram
.filter(telegram_id.eq::<i64>( .filter(telegram_id.eq::<i64>(
author.id.0.try_into() author.id.0.try_into()
@ -122,10 +123,10 @@ pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database:
.get_result(&mut database) .get_result(&mut database)
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")? .context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?
}; };
let entry = { let entry = {
use schema::diario; use schema::diario;
insert_into(diario::table) insert_into(diario::table)
.values(&( .values(&(
diario::saver_id.eq(Some(royalnet_user.id)), diario::saver_id.eq(Some(royalnet_user.id)),
@ -137,14 +138,14 @@ pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database:
.get_result::<Diario>(&mut database) .get_result::<Diario>(&mut database)
.context("Non è stato possibile aggiungere la riga di diario al database RYG.")? .context("Non è stato possibile aggiungere la riga di diario al database RYG.")?
}; };
let text = format!( let text = format!(
"🖋 Riga aggiunta al diario!\n\ "🖋 Riga aggiunta al diario!\n\
\n\ \n\
{}", {}",
entry.to_string_telegram(), entry.to_string_telegram(),
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
@ -153,6 +154,6 @@ pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database:
// teloxide does not support blockquotes yet and errors out on parsing the response // teloxide does not support blockquotes yet and errors out on parsing the response
// .context("Non è stato possibile inviare la risposta.")? // .context("Non è stato possibile inviare la risposta.")?
; ;
Ok(()) Ok(())
} }

View file

@ -22,17 +22,17 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
.context("Non è stato possibile richiedere un cane all'API.")? .context("Non è stato possibile richiedere un cane all'API.")?
.json::<DogQueryResponse>().await .json::<DogQueryResponse>().await
.context("Il cane ricevuto in risposta dall'API è indecifrabile, quindi non è stato possibile riceverlo.")?; .context("Il cane ricevuto in risposta dall'API è indecifrabile, quindi non è stato possibile riceverlo.")?;
let url: Url = response.message.parse() let url: Url = response.message.parse()
.context("L'URL del cane ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?; .context("L'URL del cane ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?;
let input = InputFile::url(url); let input = InputFile::url(url);
let _reply = bot let _reply = bot
.send_photo(message.chat.id, input) .send_photo(message.chat.id, input)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare un cane in risposta a questo messaggio.")?; .context("Non è stato possibile inviare un cane in risposta a questo messaggio.")?;
Ok(()) Ok(())
} }

View file

@ -10,12 +10,12 @@ pub async fn handler(bot: &Bot, message: &Message, text: &str) -> CommandResult
let text = format!( let text = format!(
"💬 {text}" "💬 {text}"
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -174,21 +174,21 @@ const FORTUNES: [&str; 164] = [
"🍻 Oggi una birra ti ridarà una vita!", "🍻 Oggi una birra ti ridarà una vita!",
"🎶 Oggi Hatsune Miku si nasconderà nella tua Wi-Fi!", "🎶 Oggi Hatsune Miku si nasconderà nella tua Wi-Fi!",
"🚽 Oggi delle telecamere combatteranno contro dei gabinetti!", "🚽 Oggi delle telecamere combatteranno contro dei gabinetti!",
"🌟 Oggi verrà scoperta una galassia grande quanto qualcuno della tua famiglia!", "🌟 Oggi verrà scoperta una galassia grande quanto qualcuno della tua famiglia!",
"🎶 Oggi Rick non rinuncerà mai a te!", "🎶 Oggi Rick non rinuncerà mai a te!",
"🏚 Oggi ristrutturerai una villa completando dei minigiochi match-3!", "🏚 Oggi ristrutturerai una villa completando dei minigiochi match-3!",
]; ];
struct FortuneKey { struct FortuneKey {
today: chrono::NaiveDate, today: chrono::NaiveDate,
author_id: teloxide::types::UserId author_id: teloxide::types::UserId,
} }
impl Hash for FortuneKey { impl Hash for FortuneKey {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
let days: i32 = self.today.num_days_from_ce(); let days: i32 = self.today.num_days_from_ce();
let id: u64 = self.author_id.0; let id: u64 = self.author_id.0;
state.write_i32(days); state.write_i32(days);
state.write_u64(id); state.write_u64(id);
} }
@ -196,13 +196,13 @@ impl Hash for FortuneKey {
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
let today = chrono::Local::now().date_naive(); let today = chrono::Local::now().date_naive();
let author = message.from.as_ref() let author = message.from.as_ref()
.context("Non è stato possibile determinare chi ha inviato questo comando.")?; .context("Non è stato possibile determinare chi ha inviato questo comando.")?;
let author_id = author.id; let author_id = author.id;
let key = FortuneKey {today, author_id}; let key = FortuneKey { today, author_id };
let mut hasher = std::hash::DefaultHasher::new(); let mut hasher = std::hash::DefaultHasher::new();
key.hash(&mut hasher); key.hash(&mut hasher);
let hash = hasher.finish() let hash = hasher.finish()
@ -216,17 +216,17 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
anyhow::bail!("Non è stato possibile determinare il tuo oroscopo."); anyhow::bail!("Non è stato possibile determinare il tuo oroscopo.");
} }
let hash = hash.unwrap(); let hash = hash.unwrap();
let mut rng = rand::rngs::SmallRng::from_seed(hash); let mut rng = rand::rngs::SmallRng::from_seed(hash);
let fortune = FORTUNES.choose(&mut rng) let fortune = FORTUNES.choose(&mut rng)
.context("Non è stato possibile selezionare il tuo oroscopo.")?; .context("Non è stato possibile selezionare il tuo oroscopo.")?;
let _reply = bot let _reply = bot
.send_message(message.chat.id, fortune.to_string()) .send_message(message.chat.id, fortune.to_string())
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -9,16 +9,16 @@ use super::CommandResult;
pub async fn handler_all(bot: &Bot, message: &Message) -> CommandResult { pub async fn handler_all(bot: &Bot, message: &Message) -> CommandResult {
let descriptions = super::Command::descriptions().to_string(); let descriptions = super::Command::descriptions().to_string();
let text = format!("❔ <b>Comandi disponibili</b>\n\n{descriptions}"); let text = format!("❔ <b>Comandi disponibili</b>\n\n{descriptions}");
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }
@ -27,51 +27,51 @@ pub async fn handler_specific(bot: &Bot, message: &Message, target: &str) -> Com
.get_me() .get_me()
.await .await
.context("Non è stato possibile recuperare informazioni sul bot.")?; .context("Non è stato possibile recuperare informazioni sul bot.")?;
let me_username = me.username.as_ref() let me_username = me.username.as_ref()
.context("Non è stato possibile determinare l'username del bot.")?; .context("Non è stato possibile determinare l'username del bot.")?;
let suffix = format!("@{me_username}"); let suffix = format!("@{me_username}");
let target = target.strip_prefix('/') let target = target.strip_prefix('/')
.unwrap_or(target); .unwrap_or(target);
let target = target.strip_suffix(&suffix) let target = target.strip_suffix(&suffix)
.unwrap_or(target); .unwrap_or(target);
log::trace!("Stripped target command: {target:?}"); log::trace!("Stripped target command: {target:?}");
let all_commands: Vec<BotCommand> = super::Command::bot_commands(); let all_commands: Vec<BotCommand> = super::Command::bot_commands();
log::trace!("All commands are: {all_commands:?}"); log::trace!("All commands are: {all_commands:?}");
let identify_command = |cmd: &&BotCommand| -> bool { let identify_command = |cmd: &&BotCommand| -> bool {
let command = &cmd.command; let command = &cmd.command;
let command = command.strip_prefix('/') let command = command.strip_prefix('/')
.unwrap_or_else(|| command); .unwrap_or_else(|| command);
target == command target == command
}; };
let target = match all_commands.iter().find(identify_command) { let target = match all_commands.iter().find(identify_command) {
Some(bot_command) => bot_command, Some(bot_command) => bot_command,
None => anyhow::bail!("Non è stato possibile trovare il comando specificato."), None => anyhow::bail!("Non è stato possibile trovare il comando specificato."),
}; };
let display_suffix = match message.chat.is_private() { let display_suffix = match message.chat.is_private() {
false => &suffix, false => &suffix,
true => "", true => "",
}; };
let text = format!("❔ <b>Comando {}{}</b>\n\n{}", target.command, display_suffix, target.description); let text = format!("❔ <b>Comando {}{}</b>\n\n{}", target.command, display_suffix, target.description);
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -21,26 +21,26 @@ pub struct MatchmakingArgs {
impl FromStr for MatchmakingArgs { impl FromStr for MatchmakingArgs {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<start>.*)]\s*(?<text>.+)$").unwrap()); static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<start>.*)]\s*(?<text>.+)$").unwrap());
let captures = REGEX.captures(s) let captures = REGEX.captures(s)
.context("Sintassi del comando incorretta.")?; .context("Sintassi del comando incorretta.")?;
let start = captures.name("start") let start = captures.name("start")
.unwrap() .unwrap()
.as_str(); .as_str();
let text = captures.name("text") let text = captures.name("text")
.unwrap() .unwrap()
.as_str() .as_str()
.to_string(); .to_string();
let start = parse_datetime_at_date(chrono::Local::now(), start) let start = parse_datetime_at_date(chrono::Local::now(), start)
.context("Impossibile determinare la data in cui l'attesa avrà termine.")? .context("Impossibile determinare la data in cui l'attesa avrà termine.")?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
Ok( Ok(
Self { start, text } Self { start, text }
) )
@ -49,23 +49,23 @@ impl FromStr for MatchmakingArgs {
pub async fn handler(bot: &Bot, message: &Message, args: &MatchmakingArgs, database: &DatabaseInterface) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message, args: &MatchmakingArgs, database: &DatabaseInterface) -> CommandResult {
let mut database = database.connect()?; let mut database = database.connect()?;
let event = MatchmakingEvent::create(&mut database, &args.text, &args.start) let event = MatchmakingEvent::create(&mut database, &args.text, &args.start)
.context("Non è stato possibile creare un nuovo matchmaking.")?; .context("Non è stato possibile creare un nuovo matchmaking.")?;
let mm1 = MatchmakingMessageTelegram::send_new_and_create(&mut database, event.id, bot, message.chat.id, Some(message.id)) let mm1 = MatchmakingMessageTelegram::send_new_and_create(&mut database, event.id, bot, message.chat.id, Some(message.id))
.await .await
.context("Non è stato possibile postare il matchmaking.")?; .context("Non è stato possibile postare il matchmaking.")?;
sleep_chrono(&args.start).await; sleep_chrono(&args.start).await;
let _mm2 = MatchmakingMessageTelegram::send_new_and_create(&mut database, event.id, bot, message.chat.id, Some(message.id)) let _mm2 = MatchmakingMessageTelegram::send_new_and_create(&mut database, event.id, bot, message.chat.id, Some(message.id))
.await .await
.context("Non è stato possibile confermare il matchmaking.")?; .context("Non è stato possibile confermare il matchmaking.")?;
mm1.destroy_and_send_delete(&mut database, bot) mm1.destroy_and_send_delete(&mut database, bot)
.await .await
.context("Non è stato possibile eliminare il matchmaking.")?; .context("Non è stato possibile eliminare il matchmaking.")?;
Ok(()) Ok(())
} }

View file

@ -34,7 +34,9 @@ type CommandResult = AnyResult<()>;
pub enum Command { pub enum Command {
#[command(description = "Invia messaggio di introduzione.")] #[command(description = "Invia messaggio di introduzione.")]
Start, Start,
#[command(description = "Visualizza l'elenco dei comandi disponibili, o mostra informazioni su uno specifico comando.")] #[command(
description = "Visualizza l'elenco dei comandi disponibili, o mostra informazioni su uno specifico comando."
)]
Help(String), Help(String),
#[command(description = "Mostra il tuo oroscopo di oggi.")] #[command(description = "Mostra il tuo oroscopo di oggi.")]
Fortune, Fortune,
@ -62,28 +64,28 @@ impl Command {
/// Update the [commands menu](https://core.telegram.org/bots/features#commands) of the bot. /// Update the [commands menu](https://core.telegram.org/bots/features#commands) of the bot.
pub async fn set_commands(bot: &mut Bot) -> AnyResult<()> { pub async fn set_commands(bot: &mut Bot) -> AnyResult<()> {
log::debug!("Setting bot commands..."); log::debug!("Setting bot commands...");
log::trace!("Determining bot commands..."); log::trace!("Determining bot commands...");
let commands = Self::bot_commands(); let commands = Self::bot_commands();
log::trace!("Setting commands: {commands:#?}"); log::trace!("Setting commands: {commands:#?}");
bot.set_my_commands(commands).await bot.set_my_commands(commands).await
.context("Non è stato possibile aggiornare la lista comandi del bot.")?; .context("Non è stato possibile aggiornare la lista comandi del bot.")?;
log::trace!("Setting commands successful!"); log::trace!("Setting commands successful!");
Ok(()) Ok(())
} }
pub async fn handle_self(self, bot: Bot, message: Message, database: Arc<DatabaseInterface>) -> CommandResult { pub async fn handle_self(self, bot: Bot, message: Message, database: Arc<DatabaseInterface>) -> CommandResult {
log::debug!("Handling command..."); log::debug!("Handling command...");
log::trace!( log::trace!(
"Handling {:?} in {:?} with {:?}...", "Handling {:?} in {:?} with {:?}...",
self, self,
&message.chat.id, &message.chat.id,
&message.id, &message.id,
); );
// FIXME: Quick hack to fix single thread // FIXME: Quick hack to fix single thread
log::trace!("Spawning task for future..."); log::trace!("Spawning task for future...");
let _task = tokio::spawn(async move { let _task = tokio::spawn(async move {
@ -105,28 +107,28 @@ impl Command {
Command::Diario(ref args) => diario::handler(&bot, &message, args, &database).await, Command::Diario(ref args) => diario::handler(&bot, &message, args, &database).await,
Command::Matchmaking(ref args) => matchmaking::handler(&bot, &message, args, &database).await, Command::Matchmaking(ref args) => matchmaking::handler(&bot, &message, args, &database).await,
}; };
log::trace!("Delegating error handling to error handler..."); log::trace!("Delegating error handling to error handler...");
let result2 = match result1.as_ref() { let result2 = match result1.as_ref() {
Ok(_) => return, Ok(_) => return,
Err(e1) => self.handle_error(&bot, &message, e1).await Err(e1) => self.handle_error(&bot, &message, e1).await
}; };
let e1 = result1.unwrap_err(); let e1 = result1.unwrap_err();
log::trace!("Delegating fatal error handling to fatal error handler..."); log::trace!("Delegating fatal error handling to fatal error handler...");
let _result3 = match result2 { let _result3 = match result2 {
Ok(_) => return, Ok(_) => return,
Err(e2) => self.handle_fatal(&bot, &message, &e1, &e2).await Err(e2) => self.handle_fatal(&bot, &message, &e1, &e2).await
}; };
log::trace!("Successfully handled command!"); log::trace!("Successfully handled command!");
}); });
log::trace!("Successfully spawned task!"); log::trace!("Successfully spawned task!");
Ok(()) Ok(())
} }
pub async fn handle_unknown(bot: Bot, message: Message) -> CommandResult { pub async fn handle_unknown(bot: Bot, message: Message) -> CommandResult {
log::debug!("Received an unknown command or an invalid syntax: {:?}", message.text()); log::debug!("Received an unknown command or an invalid syntax: {:?}", message.text());
@ -136,11 +138,11 @@ impl Command {
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare il messaggio di errore.")?; .context("Non è stato possibile inviare il messaggio di errore.")?;
log::trace!("Successfully handled unknown command!"); log::trace!("Successfully handled unknown command!");
Ok(()) Ok(())
} }
async fn handle_error(&self, bot: &Bot, message: &Message, error: &Error) -> CommandResult { async fn handle_error(&self, bot: &Bot, message: &Message, error: &Error) -> CommandResult {
log::debug!( log::debug!(
"Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`", "Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`",
@ -156,11 +158,11 @@ impl Command {
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare il messaggio di errore.")?; .context("Non è stato possibile inviare il messaggio di errore.")?;
log::trace!("Successfully handled errored command!"); log::trace!("Successfully handled errored command!");
Ok(()) Ok(())
} }
async fn handle_fatal(&self, _bot: &Bot, message: &Message, error1: &Error, error2: &Error) -> CommandResult { async fn handle_fatal(&self, _bot: &Bot, message: &Message, error1: &Error, error2: &Error) -> CommandResult {
log::error!( log::error!(
"Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`, and it was impossible to handle the error because of `{:?}`", "Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`, and it was impossible to handle the error because of `{:?}`",
@ -170,7 +172,7 @@ impl Command {
error1, error1,
error2, error2,
); );
Ok(()) Ok(())
} }
} }

View file

@ -22,26 +22,26 @@ pub struct ReminderArgs {
impl FromStr for ReminderArgs { impl FromStr for ReminderArgs {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<target>.*)]\s*(?<reminder>.+)$").unwrap()); static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<target>.*)]\s*(?<reminder>.+)$").unwrap());
let captures = REGEX.captures(s) let captures = REGEX.captures(s)
.context("Sintassi del comando incorretta.")?; .context("Sintassi del comando incorretta.")?;
let target = captures.name("target") let target = captures.name("target")
.unwrap() .unwrap()
.as_str(); .as_str();
let reminder = captures.name("reminder") let reminder = captures.name("reminder")
.unwrap() .unwrap()
.as_str() .as_str()
.to_string(); .to_string();
let target = parse_datetime_at_date(chrono::Local::now(), target) let target = parse_datetime_at_date(chrono::Local::now(), target)
.context("Impossibile determinare la data in cui l'attesa avrà termine.")? .context("Impossibile determinare la data in cui l'attesa avrà termine.")?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
Ok( Ok(
ReminderArgs { target, reminder } ReminderArgs { target, reminder }
) )
@ -57,16 +57,16 @@ pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, remind
target.format("%c").to_string().escape_telegram_html(), target.format("%c").to_string().escape_telegram_html(),
reminder.clone().escape_telegram_html() reminder.clone().escape_telegram_html()
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la conferma del promemoria.")?; .context("Non è stato possibile inviare la conferma del promemoria.")?;
sleep_chrono(target).await; sleep_chrono(target).await;
let text = format!( let text = format!(
"🕒 <b>Promemoria attivato</b>\n\ "🕒 <b>Promemoria attivato</b>\n\
<i>{}</i>\n\ <i>{}</i>\n\
@ -75,13 +75,13 @@ pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, remind
target.format("%c").to_string().escape_telegram_html(), target.format("%c").to_string().escape_telegram_html(),
reminder.escape_telegram_html() reminder.escape_telegram_html()
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare il promemoria.")?; .context("Non è stato possibile inviare il promemoria.")?;
Ok(()) Ok(())
} }

View file

@ -9,20 +9,20 @@ use teloxide::types::ReplyParameters;
use crate::services::telegram::commands::CommandResult; use crate::services::telegram::commands::CommandResult;
pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult {
let mut rng = rand::rngs::SmallRng::from_entropy(); let mut rng = rand::rngs::SmallRng::from_entropy();
if rng.gen_range(1..1001) == 1 { if rng.gen_range(1..1001) == 1 {
let _reply = bot let _reply = bot
.send_message(message.chat.id, "🎶 Roll? Rick roll! https://www.youtube.com/watch?v=dQw4w9WgXcQ") .send_message(message.chat.id, "🎶 Roll? Rick roll! https://www.youtube.com/watch?v=dQw4w9WgXcQ")
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
return Ok(()) return Ok(());
} }
let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]+[0-9]+)?"#).unwrap(); let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]+[0-9]+)?"#).unwrap();
let captures = re.captures(roll) let captures = re.captures(roll)
.context("Sintassi dei dadi non corretta.")?; .context("Sintassi dei dadi non corretta.")?;
@ -31,58 +31,58 @@ pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult
.map(|m| m.parse::<u32>()) .map(|m| m.parse::<u32>())
.unwrap_or(Ok(1)) .unwrap_or(Ok(1))
.context("La quantità di dadi da lanciare deve essere un numero intero positivo diverso da 0.")?; .context("La quantità di dadi da lanciare deve essere un numero intero positivo diverso da 0.")?;
let die = captures.name("die") let die = captures.name("die")
.unwrap() .unwrap()
.as_str() .as_str()
.parse::<u32>() .parse::<u32>()
.context("La dimensione del dado da lanciare deve essere un numero intero positivo.")?; .context("La dimensione del dado da lanciare deve essere un numero intero positivo.")?;
let modifier = captures.name("modifier") let modifier = captures.name("modifier")
.map(|m| m.as_str()) .map(|m| m.as_str())
.map(|m| m.parse::<i32>()) .map(|m| m.parse::<i32>())
.unwrap_or(Ok(0)) .unwrap_or(Ok(0))
.context("Il modificatore dei dadi lanciati deve essere un numero intero.")?; .context("Il modificatore dei dadi lanciati deve essere un numero intero.")?;
if die == 0 { if die == 0 {
anyhow::bail!("Non è stato specificato nessun dado.") anyhow::bail!("Non è stato specificato nessun dado.")
} }
if qty < 1 { if qty < 1 {
anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.") anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.")
} }
let mut nums_rolled = Vec::<u32>::new(); let mut nums_rolled = Vec::<u32>::new();
for _ in 0..qty { for _ in 0..qty {
nums_rolled.push( nums_rolled.push(
rng.gen_range(1..=die) rng.gen_range(1..=die)
); );
} }
let roll_string = nums_rolled let roll_string = nums_rolled
.iter() .iter()
.map(|n| n.to_string()) .map(|n| n.to_string())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n"); .join("\n");
let mut answer = format!("🎲 [{roll_string}]");
if modifier != 0 {
answer.push_str(&format!("{modifier:+}"))
}
answer.push_str(" = ");
let sum: u32 = nums_rolled.iter().sum();
let sum: i32 = sum as i32 + modifier;
answer.push_str(&sum.to_string()); let mut answer = format!("🎲 [{roll_string}]");
if modifier != 0 {
answer.push_str(&format!("{modifier:+}"))
}
answer.push_str(" = ");
let sum: u32 = nums_rolled.iter().sum();
let sum: i32 = sum as i32 + modifier;
answer.push_str(&sum.to_string());
let _reply = bot let _reply = bot
.send_message(message.chat.id, answer) .send_message(message.chat.id, answer)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -9,38 +9,38 @@ use super::CommandResult;
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
let author = message.from.as_ref() let author = message.from.as_ref()
.context("Non è stato possibile determinare chi ha inviato questo comando.")?; .context("Non è stato possibile determinare chi ha inviato questo comando.")?;
let author_username = match author.username.as_ref() { let author_username = match author.username.as_ref() {
None => { None => {
author.first_name.clone() author.first_name.clone()
}, }
Some(username) => { Some(username) => {
format!("@{}", &username) format!("@{}", &username)
}, }
}; };
let me = bot let me = bot
.get_me() .get_me()
.await .await
.context("Non è stato possibile recuperare informazioni sul bot.")?; .context("Non è stato possibile recuperare informazioni sul bot.")?;
let me_username = me.username.as_ref() let me_username = me.username.as_ref()
.context("Non è stato possibile determinare l'username del bot.")?; .context("Non è stato possibile determinare l'username del bot.")?;
let version = crate::utils::version::VERSION; let version = crate::utils::version::VERSION;
let text = format!( let text = format!(
"👋 Ciao {author_username}! Sono @{me_username}, il robot tuttofare della RYG!\n\n\ "👋 Ciao {author_username}! Sono @{me_username}, il robot tuttofare della RYG!\n\n\
Sto eseguendo la versione {version}.\n\n\ Sto eseguendo la versione {version}.\n\n\
Puoi vedere l'elenco delle mie funzionalità dal menu in basso.\n\n\ Puoi vedere l'elenco delle mie funzionalità dal menu in basso.\n\n\
Cosa posso fare per te oggi?", Cosa posso fare per te oggi?",
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -13,15 +13,15 @@ use super::CommandResult;
pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult { pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult {
let author = message.from.as_ref() let author = message.from.as_ref()
.context("Non è stato possibile determinare chi ha inviato questo comando.")?; .context("Non è stato possibile determinare chi ha inviato questo comando.")?;
let mut database = database.connect()?; let mut database = database.connect()?;
let royalnet_user: RoyalnetUser = { let royalnet_user: RoyalnetUser = {
use diesel::prelude::*; use diesel::prelude::*;
use diesel::{ExpressionMethods, QueryDsl}; use diesel::{ExpressionMethods, QueryDsl};
use crate::interfaces::database::schema::telegram::dsl::*; use crate::interfaces::database::schema::telegram::dsl::*;
use crate::interfaces::database::schema::users::dsl::*; use crate::interfaces::database::schema::users::dsl::*;
telegram telegram
.filter(telegram_id.eq::<i64>( .filter(telegram_id.eq::<i64>(
author.id.0.try_into() author.id.0.try_into()
@ -32,20 +32,20 @@ pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface)
.get_result(&mut database) .get_result(&mut database)
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")? .context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?
}; };
let username = &royalnet_user.username; let username = &royalnet_user.username;
let text = format!( let text = format!(
"👤 Nel database RYG, tu hai l'username <code>{}</code>.", "👤 Nel database RYG, tu hai l'username <code>{}</code>.",
username.escape_telegram_html(), username.escape_telegram_html(),
); );
let _reply = bot let _reply = bot
.send_message(message.chat.id, text) .send_message(message.chat.id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id)) .reply_parameters(ReplyParameters::new(message.id))
.await .await
.context("Non è stato possibile inviare la risposta.")?; .context("Non è stato possibile inviare la risposta.")?;
Ok(()) Ok(())
} }

View file

@ -12,7 +12,7 @@ impl DatabaseInterface {
pub fn new(database_url: String) -> Self { pub fn new(database_url: String) -> Self {
Self { database_url } Self { database_url }
} }
pub fn connect(&self) -> AnyResult<PgConnection> { pub fn connect(&self) -> AnyResult<PgConnection> {
crate::interfaces::database::connect(&self.database_url) crate::interfaces::database::connect(&self.database_url)
.context("Impossibile connettersi al database RYG.") .context("Impossibile connettersi al database RYG.")

View file

@ -10,13 +10,13 @@ use crate::services::telegram::keyboard_callbacks::KeyboardCallbackResult;
pub async fn handler(bot: &Bot, query: CallbackQuery, matchmaking_id: MatchmakingId, callback: MatchmakingTelegramKeyboardCallback, database: &DatabaseInterface) -> KeyboardCallbackResult { pub async fn handler(bot: &Bot, query: CallbackQuery, matchmaking_id: MatchmakingId, callback: MatchmakingTelegramKeyboardCallback, database: &DatabaseInterface) -> KeyboardCallbackResult {
use MatchmakingTelegramKeyboardCallback::*; use MatchmakingTelegramKeyboardCallback::*;
let mut database = database.connect() let mut database = database.connect()
.context("Non è stato possibile connettersi al database RYG.")?; .context("Non è stato possibile connettersi al database RYG.")?;
let royalnet_user = RoyalnetUser::from_telegram_userid(&mut database, query.from.id) let royalnet_user = RoyalnetUser::from_telegram_userid(&mut database, query.from.id)
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?; .context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?;
match callback { match callback {
Yes => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Yes)?, Yes => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Yes)?,
Plus5Min => MatchmakingReply::add_late_minutes(&mut database, matchmaking_id, royalnet_user.id, 5)?, Plus5Min => MatchmakingReply::add_late_minutes(&mut database, matchmaking_id, royalnet_user.id, 5)?,
@ -27,20 +27,20 @@ pub async fn handler(bot: &Bot, query: CallbackQuery, matchmaking_id: Matchmakin
Cant => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Cant)?, Cant => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Cant)?,
Wont => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Wont)?, Wont => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Wont)?,
}; };
let messages_telegram = MatchmakingMessageTelegram::get_all(&mut database, matchmaking_id) let messages_telegram = MatchmakingMessageTelegram::get_all(&mut database, matchmaking_id)
.context("Non è stato possibile recuperare i messaggi di matchmaking inviati su Telegram.")?; .context("Non è stato possibile recuperare i messaggi di matchmaking inviati su Telegram.")?;
for message_telegram in messages_telegram { for message_telegram in messages_telegram {
message_telegram.make_text_and_send_edit(&mut database, bot) message_telegram.make_text_and_send_edit(&mut database, bot)
.await .await
.context("Non è stato possibile aggiornare un messaggio di matchmaking su Telegram.")?; .context("Non è stato possibile aggiornare un messaggio di matchmaking su Telegram.")?;
} }
let _ = bot.answer_callback_query(query.id) let _ = bot.answer_callback_query(query.id)
.text("Ricevuto!") .text("Ricevuto!")
.await .await
.context("Non è stato possibile rispondere alla pressione del bottone su Telegram.")?; .context("Non è stato possibile rispondere alla pressione del bottone su Telegram.")?;
Ok(()) Ok(())
} }

View file

@ -19,24 +19,24 @@ pub enum KeyboardCallback {
impl FromStr for KeyboardCallback { impl FromStr for KeyboardCallback {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let (keyword, data) = s.split_once(":") let (keyword, data) = s.split_once(":")
.context("Impossibile dividere il payload in keyword e dati.")?; .context("Impossibile dividere il payload in keyword e dati.")?;
let (id, data) = data.split_once(":") let (id, data) = data.split_once(":")
.context("Impossibile dividere il payload in id e dati.")?; .context("Impossibile dividere il payload in id e dati.")?;
let id: MatchmakingId = id.parse() let id: MatchmakingId = id.parse()
.context("Impossibile convertire l'id a un numero.")?; .context("Impossibile convertire l'id a un numero.")?;
match keyword { match keyword {
"matchmaking" => { "matchmaking" => {
data data
.parse() .parse()
.map(|c| Self::Matchmaking(id, c)) .map(|c| Self::Matchmaking(id, c))
.context("Impossibile processare i dati.") .context("Impossibile processare i dati.")
}, }
x => { x => {
anyhow::bail!("Keyword sconosciuta: {x:?}") anyhow::bail!("Keyword sconosciuta: {x:?}")
} }
@ -47,33 +47,33 @@ impl FromStr for KeyboardCallback {
impl KeyboardCallback { impl KeyboardCallback {
pub async fn handle_self(self, bot: Bot, query: CallbackQuery, database: Arc<DatabaseInterface>) -> KeyboardCallbackResult { pub async fn handle_self(self, bot: Bot, query: CallbackQuery, database: Arc<DatabaseInterface>) -> KeyboardCallbackResult {
log::debug!("Handling keyboard callback..."); log::debug!("Handling keyboard callback...");
log::trace!( log::trace!(
"Handling {:?} in {:?} with {:?}...", "Handling {:?} in {:?} with {:?}...",
self, self,
&query.message.as_ref().map(|q| q.chat().id), &query.message.as_ref().map(|q| q.chat().id),
&query.id, &query.id,
); );
match self { match self {
Self::Matchmaking(matchmaking_id, callback) => { Self::Matchmaking(matchmaking_id, callback) => {
matchmaking::handler(&bot, query, matchmaking_id, callback, &database).await? matchmaking::handler(&bot, query, matchmaking_id, callback, &database).await?
} }
} }
log::trace!("Successfully handled keyboard callback!"); log::trace!("Successfully handled keyboard callback!");
Ok(()) Ok(())
} }
pub async fn handle_unknown(bot: Bot, query: CallbackQuery) -> KeyboardCallbackResult { pub async fn handle_unknown(bot: Bot, query: CallbackQuery) -> KeyboardCallbackResult {
log::warn!("Received an unknown keyboard callback: {:#?}", &query.data); log::warn!("Received an unknown keyboard callback: {:#?}", &query.data);
bot bot
.answer_callback_query(query.id) .answer_callback_query(query.id)
.show_alert(true) .show_alert(true)
.text("⚠️ Il tasto che hai premuto non è più valido.") .text("⚠️ Il tasto che hai premuto non è più valido.")
.await?; .await?;
log::trace!("Successfully handled unknown keyboard callback!"); log::trace!("Successfully handled unknown keyboard callback!");
Ok(()) Ok(())
} }

View file

@ -32,52 +32,52 @@ pub struct TelegramService {
impl TelegramService { impl TelegramService {
pub async fn new(database_url: String, token: String, notification_chat_id: Option<ChatId>) -> AnyResult<Self> { pub async fn new(database_url: String, token: String, notification_chat_id: Option<ChatId>) -> AnyResult<Self> {
log::info!("Initializing a new Telegram service..."); log::info!("Initializing a new Telegram service...");
let bot = Bot::new(token); let bot = Bot::new(token);
log::trace!("Using bot: {bot:#?}"); log::trace!("Using bot: {bot:#?}");
let me = Self::get_me(&bot) let me = Self::get_me(&bot)
.await?; .await?;
log::trace!("Using self details: {me:#?}"); log::trace!("Using self details: {me:#?}");
let service = Self { let service = Self {
database_url, database_url,
bot, bot,
me, me,
notification_chat_id notification_chat_id,
}; };
log::trace!("Created service: {service:#?}"); log::trace!("Created service: {service:#?}");
Ok(service) Ok(service)
} }
async fn get_me(bot: &Bot) -> AnyResult<Me> { async fn get_me(bot: &Bot) -> AnyResult<Me> {
log::debug!("Getting self details..."); log::debug!("Getting self details...");
bot.get_me().await bot.get_me().await
.context("Recupero dettagli sul bot non riuscito.") .context("Recupero dettagli sul bot non riuscito.")
} }
async fn send_start_notification(&self) -> AnyResult<Message> { async fn send_start_notification(&self) -> AnyResult<Message> {
log::debug!("Sending start notification..."); log::debug!("Sending start notification...");
let notification_chat_id = self.notification_chat_id let notification_chat_id = self.notification_chat_id
.context("La chat di notifica non è abilitata.")?; .context("La chat di notifica non è abilitata.")?;
let version = crate::utils::version::VERSION let version = crate::utils::version::VERSION
.escape_telegram_html(); .escape_telegram_html();
let username = self.me.username let username = self.me.username
.as_ref() .as_ref()
.unwrap() .unwrap()
.escape_telegram_html(); .escape_telegram_html();
let id = self.me.user.id let id = self.me.user.id
.to_string() .to_string()
.escape_telegram_html(); .escape_telegram_html();
let text = format!( let text = format!(
"💠 <b>Servizio Telegram avviato</b>\n\ "💠 <b>Servizio Telegram avviato</b>\n\
\n\ \n\
@ -85,35 +85,35 @@ impl TelegramService {
\n\ \n\
@{username} [<code>{id}</code>]" @{username} [<code>{id}</code>]"
); );
log::trace!("Sending start notification message..."); log::trace!("Sending start notification message...");
let msg = self.bot.send_message(notification_chat_id, text) let msg = self.bot.send_message(notification_chat_id, text)
.parse_mode(ParseMode::Html) .parse_mode(ParseMode::Html)
.await .await
.context("Invio della notifica di avvio non riuscito.")?; .context("Invio della notifica di avvio non riuscito.")?;
log::trace!("Successfully sent start notification message!"); log::trace!("Successfully sent start notification message!");
Ok(msg) Ok(msg)
} }
async fn set_commands(&mut self) -> AnyResult<()> { async fn set_commands(&mut self) -> AnyResult<()> {
log::debug!("Setting self commands..."); log::debug!("Setting self commands...");
Command::set_commands(&mut self.bot).await Command::set_commands(&mut self.bot).await
.context("Aggiornamento dei comandi del bot non riuscito.") .context("Aggiornamento dei comandi del bot non riuscito.")
} }
fn dispatcher(&mut self) -> Dispatcher<Bot, AnyError, DefaultKey> { fn dispatcher(&mut self) -> Dispatcher<Bot, AnyError, DefaultKey> {
log::debug!("Building dispatcher..."); log::debug!("Building dispatcher...");
let bot_name = self.me.user.username.as_ref().unwrap(); let bot_name = self.me.user.username.as_ref().unwrap();
log::trace!("Bot username is: @{bot_name:?}"); log::trace!("Bot username is: @{bot_name:?}");
log::trace!("Determining pseudo-command regex..."); log::trace!("Determining pseudo-command regex...");
let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap(); let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap();
log::trace!("Pseudo-command regex is: {regex:?}"); log::trace!("Pseudo-command regex is: {regex:?}");
let database = Arc::new(DatabaseInterface::new(self.database_url.clone())); let database = Arc::new(DatabaseInterface::new(self.database_url.clone()));
log::trace!("Building dispatcher..."); log::trace!("Building dispatcher...");
Dispatcher::builder( Dispatcher::builder(
self.bot.clone(), self.bot.clone(),
@ -152,8 +152,8 @@ impl TelegramService {
.endpoint(KeyboardCallback::handle_self) .endpoint(KeyboardCallback::handle_self)
) )
.endpoint(KeyboardCallback::handle_unknown) .endpoint(KeyboardCallback::handle_unknown)
) ),
) )
.dependencies( .dependencies(
dptree::deps![ dptree::deps![
database database
@ -164,11 +164,11 @@ impl TelegramService {
}) })
.build() .build()
} }
async fn dispatch(&mut self) -> AnyResult<()> { async fn dispatch(&mut self) -> AnyResult<()> {
log::debug!("Starting Telegram dispatcher..."); log::debug!("Starting Telegram dispatcher...");
self.dispatcher().dispatch().await; self.dispatcher().dispatch().await;
anyhow::bail!("Telegram dispatcher has exited unexpectedly.") anyhow::bail!("Telegram dispatcher has exited unexpectedly.")
} }
} }
@ -176,13 +176,13 @@ impl TelegramService {
impl RoyalnetService for TelegramService { impl RoyalnetService for TelegramService {
async fn run(&mut self) -> AnyResult<()> { async fn run(&mut self) -> AnyResult<()> {
log::info!("Starting Telegram service..."); log::info!("Starting Telegram service...");
let _ = self.set_commands() let _ = self.set_commands()
.await; .await;
let _ = self.send_start_notification() let _ = self.send_start_notification()
.await; .await;
self.dispatch() self.dispatch()
.await .await
} }

View file

@ -9,9 +9,9 @@ impl RoyalnetUser {
pub fn from_telegram_userid(database: &mut PgConnection, user_id: UserId) -> AnyResult<Self> { pub fn from_telegram_userid(database: &mut PgConnection, user_id: UserId) -> AnyResult<Self> {
use crate::interfaces::database::query_prelude::*; use crate::interfaces::database::query_prelude::*;
use schema::{telegram, users}; use schema::{telegram, users};
log::trace!("Retrieving RoyalnetUser with {user_id:?}"); log::trace!("Retrieving RoyalnetUser with {user_id:?}");
telegram::table telegram::table
.filter(telegram::telegram_id.eq::<i64>( .filter(telegram::telegram_id.eq::<i64>(
user_id.0.try_into() user_id.0.try_into()

View file

@ -2,8 +2,9 @@ use std::fmt::{Error, Write};
pub trait TelegramWrite { pub trait TelegramWrite {
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error> fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
where T: Write; where
T: Write;
fn to_string_telegram(&self) -> String { fn to_string_telegram(&self) -> String {
let mut result = String::new(); let mut result = String::new();
self.write_telegram(&mut result).unwrap(); self.write_telegram(&mut result).unwrap();
@ -16,7 +17,8 @@ pub trait TelegramEscape {
} }
impl<T> TelegramEscape for T impl<T> TelegramEscape for T
where String: From<T> where
String: From<T>,
{ {
fn escape_telegram_html(self) -> String { fn escape_telegram_html(self) -> String {
String::from(self) String::from(self)

View file

@ -1,6 +1,6 @@
pub fn chrono_to_tokio_duration(duration: chrono::TimeDelta) -> Option<tokio::time::Duration> { pub fn chrono_to_tokio_duration(duration: chrono::TimeDelta) -> Option<tokio::time::Duration> {
let nanos = duration.num_nanoseconds()?; let nanos = duration.num_nanoseconds()?;
Some( Some(
tokio::time::Duration::from_nanos(nanos as u64) tokio::time::Duration::from_nanos(nanos as u64)
) )
@ -8,11 +8,11 @@ pub fn chrono_to_tokio_duration(duration: chrono::TimeDelta) -> Option<tokio::ti
pub async fn sleep_chrono(until: &chrono::DateTime<chrono::Local>) { pub async fn sleep_chrono(until: &chrono::DateTime<chrono::Local>) {
let now = chrono::Local::now(); let now = chrono::Local::now();
let duration = until.signed_duration_since(now); let duration = until.signed_duration_since(now);
let duration = chrono_to_tokio_duration(duration) let duration = chrono_to_tokio_duration(duration)
.expect("Nanoseconds to not overflow u64"); .expect("Nanoseconds to not overflow u64");
tokio::time::sleep(duration).await; tokio::time::sleep(duration).await;
} }