mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-21 18:44:19 +00:00
Run "Reformat code..." IDE feature
This commit is contained in:
parent
b84b70080f
commit
ec3f5daff7
34 changed files with 67626 additions and 67621 deletions
|
@ -4,7 +4,7 @@
|
|||
#[cfg(feature = "interface_database")]
|
||||
pub mod interface_database {
|
||||
use micronfig::config;
|
||||
|
||||
|
||||
config! {
|
||||
DATABASE_AUTOMIGRATE: String > bool,
|
||||
DATABASE_URL: String,
|
||||
|
@ -14,7 +14,7 @@ pub mod interface_database {
|
|||
#[cfg(feature = "service_telegram")]
|
||||
pub mod service_telegram {
|
||||
use micronfig::config;
|
||||
|
||||
|
||||
config! {
|
||||
TELEGRAM_DATABASE_URL: String,
|
||||
TELEGRAM_BOT_TOKEN: String,
|
||||
|
@ -25,7 +25,7 @@ pub mod service_telegram {
|
|||
#[cfg(feature = "service_brooch")]
|
||||
pub mod brooch {
|
||||
use micronfig::config;
|
||||
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
config! {
|
||||
BROOCH_DATABASE_URL: String,
|
||||
|
@ -66,7 +66,7 @@ impl From<i64> for TimeDeltaConversionHack {
|
|||
|
||||
impl TryFrom<TimeDeltaConversionHack> for chrono::TimeDelta {
|
||||
type Error = ();
|
||||
|
||||
|
||||
fn try_from(value: TimeDeltaConversionHack) -> Result<Self, Self::Error> {
|
||||
Self::new(value.0, 0).ok_or(())
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ pub(self) mod config;
|
|||
pub struct RoyalnetInstance {
|
||||
#[cfg(feature = "service_telegram")]
|
||||
service_telegram: crate::services::telegram::TelegramService,
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_telegram"))]
|
||||
service_telegram: (),
|
||||
|
||||
|
||||
#[cfg(feature = "service_brooch")]
|
||||
service_brooch: crate::services::brooch::BroochService,
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_brooch"))]
|
||||
service_brooch: (),
|
||||
}
|
||||
|
@ -27,83 +27,83 @@ impl RoyalnetInstance {
|
|||
service_brooch: Self::setup_brooch_service().await,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn run(mut self) {
|
||||
Self::run_pending_migrations();
|
||||
|
||||
|
||||
let future_telegram = async move {
|
||||
Self::get_telegram_future(&mut self.service_telegram).await;
|
||||
};
|
||||
let future_brooch = async move {
|
||||
Self::get_brooch_future(&mut self.service_brooch).await;
|
||||
};
|
||||
|
||||
|
||||
let task_telegram = tokio::spawn(future_telegram);
|
||||
let task_brooch = tokio::spawn(future_brooch);
|
||||
|
||||
|
||||
let _ = tokio::join!(
|
||||
task_telegram,
|
||||
task_brooch,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "interface_database")]
|
||||
fn run_pending_migrations() {
|
||||
if !config::interface_database::DATABASE_AUTOMIGRATE() {
|
||||
log::warn!("Database automigration is disabled.");
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
log::debug!("Automatically applying database migrations...");
|
||||
|
||||
|
||||
log::trace!("Connecting to the database...");
|
||||
let mut db = crate::interfaces::database::connect(
|
||||
config::interface_database::DATABASE_URL()
|
||||
).expect("Unable to connect to the database to apply migrations.");
|
||||
|
||||
|
||||
log::trace!("Applying migrations...");
|
||||
crate::interfaces::database::migrate(&mut db)
|
||||
.expect("Failed to automatically apply migrations to the database.");
|
||||
|
||||
|
||||
log::trace!("Migration successful!");
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "interface_database"))]
|
||||
fn run_pending_migrations() {
|
||||
log::warn!("Database automigration is not compiled in.");
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
async fn setup_telegram_service() -> crate::services::telegram::TelegramService {
|
||||
log::debug!("Setting up Telegram service...");
|
||||
|
||||
|
||||
crate::services::telegram::TelegramService::new(
|
||||
config::service_telegram::TELEGRAM_DATABASE_URL().clone(),
|
||||
config::service_telegram::TELEGRAM_BOT_TOKEN().clone(),
|
||||
*config::service_telegram::TELEGRAM_NOTIFICATION_CHATID(),
|
||||
).await.expect("Unable to setup Telegram service.")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_telegram"))]
|
||||
async fn setup_telegram_service() {
|
||||
log::warn!("Telegram service is not compiled in.");
|
||||
}
|
||||
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_telegram"))]
|
||||
#[allow(clippy::manual_async_fn)]
|
||||
fn get_telegram_future(_service: &mut ()) -> impl Future<Output = ()> + '_ {
|
||||
fn get_telegram_future(_service: &mut ()) -> impl Future<Output=()> + '_ {
|
||||
async {}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "service_brooch")]
|
||||
async fn setup_brooch_service() -> crate::services::brooch::BroochService {
|
||||
log::debug!("Setting up Brooch service...");
|
||||
|
||||
|
||||
crate::services::brooch::BroochService::new(
|
||||
config::brooch::BROOCH_DATABASE_URL().clone(),
|
||||
config::brooch::BROOCH_GRAPHQL_URL(),
|
||||
|
@ -115,20 +115,20 @@ impl RoyalnetInstance {
|
|||
*config::brooch::BROOCH_MAX_IMP_WAIT_SECS(),
|
||||
).expect("Unable to setup Brooch service.")
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_brooch"))]
|
||||
async fn setup_brooch_service() {
|
||||
log::warn!("Brooch service is not compiled in.");
|
||||
}
|
||||
|
||||
|
||||
#[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()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(feature = "service_brooch"))]
|
||||
#[allow(clippy::manual_async_fn)]
|
||||
fn get_brooch_future(_service: &mut ()) -> impl Future<Output = ()> + '_ {
|
||||
fn get_brooch_future(_service: &mut ()) -> impl Future<Output=()> + '_ {
|
||||
async {}
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ impl BroochMatch {
|
|||
pub fn is_flagged(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<bool> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use schema::brooch_match;
|
||||
|
||||
|
||||
log::trace!("Checking if {match_id:?} is flagged...");
|
||||
|
||||
|
||||
Ok(
|
||||
brooch_match::table
|
||||
.find(match_id)
|
||||
|
@ -32,13 +32,13 @@ impl BroochMatch {
|
|||
.gt(&0usize)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
pub fn flag(database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Self> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use schema::brooch_match;
|
||||
|
||||
|
||||
log::debug!("Flagging {match_id:?} as parsed...");
|
||||
|
||||
|
||||
diesel::insert_into(brooch_match::table)
|
||||
.values(brooch_match::id.eq(match_id))
|
||||
.on_conflict_do_nothing()
|
||||
|
|
|
@ -29,7 +29,7 @@ impl MatchmakingEvent {
|
|||
.get_result::<Self>(database)
|
||||
.context("Non è stato possibile aggiungere il matchmaking al database RYG.")
|
||||
}
|
||||
|
||||
|
||||
/// Retrieve a [MatchmakingEvent] from the database, given its [MatchmakingId].
|
||||
pub fn get(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Self> {
|
||||
matchmaking_events::table
|
||||
|
@ -37,7 +37,7 @@ impl MatchmakingEvent {
|
|||
.get_result::<Self>(database)
|
||||
.context("Non è stato possibile recuperare il matchmaking dal database RYG.")
|
||||
}
|
||||
|
||||
|
||||
pub fn has_started(&self) -> bool {
|
||||
self.starts_at.lt(&chrono::Local::now().naive_utc())
|
||||
}
|
||||
|
|
|
@ -51,26 +51,26 @@ pub(crate) mod telegram_ext {
|
|||
Cant,
|
||||
Wont,
|
||||
}
|
||||
|
||||
|
||||
impl MatchmakingTelegramKeyboardCallback {
|
||||
/// Create callback data representing the [MatchmakingTelegramKeyboardCallback] in the given [MatchmakingId].
|
||||
pub fn callback_data(self, matchmaking_id: MatchmakingId) -> String {
|
||||
matchmaking_id.callback_data(self.into())
|
||||
}
|
||||
|
||||
|
||||
pub fn inline_button(self, matchmaking_id: MatchmakingId, text: &str) -> teloxide::types::InlineKeyboardButton {
|
||||
teloxide::types::InlineKeyboardButton::new(
|
||||
text,
|
||||
teloxide::types::InlineKeyboardButtonKind::CallbackData(
|
||||
self.callback_data(matchmaking_id)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl FromStr for MatchmakingTelegramKeyboardCallback {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(
|
||||
match s {
|
||||
|
@ -87,7 +87,7 @@ pub(crate) mod telegram_ext {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<MatchmakingTelegramKeyboardCallback> for &'static str {
|
||||
fn from(value: MatchmakingTelegramKeyboardCallback) -> Self {
|
||||
match value {
|
||||
|
@ -102,22 +102,22 @@ pub(crate) mod telegram_ext {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl MatchmakingMessageTelegram {
|
||||
/// Get all the [MatchmakingMessageTelegram] for a specific [MatchmakingId].
|
||||
pub fn get_all(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<Self>> {
|
||||
use diesel::prelude::*;
|
||||
use crate::interfaces::database::schema::matchmaking_messages_telegram;
|
||||
|
||||
|
||||
matchmaking_messages_telegram::table
|
||||
.filter(matchmaking_messages_telegram::matchmaking_id.eq(matchmaking_id.0))
|
||||
.get_results::<MatchmakingMessageTelegram>(database)
|
||||
.context("La query al database RYG è fallita.")
|
||||
}
|
||||
|
||||
|
||||
fn reply_markup(matchmaking_id: MatchmakingId) -> teloxide::types::InlineKeyboardMarkup {
|
||||
use MatchmakingTelegramKeyboardCallback::*;
|
||||
|
||||
|
||||
let button_yes = Yes.inline_button(matchmaking_id, "🔵 Ci sarò!");
|
||||
let button_5min = Plus5Min.inline_button(matchmaking_id, "🕐 +5 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_cant = Cant.inline_button(matchmaking_id, "🔺 Non posso...");
|
||||
let button_wont = Wont.inline_button(matchmaking_id, "🔻 Non mi interessa.");
|
||||
|
||||
|
||||
teloxide::types::InlineKeyboardMarkup::new(vec![
|
||||
vec![button_yes],
|
||||
vec![button_5min, button_15min, button_60min],
|
||||
|
@ -134,20 +134,20 @@ pub(crate) mod telegram_ext {
|
|||
vec![button_cant, button_wont],
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
fn text(event: &MatchmakingEvent, replies: &Vec<(MatchmakingReply, RoyalnetUser, TelegramUser)>) -> String {
|
||||
use std::fmt::Write;
|
||||
|
||||
|
||||
let mut result = String::new();
|
||||
|
||||
|
||||
let emoji = match event.has_started() {
|
||||
false => "🚩",
|
||||
true => "🔔",
|
||||
};
|
||||
|
||||
|
||||
let text = event.text.as_str().escape_telegram_html();
|
||||
writeln!(result, "{emoji} <b>{text}</b>").unwrap();
|
||||
|
||||
|
||||
let start = event.starts_at
|
||||
.and_utc()
|
||||
.with_timezone(&Local)
|
||||
|
@ -155,12 +155,12 @@ pub(crate) mod telegram_ext {
|
|||
.to_string()
|
||||
.escape_telegram_html();
|
||||
writeln!(result, "<i>{start}</i>").unwrap();
|
||||
|
||||
|
||||
writeln!(result).unwrap();
|
||||
|
||||
|
||||
for (reply, royalnet, telegram) in replies {
|
||||
use MatchmakingChoice::*;
|
||||
|
||||
|
||||
let emoji = match reply.choice {
|
||||
Yes => "🔵",
|
||||
Late => match reply.late_mins {
|
||||
|
@ -182,24 +182,24 @@ pub(crate) mod telegram_ext {
|
|||
Cant => "🔺",
|
||||
Wont => "🔻",
|
||||
};
|
||||
|
||||
|
||||
let telegram_id = telegram.telegram_id.0;
|
||||
let username = &royalnet.username;
|
||||
|
||||
|
||||
write!(result, "{emoji} <a href=\"tg://user?id={telegram_id}\">{username}</a>").unwrap();
|
||||
|
||||
|
||||
if reply.choice == Late {
|
||||
let late_mins = reply.late_mins;
|
||||
|
||||
|
||||
write!(result, " (+{late_mins} mins)").unwrap();
|
||||
}
|
||||
|
||||
|
||||
writeln!(result).unwrap();
|
||||
}
|
||||
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
async fn send_new(
|
||||
database: &mut PgConnection,
|
||||
matchmaking_id: MatchmakingId,
|
||||
|
@ -209,32 +209,32 @@ pub(crate) mod telegram_ext {
|
|||
) -> AnyResult<teloxide::types::Message> {
|
||||
let event = MatchmakingEvent::get(database, matchmaking_id)
|
||||
.context("Non è stato possibile recuperare il matchmaking dal database RYG.")?;
|
||||
|
||||
|
||||
let replies = MatchmakingReply::get_all_telegram(database, matchmaking_id)
|
||||
.context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?;
|
||||
|
||||
|
||||
let text = Self::text(&event, &replies);
|
||||
|
||||
|
||||
let mut request = bot.send_message(chat_id, text)
|
||||
.parse_mode(ParseMode::Html);
|
||||
|
||||
|
||||
if !event.has_started() {
|
||||
request = request.reply_markup(
|
||||
Self::reply_markup(matchmaking_id)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if let Some(reply_to) = reply_to {
|
||||
request = request.reply_parameters(
|
||||
teloxide::types::ReplyParameters::new(reply_to)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
request
|
||||
.await
|
||||
.context("La richiesta di invio messaggio alla Bot API di Telegram è fallita.")
|
||||
}
|
||||
|
||||
|
||||
fn create(
|
||||
database: &mut PgConnection,
|
||||
matchmaking_id: MatchmakingId,
|
||||
|
@ -245,7 +245,7 @@ pub(crate) mod telegram_ext {
|
|||
use diesel::prelude::*;
|
||||
use diesel::dsl::*;
|
||||
use crate::interfaces::database::schema::matchmaking_messages_telegram;
|
||||
|
||||
|
||||
insert_into(matchmaking_messages_telegram::table)
|
||||
.values(&MatchmakingMessageTelegram {
|
||||
matchmaking_id,
|
||||
|
@ -256,7 +256,7 @@ pub(crate) mod telegram_ext {
|
|||
.get_result::<MatchmakingMessageTelegram>(database)
|
||||
.context("L'inserimento nel database RYG è fallito.")
|
||||
}
|
||||
|
||||
|
||||
pub async fn send_new_and_create(
|
||||
database: &mut PgConnection,
|
||||
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)
|
||||
.await
|
||||
.context("Non è stato possibile inviare il messaggio Telegram del matchmaking.")?;
|
||||
|
||||
|
||||
let this = Self::create(database, matchmaking_id, &reply)
|
||||
.context("Non è stato possibile aggiungere il messaggio Telegram al database RYG.")?;
|
||||
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
|
||||
async fn send_edit(
|
||||
&self,
|
||||
bot: &teloxide::Bot,
|
||||
|
@ -285,21 +285,21 @@ pub(crate) mod telegram_ext {
|
|||
-> AnyResult<teloxide::types::Message>
|
||||
{
|
||||
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)
|
||||
.parse_mode(ParseMode::Html);
|
||||
|
||||
|
||||
if with_keyboard {
|
||||
request = request.reply_markup(
|
||||
Self::reply_markup(self.matchmaking_id)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
request
|
||||
.await
|
||||
.context("La richiesta di modifica messaggio alla Bot API di Telegram è fallita.")
|
||||
}
|
||||
|
||||
|
||||
pub async fn make_text_and_send_edit(
|
||||
&self,
|
||||
database: &mut PgConnection,
|
||||
|
@ -309,19 +309,19 @@ pub(crate) mod telegram_ext {
|
|||
{
|
||||
let event = MatchmakingEvent::get(database, self.matchmaking_id)
|
||||
.context("Non è stato possibile recuperare il matchmaking dal database RYG.")?;
|
||||
|
||||
|
||||
let replies = MatchmakingReply::get_all_telegram(database, self.matchmaking_id)
|
||||
.context("Non è stato possibile recuperare le risposte al matchmaking dal database RYG.")?;
|
||||
|
||||
|
||||
let text = Self::text(&event, &replies);
|
||||
|
||||
|
||||
self.send_edit(bot, &text, !event.has_started())
|
||||
.await
|
||||
.context("Non è stato possibile modificare il messaggio Telegram del matchmaking.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn send_delete(
|
||||
&self,
|
||||
bot: &teloxide::Bot,
|
||||
|
@ -333,7 +333,7 @@ pub(crate) mod telegram_ext {
|
|||
.await
|
||||
.context("La richiesta di eliminazione messaggio alla Bot API di Telegram è fallita.")
|
||||
}
|
||||
|
||||
|
||||
fn destroy(
|
||||
&self,
|
||||
database: &mut PgConnection,
|
||||
|
@ -343,7 +343,7 @@ pub(crate) mod telegram_ext {
|
|||
use diesel::prelude::*;
|
||||
use diesel::dsl::*;
|
||||
use crate::interfaces::database::schema::matchmaking_messages_telegram;
|
||||
|
||||
|
||||
delete(matchmaking_messages_telegram::table)
|
||||
.filter(matchmaking_messages_telegram::matchmaking_id.eq(self.matchmaking_id))
|
||||
.filter(matchmaking_messages_telegram::telegram_chat_id.eq(self.telegram_chat_id))
|
||||
|
@ -351,21 +351,21 @@ pub(crate) mod telegram_ext {
|
|||
.execute(database)
|
||||
.context("La rimozione dal database RYG è fallita.")
|
||||
}
|
||||
|
||||
|
||||
pub async fn destroy_and_send_delete(
|
||||
self,
|
||||
database: &mut PgConnection,
|
||||
bot: &teloxide::Bot
|
||||
bot: &teloxide::Bot,
|
||||
)
|
||||
-> AnyResult<()>
|
||||
{
|
||||
self.destroy(database)
|
||||
.context("Non è stato possibile eliminare il messaggio Telegram dal database RYG.")?;
|
||||
|
||||
|
||||
self.send_delete(bot)
|
||||
.await
|
||||
.context("Non è stato possibile eliminare il messaggio Telegram del matchmaking.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ pub struct MatchmakingReply {
|
|||
impl MatchmakingReply {
|
||||
pub fn get_all_telegram(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<(Self, RoyalnetUser, TelegramUser)>> {
|
||||
use schema::{matchmaking_replies, telegram, users};
|
||||
|
||||
|
||||
matchmaking_replies::table
|
||||
.filter(matchmaking_replies::matchmaking_id.eq(matchmaking_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)
|
||||
.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> {
|
||||
use schema::matchmaking_replies;
|
||||
|
||||
|
||||
insert_into(matchmaking_replies::table)
|
||||
.values(&Self {
|
||||
matchmaking_id,
|
||||
|
@ -56,10 +56,10 @@ impl MatchmakingReply {
|
|||
.get_result::<Self>(database)
|
||||
.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> {
|
||||
use schema::matchmaking_replies;
|
||||
|
||||
|
||||
insert_into(matchmaking_replies::table)
|
||||
.values(&Self {
|
||||
matchmaking_id,
|
||||
|
|
|
@ -29,33 +29,33 @@ mod telegram_ext {
|
|||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<TelegramChatId> for teloxide::types::ChatId {
|
||||
fn from(value: TelegramChatId) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<teloxide::types::UserId> for TelegramUserId {
|
||||
fn from(value: teloxide::types::UserId) -> Self {
|
||||
// FIXME: this surely seems like a great idea
|
||||
Self(value.0 as i64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<TelegramUserId> for teloxide::types::UserId {
|
||||
fn from(value: TelegramUserId) -> Self {
|
||||
// FIXME: this surely seems like a great idea
|
||||
Self(value.0 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<teloxide::types::MessageId> for TelegramMessageId {
|
||||
fn from(value: teloxide::types::MessageId) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From<TelegramMessageId> for teloxide::types::MessageId {
|
||||
fn from(value: TelegramMessageId) -> Self {
|
||||
Self(value.0)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
pub mod sql_types {
|
||||
#[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "matchmaking_choice"))]
|
||||
pub struct MatchmakingChoice;
|
||||
#[derive(diesel::query_builder::QueryId, Clone, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "matchmaking_choice"))]
|
||||
pub struct MatchmakingChoice;
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
|
|
|
@ -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::trace!("Using client: {client:?}");
|
||||
log::trace!("Using API at: {url:?}");
|
||||
|
||||
|
||||
log::trace!("Configuring query variables...");
|
||||
let vars = query::Variables { guild_id };
|
||||
|
||||
|
||||
log::trace!("Building query...");
|
||||
let body = Query::build_query(vars);
|
||||
|
||||
|
||||
log::trace!("Making request...");
|
||||
let response = client.post(url)
|
||||
.json(&body)
|
||||
|
@ -50,6 +50,6 @@ pub async fn query(client: &reqwest::Client, url: Url, guild_id: i64) -> QueryRe
|
|||
.json::<QueryResponse>()
|
||||
.await
|
||||
.map_err(|_| Error::Parsing)?;
|
||||
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
@ -1,37 +1,37 @@
|
|||
query Query($guild_id: Int!) {
|
||||
guild(id: $guild_id) {
|
||||
id
|
||||
matches(take: 10) {
|
||||
id
|
||||
lobbyType
|
||||
gameMode
|
||||
durationSeconds
|
||||
endDateTime
|
||||
players(steamAccountId: null) {
|
||||
isRadiant
|
||||
isVictory
|
||||
imp
|
||||
kills
|
||||
deaths
|
||||
assists
|
||||
lane
|
||||
role
|
||||
hero {
|
||||
displayName
|
||||
}
|
||||
steamAccount {
|
||||
id
|
||||
name
|
||||
}
|
||||
stats {
|
||||
matchPlayerBuffEvent {
|
||||
time
|
||||
itemId
|
||||
abilityId
|
||||
stackCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
guild(id: $guild_id) {
|
||||
id
|
||||
matches(take: 10) {
|
||||
id
|
||||
lobbyType
|
||||
gameMode
|
||||
durationSeconds
|
||||
endDateTime
|
||||
players(steamAccountId: null) {
|
||||
isRadiant
|
||||
isVictory
|
||||
imp
|
||||
kills
|
||||
deaths
|
||||
assists
|
||||
lane
|
||||
role
|
||||
hero {
|
||||
displayName
|
||||
}
|
||||
steamAccount {
|
||||
id
|
||||
name
|
||||
}
|
||||
stats {
|
||||
matchPlayerBuffEvent {
|
||||
time
|
||||
itemId
|
||||
abilityId
|
||||
stackCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
22
src/main.rs
22
src/main.rs
|
@ -7,15 +7,15 @@ pub(crate) mod utils;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Logging setup
|
||||
pretty_env_logger::init();
|
||||
log::debug!("Logging initialized successfully!");
|
||||
|
||||
// Create instance
|
||||
let instance = RoyalnetInstance::new().await;
|
||||
|
||||
log::trace!("Starting {instance:?}!");
|
||||
instance.run().await;
|
||||
|
||||
log::error!("No services configured.");
|
||||
// Logging setup
|
||||
pretty_env_logger::init();
|
||||
log::debug!("Logging initialized successfully!");
|
||||
|
||||
// Create instance
|
||||
let instance = RoyalnetInstance::new().await;
|
||||
|
||||
log::trace!("Starting {instance:?}!");
|
||||
instance.run().await;
|
||||
|
||||
log::error!("No services configured.");
|
||||
}
|
||||
|
|
|
@ -40,33 +40,33 @@ impl BroochService {
|
|||
min_players_to_process: usize,
|
||||
telegram_bot_token: String,
|
||||
notification_chat_id: ChatId,
|
||||
max_imp_wait: TimeDelta
|
||||
max_imp_wait: TimeDelta,
|
||||
)
|
||||
-> AnyResult<Self>
|
||||
{
|
||||
log::info!("Initializing a new Brooch service...");
|
||||
|
||||
|
||||
let mut graphql_url = Url::parse(graphql_base_url)
|
||||
.context("URL GraphQL non valido.")?;
|
||||
{
|
||||
let mut graphql_url_params = graphql_url.query_pairs_mut();
|
||||
graphql_url_params.append_pair("jwt", stratz_token);
|
||||
}
|
||||
|
||||
|
||||
log::trace!("Using GraphQL API URL: {graphql_url:?}");
|
||||
|
||||
|
||||
if min_players_to_process == 0 {
|
||||
anyhow::bail!("min_players_to_progress devono essere almeno 1.");
|
||||
}
|
||||
|
||||
|
||||
log::trace!("Processing only matches with at least {min_players_to_process} players.");
|
||||
|
||||
|
||||
let telegram_bot = Bot::new(telegram_bot_token);
|
||||
|
||||
|
||||
log::trace!("Using bot: {telegram_bot:#?}");
|
||||
|
||||
|
||||
log::trace!("Max IMP wait is: {max_imp_wait:?}");
|
||||
|
||||
|
||||
Ok(
|
||||
BroochService {
|
||||
database_url,
|
||||
|
@ -79,143 +79,143 @@ impl BroochService {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn create_http_client(&self) -> AnyResult<reqwest::Client> {
|
||||
log::debug!("Creating HTTP client...");
|
||||
|
||||
|
||||
reqwest::Client::builder()
|
||||
.build()
|
||||
.context("Impossibile creare un client HTTP appropriato a fare richieste all'API.")
|
||||
}
|
||||
|
||||
|
||||
fn create_postgres_connection(&self) -> AnyResult<PgConnection> {
|
||||
log::debug!("Creating PostgreSQL connection...");
|
||||
|
||||
|
||||
database::connect(&self.database_url)
|
||||
.context("Non è stato possibile connettersi al database RYG.")
|
||||
}
|
||||
|
||||
|
||||
async fn query_guild_matches(&self, client: &reqwest::Client) -> AnyResult<guild_matches::QueryResponse> {
|
||||
log::debug!("Querying for guild matches...");
|
||||
|
||||
|
||||
guild_matches::query(client, self.graphql_url.clone(), self.watched_guild_id)
|
||||
.await
|
||||
.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> {
|
||||
log::debug!("Processing guild data...");
|
||||
|
||||
|
||||
let data = data.data
|
||||
.context("La richiesta è riuscita, ma la risposta ricevuta da STRATZ era vuota.")?;
|
||||
|
||||
|
||||
let guild = data.guild
|
||||
.context("La richiesta è riuscita, ma non sono state ricevute gilde da STRATZ.")?;
|
||||
|
||||
|
||||
let guild_id: i64 = guild.id
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto l'ID della gilda da STRATZ.")?;
|
||||
|
||||
|
||||
log::trace!("Guild id is: {guild_id}");
|
||||
|
||||
|
||||
if guild_id != self.watched_guild_id {
|
||||
anyhow::bail!("La richiesta è riuscita, ma STRATZ ha risposto con le informazioni della gilda sbagliata.");
|
||||
}
|
||||
|
||||
|
||||
log::trace!("Guild id matches watched guild.");
|
||||
|
||||
|
||||
Ok(guild)
|
||||
}
|
||||
|
||||
|
||||
fn process_matches_data(&self, guild: guild_matches::Guild) -> AnyResult<Vec<Match>> {
|
||||
log::debug!("Processing matches data...");
|
||||
|
||||
|
||||
let mut matches = guild.matches
|
||||
.context("La richiesta è riuscita, ma non sono state ricevute informazioni sulle partite della gilda da STRATZ.")?
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<Match>>();
|
||||
|
||||
|
||||
log::trace!("Received {} matches.", matches.len());
|
||||
|
||||
|
||||
log::trace!("Sorting matches by datetime...");
|
||||
|
||||
|
||||
// Sort matches chronologically
|
||||
matches.sort_unstable_by_key(|o| o
|
||||
.end_date_time
|
||||
.unwrap_or(0)
|
||||
);
|
||||
|
||||
|
||||
log::trace!("Sorted matches by datetime!");
|
||||
|
||||
|
||||
Ok(matches)
|
||||
}
|
||||
|
||||
|
||||
fn get_match_id(&self, r#match: &Match) -> AnyResult<DotaMatchId> {
|
||||
log::trace!("Getting match id...");
|
||||
|
||||
|
||||
Ok(
|
||||
r#match.id
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")?
|
||||
.into()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn get_database_match(&self, database: &mut PgConnection, match_id: DotaMatchId) -> AnyResult<Option<BroochMatch>> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use crate::interfaces::database::schema::brooch_match;
|
||||
|
||||
|
||||
log::trace!("Getting {match_id:?} from the database...");
|
||||
|
||||
|
||||
brooch_match::table
|
||||
.filter(brooch_match::id.eq(match_id))
|
||||
.get_result::<BroochMatch>(database)
|
||||
.optional()
|
||||
.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> {
|
||||
log::trace!("Determining whether {match_id:?} should be processed...");
|
||||
|
||||
|
||||
self.get_database_match(database, match_id)
|
||||
.map(|m| m.is_none())
|
||||
.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>> {
|
||||
log::trace!("Getting match datetime...");
|
||||
|
||||
|
||||
let match_date = r#match.end_date_time
|
||||
.context("Non è stato ricevuto da STRATZ il momento di termine della partita.")?;
|
||||
|
||||
|
||||
log::trace!("Converting match datetime to local datetime...");
|
||||
|
||||
|
||||
Local.timestamp_opt(match_date, 0)
|
||||
.earliest()
|
||||
.context("È stato ricevuto da STRATZ un momento di termine della partita non valido.")
|
||||
}
|
||||
|
||||
|
||||
fn get_match_timedelta(&self, datetime: &DateTime<Local>) -> TimeDelta {
|
||||
log::trace!("Getting current time...");
|
||||
|
||||
|
||||
let now = Local::now();
|
||||
|
||||
|
||||
log::trace!("Getting match timedelta...");
|
||||
|
||||
|
||||
now - *datetime
|
||||
}
|
||||
|
||||
|
||||
fn get_match_type(&self, r#match: &Match) -> AnyResult<LobbyType> {
|
||||
log::trace!("Getting match type...");
|
||||
|
||||
|
||||
r#match.lobby_type.clone()
|
||||
.context("Non è stato ricevuta da STRATZ il tipo della partita.")
|
||||
}
|
||||
|
||||
|
||||
fn stringify_type(&self, r#type: LobbyType) -> String {
|
||||
use LobbyType::*;
|
||||
|
||||
|
||||
log::trace!("Stringifying match type: {:?}", r#type);
|
||||
|
||||
|
||||
match r#type {
|
||||
UNRANKED => String::from("Normale"),
|
||||
PRACTICE => String::from("Torneo"),
|
||||
|
@ -232,19 +232,19 @@ impl BroochService {
|
|||
Other(t) => t.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_match_mode(&self, r#match: &Match) -> AnyResult<GameMode> {
|
||||
log::trace!("Getting match mode...");
|
||||
|
||||
|
||||
r#match.game_mode.clone()
|
||||
.context("Non è stata ricevuta da STRATZ la modalità della partita.")
|
||||
}
|
||||
|
||||
|
||||
fn stringify_mode(&self, mode: GameMode) -> String {
|
||||
use GameMode::*;
|
||||
|
||||
|
||||
log::trace!("Stringifying match mode: {:?}", mode);
|
||||
|
||||
|
||||
match mode {
|
||||
NONE => String::from("Sandbox"),
|
||||
ALL_PICK => String::from("All Pick"),
|
||||
|
@ -275,185 +275,185 @@ impl BroochService {
|
|||
Other(t) => t.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn stringify_duration(&self, duration: TimeDelta) -> String {
|
||||
let minutes = duration.num_minutes();
|
||||
let seconds = duration.num_seconds() % 60;
|
||||
|
||||
|
||||
format!("{minutes:02}:{seconds:02}")
|
||||
}
|
||||
|
||||
|
||||
fn get_match_duration(&self, r#match: &Match) -> AnyResult<TimeDelta> {
|
||||
log::trace!("Getting match duration...");
|
||||
|
||||
|
||||
let secs = r#match.duration_seconds
|
||||
.context("Non è stata ricevuta da STRATZ la durata della partita.")?;
|
||||
|
||||
|
||||
log::trace!("Getting match duration timedelta...");
|
||||
|
||||
|
||||
let delta = TimeDelta::new(secs, 0)
|
||||
.context("Non è stato possibile rappresentare la durata della partita ricevuta da STRATZ.")?;
|
||||
|
||||
|
||||
Ok(delta)
|
||||
}
|
||||
|
||||
|
||||
fn get_match_players(&self, r#match: Match) -> AnyResult<Vec<Player>> {
|
||||
log::debug!("Getting match players...");
|
||||
|
||||
|
||||
let mut players: Vec<Player> = r#match.players
|
||||
.context("Non è stato ricevuto da STRATZ l'elenco dei giocatori della partita.")?
|
||||
.iter()
|
||||
.filter_map(|o| o.to_owned())
|
||||
.collect();
|
||||
|
||||
|
||||
log::trace!("Sorting match players...");
|
||||
|
||||
|
||||
players.sort_unstable_by_key(|o| match o.is_radiant.unwrap() {
|
||||
true => 1,
|
||||
false => 2,
|
||||
});
|
||||
|
||||
|
||||
log::trace!("Sorted match players!");
|
||||
|
||||
|
||||
Ok(players)
|
||||
}
|
||||
|
||||
|
||||
fn should_process_match_players(&self, players: &[Player]) -> bool {
|
||||
let players_len = players.len();
|
||||
|
||||
|
||||
log::trace!("Determining whether {players_len} are enough for the match to be processed...");
|
||||
|
||||
|
||||
players_len >= self.min_players_to_process
|
||||
}
|
||||
|
||||
|
||||
fn should_process_match_imp(&self, players: &[Player], timedelta: &TimeDelta) -> bool {
|
||||
log::trace!("Determining whether IMP is available for all players...");
|
||||
|
||||
|
||||
let imp_available_for_everyone = players.iter()
|
||||
.map(|o| o.imp)
|
||||
.map(|o| o.is_some())
|
||||
.all(|o| o);
|
||||
|
||||
|
||||
log::trace!("Determining whether enough time has passed for IMP to be ignored...");
|
||||
|
||||
|
||||
let imp_waited_too_long = *timedelta > self.max_imp_wait;
|
||||
|
||||
|
||||
imp_available_for_everyone || imp_waited_too_long
|
||||
}
|
||||
|
||||
|
||||
fn get_match_side(&self, players: &[Player]) -> AnyResult<MatchSide> {
|
||||
use MatchSide::*;
|
||||
|
||||
|
||||
log::debug!("Getting match side...");
|
||||
|
||||
|
||||
let mut side = None;
|
||||
|
||||
|
||||
for player in players.iter() {
|
||||
side = match (side, player.is_radiant) {
|
||||
(_, None) => {
|
||||
anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.")
|
||||
},
|
||||
}
|
||||
(None, Some(true)) => {
|
||||
Some(Radiant)
|
||||
},
|
||||
}
|
||||
(None, Some(false)) => {
|
||||
Some(Dire)
|
||||
},
|
||||
}
|
||||
(Some(Radiant), Some(true)) |
|
||||
(Some(Dire), Some(false)) => {
|
||||
side
|
||||
},
|
||||
}
|
||||
(Some(Radiant), Some(false)) |
|
||||
(Some(Dire), Some(true)) => {
|
||||
Some(Both)
|
||||
},
|
||||
}
|
||||
(Some(Both), _) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let side = side.unwrap();
|
||||
|
||||
|
||||
log::trace!("Match side is: {side:?}");
|
||||
|
||||
|
||||
Ok(side)
|
||||
}
|
||||
|
||||
fn get_match_outcome(&self, players: &[Player]) -> AnyResult<MatchOutcome> {
|
||||
use MatchOutcome::*;
|
||||
|
||||
|
||||
log::debug!("Getting match outcome...");
|
||||
|
||||
|
||||
let mut outcome = None;
|
||||
|
||||
|
||||
for player in players.iter() {
|
||||
outcome = match (outcome, player.is_victory) {
|
||||
(_, None) => {
|
||||
anyhow::bail!("Non è stata ricevuta da STRATZ la squadra di almeno uno dei giocatori.")
|
||||
},
|
||||
}
|
||||
(None, Some(true)) => {
|
||||
Some(Victory)
|
||||
},
|
||||
}
|
||||
(None, Some(false)) => {
|
||||
Some(Defeat)
|
||||
},
|
||||
}
|
||||
(Some(Victory), Some(true)) |
|
||||
(Some(Defeat), Some(false)) => {
|
||||
outcome
|
||||
},
|
||||
}
|
||||
(Some(Victory), Some(false)) |
|
||||
(Some(Defeat), Some(true)) => {
|
||||
Some(Clash)
|
||||
},
|
||||
}
|
||||
(Some(Clash), _) => {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let outcome = outcome.unwrap();
|
||||
|
||||
|
||||
log::trace!("Match outcome is: {outcome:?}");
|
||||
|
||||
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
|
||||
fn get_player_steam(&self, player: &Player) -> AnyResult<Steam> {
|
||||
log::trace!("Getting player's Steam account...");
|
||||
|
||||
|
||||
player.steam_account.clone()
|
||||
.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> {
|
||||
log::trace!("Getting player's Steam name...");
|
||||
|
||||
|
||||
steam.name.clone()
|
||||
.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>> {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::interfaces::database::schema::{steam, telegram, users};
|
||||
use crate::interfaces::database::models::TelegramUser;
|
||||
|
||||
|
||||
log::trace!("Getting player's Steam name...");
|
||||
|
||||
|
||||
let player_steam_id = player_steam.id
|
||||
.context("Non è stato ricevuto da STRATZ lo SteamID di almeno uno dei giocatori della partita.")?;
|
||||
|
||||
|
||||
log::trace!("Computing the two possible SteamIDs...");
|
||||
|
||||
|
||||
let player_steam_id_y0 = 0x_0110_0001_0000_0000 + player_steam_id;
|
||||
|
||||
|
||||
log::trace!("SteamID Y0 is: {player_steam_id_y0}");
|
||||
|
||||
|
||||
let player_steam_id_y1 = 0x_0110_0001_0000_0001 + player_steam_id;
|
||||
|
||||
|
||||
log::trace!("SteamID Y1 is: {player_steam_id_y1}");
|
||||
|
||||
|
||||
Ok(
|
||||
steam::table
|
||||
.inner_join(
|
||||
|
@ -479,24 +479,24 @@ impl BroochService {
|
|||
.map(|t| t.telegram_id)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn get_player_hero_name(&self, player: &Player) -> AnyResult<String> {
|
||||
log::trace!("Getting player's hero name...");
|
||||
|
||||
|
||||
player.hero.clone()
|
||||
.context("Non è stato ricevuto da STRATZ l'eroe giocato da almeno uno dei giocatori della partita.")?
|
||||
.display_name
|
||||
.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> {
|
||||
use MatchOutcome::*;
|
||||
|
||||
|
||||
log::trace!("Getting player's match outcome...");
|
||||
|
||||
|
||||
let is_victory = &player.is_victory
|
||||
.context("Non è stato ricevuto da STRATZ il risultato della partita per almeno uno dei giocatori.")?;
|
||||
|
||||
|
||||
Ok(
|
||||
match is_victory {
|
||||
true => Victory,
|
||||
|
@ -504,68 +504,68 @@ impl BroochService {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn emojify_outcome(&self, outcome: MatchOutcome) -> &'static str {
|
||||
use MatchOutcome::*;
|
||||
|
||||
|
||||
log::trace!("Emojifying match outcome...");
|
||||
|
||||
|
||||
match outcome {
|
||||
Victory => "🟩",
|
||||
Defeat => "🔴",
|
||||
Clash => "💛",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn stringify_outcome(&self, outcome: MatchOutcome) -> &'static str {
|
||||
use MatchOutcome::*;
|
||||
|
||||
|
||||
log::trace!("Stringifying match outcome...");
|
||||
|
||||
|
||||
match outcome {
|
||||
Victory => "Vittoria!",
|
||||
Defeat => "Sconfitta...",
|
||||
Clash => "Derby",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn numberify_role_lane(role: &Option<Role>, lane: &Option<Lane>) -> u8 {
|
||||
use Role::*;
|
||||
use Lane::*;
|
||||
|
||||
|
||||
match (role, lane) {
|
||||
( Some(CORE), Some(SAFE_LANE)) => 1,
|
||||
( Some(CORE), Some(MID_LANE)) => 2,
|
||||
( Some(CORE), Some(OFF_LANE)) => 3,
|
||||
( _, Some(ROAMING)) => 4,
|
||||
( _, Some(JUNGLE)) => 5,
|
||||
(Some(LIGHT_SUPPORT), _) => 6,
|
||||
( Some(HARD_SUPPORT), _) => 7,
|
||||
( _, _) => 8,
|
||||
(Some(CORE), Some(SAFE_LANE)) => 1,
|
||||
(Some(CORE), Some(MID_LANE)) => 2,
|
||||
(Some(CORE), Some(OFF_LANE)) => 3,
|
||||
(_, Some(ROAMING)) => 4,
|
||||
(_, Some(JUNGLE)) => 5,
|
||||
(Some(LIGHT_SUPPORT), _) => 6,
|
||||
(Some(HARD_SUPPORT), _) => 7,
|
||||
(_, _) => 8,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn stringify_role_lane(&self, role: Role, lane: Lane) -> &'static str {
|
||||
use Role::*;
|
||||
use Lane::*;
|
||||
|
||||
|
||||
log::trace!("Stringifying role and lane...");
|
||||
|
||||
|
||||
match (role, lane) {
|
||||
( CORE, SAFE_LANE) => "1️⃣ Safe Carry",
|
||||
( CORE, MID_LANE) => "2️⃣ Mid Carry",
|
||||
( CORE, OFF_LANE) => "3️⃣ Off Tank",
|
||||
( _, ROAMING) => "🔀 Roaming",
|
||||
( _, JUNGLE) => "⏫ Jungle",
|
||||
(LIGHT_SUPPORT, _) => "4️⃣ Soft Support",
|
||||
( HARD_SUPPORT, _) => "5️⃣ Hard Support",
|
||||
( _, _) => "🆕 Sconosciuto",
|
||||
(CORE, SAFE_LANE) => "1️⃣ Safe Carry",
|
||||
(CORE, MID_LANE) => "2️⃣ Mid Carry",
|
||||
(CORE, OFF_LANE) => "3️⃣ Off Tank",
|
||||
(_, ROAMING) => "🔀 Roaming",
|
||||
(_, JUNGLE) => "⏫ Jungle",
|
||||
(LIGHT_SUPPORT, _) => "4️⃣ Soft Support",
|
||||
(HARD_SUPPORT, _) => "5️⃣ Hard Support",
|
||||
(_, _) => "🆕 Sconosciuto",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn emojify_imp(&self, imp: Short) -> &'static str {
|
||||
log::trace!("Emojifying IMP...");
|
||||
|
||||
|
||||
match imp {
|
||||
Short::MIN..=-49 => "🟧",
|
||||
-48..=-25 => "🔶",
|
||||
|
@ -575,44 +575,44 @@ impl BroochService {
|
|||
49..=Short::MAX => "🟦",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn emojify_kills_deaths_assists(&self, kills: Byte, deaths: Byte, assists: Byte) -> &'static str {
|
||||
log::trace!("Emojifying KDA...");
|
||||
|
||||
|
||||
let kills = kills as i16;
|
||||
let deaths = deaths as i16;
|
||||
let assists = assists as i16;
|
||||
|
||||
|
||||
let kda_score = kills + (assists / 2) - deaths;
|
||||
|
||||
|
||||
match kda_score {
|
||||
i16::MIN..=-1 => "➖",
|
||||
0..=i16::MAX => "➕",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn stringify_player(&self, database: &mut PgConnection, player: Player, show_outcome: bool) -> AnyResult<String> {
|
||||
log::debug!("Stringifying player...");
|
||||
log::trace!("Showing outcome: {show_outcome:?}");
|
||||
|
||||
|
||||
let steam = self.get_player_steam(&player)?;
|
||||
let name = self.get_player_name(&steam)?;
|
||||
let telegram_id = self.get_player_telegram_id(database, steam)?;
|
||||
let hero_name = self.get_player_hero_name(&player)?;
|
||||
let outcome = self.get_player_outcome(&player)?;
|
||||
|
||||
|
||||
let role = player.role.clone();
|
||||
let lane = player.lane.clone();
|
||||
let imp = player.imp;
|
||||
|
||||
|
||||
let kills = player.kills;
|
||||
let deaths = player.deaths;
|
||||
let assists = player.assists;
|
||||
|
||||
|
||||
// TODO: Buffs
|
||||
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
|
||||
|
||||
match telegram_id {
|
||||
None => lines.push(format!(
|
||||
"<u><b>{}</b> ({})</u>",
|
||||
|
@ -626,102 +626,102 @@ impl BroochService {
|
|||
hero_name.to_string().escape_telegram_html(),
|
||||
)),
|
||||
}
|
||||
|
||||
|
||||
if show_outcome {
|
||||
lines.push(format!(
|
||||
"— {}", self.stringify_outcome(outcome)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if let (Some(role), Some(lane)) = (role, lane) {
|
||||
lines.push(format!(
|
||||
"— {}", self.stringify_role_lane(role, lane)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if let (Some(kills), Some(deaths), Some(assists)) = (kills, deaths, assists) {
|
||||
lines.push(format!(
|
||||
"— {} {kills} K / {deaths} D / {assists} A", self.emojify_kills_deaths_assists(kills, deaths, assists)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if let Some(imp) = imp {
|
||||
lines.push(format!(
|
||||
"— {} {imp} IMP", self.emojify_imp(imp)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
|
||||
fn stringify_match(&self, database: &mut PgConnection, r#match: Match) -> AnyResult<(DotaMatchId, Option<String>)> {
|
||||
log::debug!("Stringifying match...");
|
||||
|
||||
|
||||
let match_id = self.get_match_id(&r#match)?;
|
||||
|
||||
|
||||
if !self.should_process_match_exists(database, match_id)? {
|
||||
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 timedelta = self.get_match_timedelta(&datetime);
|
||||
|
||||
|
||||
let r#type = self.get_match_type(&r#match)?;
|
||||
let mode = self.get_match_mode(&r#match)?;
|
||||
let duration = self.get_match_duration(&r#match)?;
|
||||
|
||||
|
||||
let mut players = self.get_match_players(r#match)?;
|
||||
|
||||
|
||||
if !self.should_process_match_players(&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) {
|
||||
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));
|
||||
|
||||
|
||||
let _side = self.get_match_side(&players)?;
|
||||
let outcome = self.get_match_outcome(&players)?;
|
||||
|
||||
|
||||
let mut lines = Vec::<String>::new();
|
||||
|
||||
|
||||
lines.push(format!(
|
||||
"{} <u><b>{}</b></u>",
|
||||
self.emojify_outcome(outcome),
|
||||
self.stringify_outcome(outcome),
|
||||
));
|
||||
|
||||
|
||||
lines.push(format!(
|
||||
"<b>{}</b> · {} · <i>{}</i>",
|
||||
self.stringify_type(r#type),
|
||||
self.stringify_mode(mode),
|
||||
self.stringify_duration(duration),
|
||||
));
|
||||
|
||||
|
||||
lines.push("".to_string());
|
||||
|
||||
|
||||
for player in players.into_iter() {
|
||||
let string = self.stringify_player(database, player, outcome == MatchOutcome::Clash)?;
|
||||
lines.push(string);
|
||||
lines.push("".to_string());
|
||||
}
|
||||
|
||||
|
||||
lines.push(format!(
|
||||
"Partita <code>{}</code>",
|
||||
match_id,
|
||||
));
|
||||
|
||||
|
||||
Ok((match_id, Some(lines.join("\n"))))
|
||||
}
|
||||
|
||||
|
||||
async fn send_notification(&self, match_id: DotaMatchId, text: &str) -> AnyResult<Message> {
|
||||
log::debug!("Sending notification...");
|
||||
|
||||
|
||||
self.telegram_bot.send_message(self.notification_chat_id, text)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.disable_notification(true)
|
||||
|
@ -735,26 +735,26 @@ impl BroochService {
|
|||
.await
|
||||
.context("Impossibile inviare la notifica di una partita.")
|
||||
}
|
||||
|
||||
|
||||
async fn iteration(&self) -> AnyResult<()> {
|
||||
log::debug!("Now running an iteration of brooch!");
|
||||
|
||||
|
||||
let client = self.create_http_client()?;
|
||||
let mut database = self.create_postgres_connection()?;
|
||||
|
||||
|
||||
let data = self.query_guild_matches(&client).await?;
|
||||
|
||||
|
||||
let guild = self.process_guild_data(data)?;
|
||||
let matches = self.process_matches_data(guild)?;
|
||||
|
||||
|
||||
let results = matches
|
||||
.into_iter()
|
||||
.map(|r#match| self.stringify_match(&mut database, r#match))
|
||||
.collect::<Vec<AnyResult<(DotaMatchId, Option<String>)>>>();
|
||||
|
||||
|
||||
for result in results {
|
||||
let (match_id, message) = result?;
|
||||
|
||||
|
||||
if let Some(message) = message {
|
||||
self.send_notification(match_id, &message).await?;
|
||||
BroochMatch::flag(&mut database, match_id)?;
|
||||
|
@ -769,7 +769,7 @@ impl RoyalnetService for BroochService {
|
|||
async fn run(&mut self) -> AnyResult<()> {
|
||||
loop {
|
||||
self.iteration().await?;
|
||||
|
||||
|
||||
sleep(Duration::new(60 * 15, 0)).await;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,30 +7,30 @@ use crate::utils::anyhow_result::AnyResult;
|
|||
#[allow(dead_code)]
|
||||
pub trait RoyalnetService {
|
||||
async fn run(&mut self) -> AnyResult<()>;
|
||||
|
||||
|
||||
async fn run_loop(&mut self) {
|
||||
let mut backoff = Duration::new(1, 0);
|
||||
|
||||
|
||||
loop {
|
||||
let result = self.run().await;
|
||||
|
||||
|
||||
match result {
|
||||
Err(e) => {
|
||||
log::error!("Service exited with error: {e:?}.")
|
||||
},
|
||||
}
|
||||
_ => {
|
||||
log::debug!("Service exited successfully!")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let backoff_secs = backoff.as_secs();
|
||||
|
||||
|
||||
log::debug!("Backing off for {backoff_secs} seconds before restarting...");
|
||||
sleep(backoff).await;
|
||||
|
||||
|
||||
log::trace!("Doubling backoff value...");
|
||||
backoff *= 2;
|
||||
|
||||
|
||||
log::trace!("Backoff value is now {backoff_secs} seconds.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
const ANSWERS: [&str; 60] = [
|
||||
// risposte "sì": 20
|
||||
"🔵 Sì.",
|
||||
"🔵 Decisamente sì!",
|
||||
"🔵 Uhm, secondo me sì.",
|
||||
"🔵 Sì! Sì! SÌ!",
|
||||
"🔵 Yup.",
|
||||
"🔵 Direi proprio di sì.",
|
||||
"🔵 Assolutamente sì.",
|
||||
"🔵 Ma certo!",
|
||||
"🔵 Esatto!",
|
||||
"🔵 Senz'altro!",
|
||||
"🔵 Ovviamente.",
|
||||
"🔵 Questa domanda ha risposta affermativa.",
|
||||
"🔵 Hell yeah.",
|
||||
"🔵 YES! YES! YES!",
|
||||
"🔵 yusssssss",
|
||||
"🔵 Non vedo perchè no",
|
||||
"🔵 Ha senso, ha perfettamente senso, nulla da obiettare, ha senso.",
|
||||
"🔵 Yos!",
|
||||
"🔵 Sì, ma tienilo segreto...",
|
||||
"🔵 [RADIO] Affermativo.",
|
||||
|
||||
// risposte "no": 20
|
||||
"❌ No.",
|
||||
"❌ Decisamente no!",
|
||||
"❌ Uhm, secondo me sì. No, aspetta, ci ho ripensato. È un no.",
|
||||
"❌ No, no, e ancora NO!",
|
||||
"❌ Nope.",
|
||||
"❌ Direi proprio di no.",
|
||||
"❌ Assolutamente no.",
|
||||
"❌ Certo che no!",
|
||||
"❌ Neanche per idea!",
|
||||
"❌ Neanche per sogno!",
|
||||
"❌ Niente affatto!",
|
||||
"❌ Questa domanda ha risposta negativa.",
|
||||
"❌ Hell no.",
|
||||
"❌ NO! NO! NO!",
|
||||
"❌ lolno",
|
||||
"❌ NEIN NEIN NEIN NEIN",
|
||||
"❌ Delet dis",
|
||||
"❌ Nopety nope!",
|
||||
"❌ No, ma tienilo segreto.",
|
||||
"❌ [RADIO] Negativo.",
|
||||
|
||||
// risposte "boh": 20
|
||||
"❔ Boh.",
|
||||
"❔ E io che ne so?!",
|
||||
"❔ Non so proprio rispondere.",
|
||||
"❔ Non lo so...",
|
||||
"❔ Mi avvalgo della facoltà di non rispondere.",
|
||||
"❔ Non parlerò senza il mio avvocato!",
|
||||
"❔ Dunno.",
|
||||
"❔ Perché lo chiedi a me?",
|
||||
"❔ Ah, non lo so io!",
|
||||
r#"❔ ¯\_(ツ)_/¯"#,
|
||||
"❔ No idea.",
|
||||
"❔ Dunno.",
|
||||
"❔ Boooooh!",
|
||||
"❔ Non ne ho la più pallida idea.",
|
||||
"❔ No comment.",
|
||||
"❔ maibi",
|
||||
"❔ maibi not",
|
||||
"❔ idk dude",
|
||||
"❔ Non mi è permesso condividere questa informazione.",
|
||||
"❔ [RADIO] Mantengo la posizione.",
|
||||
"🔵 Sì.",
|
||||
"🔵 Decisamente sì!",
|
||||
"🔵 Uhm, secondo me sì.",
|
||||
"🔵 Sì! Sì! SÌ!",
|
||||
"🔵 Yup.",
|
||||
"🔵 Direi proprio di sì.",
|
||||
"🔵 Assolutamente sì.",
|
||||
"🔵 Ma certo!",
|
||||
"🔵 Esatto!",
|
||||
"🔵 Senz'altro!",
|
||||
"🔵 Ovviamente.",
|
||||
"🔵 Questa domanda ha risposta affermativa.",
|
||||
"🔵 Hell yeah.",
|
||||
"🔵 YES! YES! YES!",
|
||||
"🔵 yusssssss",
|
||||
"🔵 Non vedo perchè no",
|
||||
"🔵 Ha senso, ha perfettamente senso, nulla da obiettare, ha senso.",
|
||||
"🔵 Yos!",
|
||||
"🔵 Sì, ma tienilo segreto...",
|
||||
"🔵 [RADIO] Affermativo.",
|
||||
|
||||
// risposte "no": 20
|
||||
"❌ No.",
|
||||
"❌ Decisamente no!",
|
||||
"❌ Uhm, secondo me sì. No, aspetta, ci ho ripensato. È un no.",
|
||||
"❌ No, no, e ancora NO!",
|
||||
"❌ Nope.",
|
||||
"❌ Direi proprio di no.",
|
||||
"❌ Assolutamente no.",
|
||||
"❌ Certo che no!",
|
||||
"❌ Neanche per idea!",
|
||||
"❌ Neanche per sogno!",
|
||||
"❌ Niente affatto!",
|
||||
"❌ Questa domanda ha risposta negativa.",
|
||||
"❌ Hell no.",
|
||||
"❌ NO! NO! NO!",
|
||||
"❌ lolno",
|
||||
"❌ NEIN NEIN NEIN NEIN",
|
||||
"❌ Delet dis",
|
||||
"❌ Nopety nope!",
|
||||
"❌ No, ma tienilo segreto.",
|
||||
"❌ [RADIO] Negativo.",
|
||||
|
||||
// risposte "boh": 20
|
||||
"❔ Boh.",
|
||||
"❔ E io che ne so?!",
|
||||
"❔ Non so proprio rispondere.",
|
||||
"❔ Non lo so...",
|
||||
"❔ Mi avvalgo della facoltà di non rispondere.",
|
||||
"❔ Non parlerò senza il mio avvocato!",
|
||||
"❔ Dunno.",
|
||||
"❔ Perché lo chiedi a me?",
|
||||
"❔ Ah, non lo so io!",
|
||||
r#"❔ ¯\_(ツ)_/¯"#,
|
||||
"❔ No idea.",
|
||||
"❔ Dunno.",
|
||||
"❔ Boooooh!",
|
||||
"❔ Non ne ho la più pallida idea.",
|
||||
"❔ No comment.",
|
||||
"❔ maibi",
|
||||
"❔ maibi not",
|
||||
"❔ idk dude",
|
||||
"❔ Non mi è permesso condividere questa informazione.",
|
||||
"❔ [RADIO] Mantengo la posizione.",
|
||||
];
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let mut rng = rand::rngs::SmallRng::from_entropy();
|
||||
|
||||
|
||||
let answer = ANSWERS.choose(&mut rng)
|
||||
.context("Non è stato possibile selezionare una risposta.")?;
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, answer.to_string())
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -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.")?
|
||||
.pop()
|
||||
.context("Il gatto ricevuto in risposta dall'API non esiste, quindi non è stato possibile riceverlo.")?;
|
||||
|
||||
|
||||
let url: Url = response.url.parse()
|
||||
.context("L'URL del gatto ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?;
|
||||
|
||||
|
||||
let input = InputFile::url(url);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_photo(message.chat.id, input)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare un gatto in risposta a questo messaggio.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -25,68 +25,69 @@ pub struct DiarioArgs {
|
|||
|
||||
impl FromStr for DiarioArgs {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r#" *(?:\[(?<warning>.+)])? *"(?<quote>.+)"[, ]*(?:[-–—]+(?<quoted>\w+)(?:, *(?<context>.+))?)?"#).unwrap());
|
||||
|
||||
|
||||
let captures = REGEX.captures(s);
|
||||
|
||||
|
||||
let args = match captures {
|
||||
Some(captures) => {
|
||||
let warning = captures.name("warning")
|
||||
.map(|s| s.as_str().to_owned());
|
||||
|
||||
|
||||
let quote = captures.name("quote")
|
||||
.context("Citazione non specificata nel comando.")?
|
||||
.as_str()
|
||||
.to_owned();
|
||||
|
||||
|
||||
let quoted = captures.name("quoted")
|
||||
.map(|s| s.as_str().to_owned());
|
||||
|
||||
|
||||
let context = captures.name("context")
|
||||
.map(|s| s.as_str().to_owned());
|
||||
|
||||
DiarioArgs {warning, quote, quoted, context}
|
||||
},
|
||||
|
||||
DiarioArgs { warning, quote, quoted, context }
|
||||
}
|
||||
None => {
|
||||
let warning = None;
|
||||
let quote = s.to_string();
|
||||
let quoted = None;
|
||||
let context = None;
|
||||
|
||||
DiarioArgs {warning, quote, quoted, context}
|
||||
},
|
||||
|
||||
DiarioArgs { warning, quote, quoted, context }
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ok(args)
|
||||
}
|
||||
}
|
||||
|
||||
impl TelegramWrite for Diario {
|
||||
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
|
||||
where T: Write
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
// Diario ID
|
||||
write!(f, "<code>#{}</code>", self.id)?;
|
||||
|
||||
|
||||
// Optional content warning
|
||||
if let Some(warning) = self.to_owned().warning {
|
||||
write!(f, ", <b>{}</b>", warning.escape_telegram_html())?;
|
||||
}
|
||||
|
||||
|
||||
// Newline
|
||||
writeln!(f)?;
|
||||
|
||||
|
||||
// Quote optionally covered by a spoiler tag
|
||||
match self.warning.to_owned() {
|
||||
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())?,
|
||||
}
|
||||
|
||||
|
||||
// Newline
|
||||
writeln!(f)?;
|
||||
|
||||
|
||||
// Optional citation with optional context
|
||||
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())?,
|
||||
|
@ -94,7 +95,7 @@ impl TelegramWrite for Diario {
|
|||
(None, Some(context)) => write!(f, "...<i>{}</i>", context.escape_telegram_html())?,
|
||||
(None, None) => write!(f, "")?,
|
||||
};
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -102,16 +103,16 @@ impl TelegramWrite for Diario {
|
|||
pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database: &DatabaseInterface) -> CommandResult {
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
|
||||
|
||||
let mut database = database.connect()?;
|
||||
|
||||
|
||||
let royalnet_user: RoyalnetUser = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::interfaces::database::schema::telegram::dsl::*;
|
||||
use crate::interfaces::database::schema::users::dsl::*;
|
||||
use crate::interfaces::database::models::RoyalnetUser;
|
||||
|
||||
|
||||
telegram
|
||||
.filter(telegram_id.eq::<i64>(
|
||||
author.id.0.try_into()
|
||||
|
@ -122,10 +123,10 @@ pub async fn handler(bot: &Bot, message: &Message, args: &DiarioArgs, database:
|
|||
.get_result(&mut database)
|
||||
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?
|
||||
};
|
||||
|
||||
|
||||
let entry = {
|
||||
use schema::diario;
|
||||
|
||||
|
||||
insert_into(diario::table)
|
||||
.values(&(
|
||||
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)
|
||||
.context("Non è stato possibile aggiungere la riga di diario al database RYG.")?
|
||||
};
|
||||
|
||||
|
||||
let text = format!(
|
||||
"🖋 Riga aggiunta al diario!\n\
|
||||
\n\
|
||||
{}",
|
||||
entry.to_string_telegram(),
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.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
|
||||
// .context("Non è stato possibile inviare la risposta.")?
|
||||
;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -22,17 +22,17 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
.context("Non è stato possibile richiedere un cane all'API.")?
|
||||
.json::<DogQueryResponse>().await
|
||||
.context("Il cane ricevuto in risposta dall'API è indecifrabile, quindi non è stato possibile riceverlo.")?;
|
||||
|
||||
|
||||
let url: Url = response.message.parse()
|
||||
.context("L'URL del cane ricevuto in risposta dall'API è malformato, quindi non è stato possibile riceverlo.")?;
|
||||
|
||||
|
||||
let input = InputFile::url(url);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_photo(message.chat.id, input)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare un cane in risposta a questo messaggio.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -10,12 +10,12 @@ pub async fn handler(bot: &Bot, message: &Message, text: &str) -> CommandResult
|
|||
let text = format!(
|
||||
"💬 {text}"
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -174,21 +174,21 @@ const FORTUNES: [&str; 164] = [
|
|||
"🍻 Oggi una birra ti ridarà una vita!",
|
||||
"🎶 Oggi Hatsune Miku si nasconderà nella tua Wi-Fi!",
|
||||
"🚽 Oggi delle telecamere combatteranno contro dei gabinetti!",
|
||||
"🌟 Oggi verrà scoperta una galassia grande quanto qualcuno della tua famiglia!",
|
||||
"🎶 Oggi Rick non rinuncerà mai a te!",
|
||||
"🏚 Oggi ristrutturerai una villa completando dei minigiochi match-3!",
|
||||
"🌟 Oggi verrà scoperta una galassia grande quanto qualcuno della tua famiglia!",
|
||||
"🎶 Oggi Rick non rinuncerà mai a te!",
|
||||
"🏚 Oggi ristrutturerai una villa completando dei minigiochi match-3!",
|
||||
];
|
||||
|
||||
struct FortuneKey {
|
||||
today: chrono::NaiveDate,
|
||||
author_id: teloxide::types::UserId
|
||||
author_id: teloxide::types::UserId,
|
||||
}
|
||||
|
||||
impl Hash for FortuneKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
let days: i32 = self.today.num_days_from_ce();
|
||||
let id: u64 = self.author_id.0;
|
||||
|
||||
|
||||
state.write_i32(days);
|
||||
state.write_u64(id);
|
||||
}
|
||||
|
@ -196,13 +196,13 @@ impl Hash for FortuneKey {
|
|||
|
||||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let today = chrono::Local::now().date_naive();
|
||||
|
||||
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
let author_id = author.id;
|
||||
|
||||
let key = FortuneKey {today, author_id};
|
||||
|
||||
|
||||
let key = FortuneKey { today, author_id };
|
||||
|
||||
let mut hasher = std::hash::DefaultHasher::new();
|
||||
key.hash(&mut hasher);
|
||||
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.");
|
||||
}
|
||||
let hash = hash.unwrap();
|
||||
|
||||
|
||||
let mut rng = rand::rngs::SmallRng::from_seed(hash);
|
||||
|
||||
|
||||
let fortune = FORTUNES.choose(&mut rng)
|
||||
.context("Non è stato possibile selezionare il tuo oroscopo.")?;
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, fortune.to_string())
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -9,16 +9,16 @@ use super::CommandResult;
|
|||
|
||||
pub async fn handler_all(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let descriptions = super::Command::descriptions().to_string();
|
||||
|
||||
|
||||
let text = format!("❔ <b>Comandi disponibili</b>\n\n{descriptions}");
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -27,51 +27,51 @@ pub async fn handler_specific(bot: &Bot, message: &Message, target: &str) -> Com
|
|||
.get_me()
|
||||
.await
|
||||
.context("Non è stato possibile recuperare informazioni sul bot.")?;
|
||||
|
||||
|
||||
let me_username = me.username.as_ref()
|
||||
.context("Non è stato possibile determinare l'username del bot.")?;
|
||||
|
||||
|
||||
let suffix = format!("@{me_username}");
|
||||
|
||||
|
||||
let target = target.strip_prefix('/')
|
||||
.unwrap_or(target);
|
||||
|
||||
|
||||
let target = target.strip_suffix(&suffix)
|
||||
.unwrap_or(target);
|
||||
|
||||
|
||||
log::trace!("Stripped target command: {target:?}");
|
||||
|
||||
|
||||
let all_commands: Vec<BotCommand> = super::Command::bot_commands();
|
||||
|
||||
|
||||
log::trace!("All commands are: {all_commands:?}");
|
||||
|
||||
|
||||
let identify_command = |cmd: &&BotCommand| -> bool {
|
||||
let command = &cmd.command;
|
||||
|
||||
|
||||
let command = command.strip_prefix('/')
|
||||
.unwrap_or_else(|| command);
|
||||
|
||||
|
||||
target == command
|
||||
};
|
||||
|
||||
|
||||
let target = match all_commands.iter().find(identify_command) {
|
||||
Some(bot_command) => bot_command,
|
||||
None => anyhow::bail!("Non è stato possibile trovare il comando specificato."),
|
||||
};
|
||||
|
||||
|
||||
let display_suffix = match message.chat.is_private() {
|
||||
false => &suffix,
|
||||
true => "",
|
||||
};
|
||||
|
||||
|
||||
let text = format!("❔ <b>Comando {}{}</b>\n\n{}", target.command, display_suffix, target.description);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -21,26 +21,26 @@ pub struct MatchmakingArgs {
|
|||
|
||||
impl FromStr for MatchmakingArgs {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<start>.*)]\s*(?<text>.+)$").unwrap());
|
||||
|
||||
|
||||
let captures = REGEX.captures(s)
|
||||
.context("Sintassi del comando incorretta.")?;
|
||||
|
||||
|
||||
let start = captures.name("start")
|
||||
.unwrap()
|
||||
.as_str();
|
||||
|
||||
|
||||
let text = captures.name("text")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
||||
|
||||
let start = parse_datetime_at_date(chrono::Local::now(), start)
|
||||
.context("Impossibile determinare la data in cui l'attesa avrà termine.")?
|
||||
.with_timezone(&chrono::Local);
|
||||
|
||||
|
||||
Ok(
|
||||
Self { start, text }
|
||||
)
|
||||
|
@ -49,23 +49,23 @@ impl FromStr for MatchmakingArgs {
|
|||
|
||||
pub async fn handler(bot: &Bot, message: &Message, args: &MatchmakingArgs, database: &DatabaseInterface) -> CommandResult {
|
||||
let mut database = database.connect()?;
|
||||
|
||||
|
||||
let event = MatchmakingEvent::create(&mut database, &args.text, &args.start)
|
||||
.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))
|
||||
.await
|
||||
.context("Non è stato possibile postare il matchmaking.")?;
|
||||
|
||||
|
||||
sleep_chrono(&args.start).await;
|
||||
|
||||
|
||||
let _mm2 = MatchmakingMessageTelegram::send_new_and_create(&mut database, event.id, bot, message.chat.id, Some(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile confermare il matchmaking.")?;
|
||||
|
||||
|
||||
mm1.destroy_and_send_delete(&mut database, bot)
|
||||
.await
|
||||
.context("Non è stato possibile eliminare il matchmaking.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -34,7 +34,9 @@ type CommandResult = AnyResult<()>;
|
|||
pub enum Command {
|
||||
#[command(description = "Invia messaggio di introduzione.")]
|
||||
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),
|
||||
#[command(description = "Mostra il tuo oroscopo di oggi.")]
|
||||
Fortune,
|
||||
|
@ -62,28 +64,28 @@ impl Command {
|
|||
/// Update the [commands menu](https://core.telegram.org/bots/features#commands) of the bot.
|
||||
pub async fn set_commands(bot: &mut Bot) -> AnyResult<()> {
|
||||
log::debug!("Setting bot commands...");
|
||||
|
||||
|
||||
log::trace!("Determining bot commands...");
|
||||
let commands = Self::bot_commands();
|
||||
|
||||
|
||||
log::trace!("Setting commands: {commands:#?}");
|
||||
bot.set_my_commands(commands).await
|
||||
.context("Non è stato possibile aggiornare la lista comandi del bot.")?;
|
||||
|
||||
|
||||
log::trace!("Setting commands successful!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_self(self, bot: Bot, message: Message, database: Arc<DatabaseInterface>) -> CommandResult {
|
||||
log::debug!("Handling command...");
|
||||
|
||||
|
||||
log::trace!(
|
||||
"Handling {:?} in {:?} with {:?}...",
|
||||
self,
|
||||
&message.chat.id,
|
||||
&message.id,
|
||||
);
|
||||
|
||||
|
||||
// FIXME: Quick hack to fix single thread
|
||||
log::trace!("Spawning task for future...");
|
||||
let _task = tokio::spawn(async move {
|
||||
|
@ -105,28 +107,28 @@ impl Command {
|
|||
Command::Diario(ref args) => diario::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...");
|
||||
let result2 = match result1.as_ref() {
|
||||
Ok(_) => return,
|
||||
Err(e1) => self.handle_error(&bot, &message, e1).await
|
||||
};
|
||||
|
||||
|
||||
let e1 = result1.unwrap_err();
|
||||
|
||||
|
||||
log::trace!("Delegating fatal error handling to fatal error handler...");
|
||||
let _result3 = match result2 {
|
||||
Ok(_) => return,
|
||||
Err(e2) => self.handle_fatal(&bot, &message, &e1, &e2).await
|
||||
};
|
||||
|
||||
|
||||
log::trace!("Successfully handled command!");
|
||||
});
|
||||
|
||||
|
||||
log::trace!("Successfully spawned task!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_unknown(bot: Bot, message: Message) -> CommandResult {
|
||||
log::debug!("Received an unknown command or an invalid syntax: {:?}", message.text());
|
||||
|
||||
|
@ -136,11 +138,11 @@ impl Command {
|
|||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare il messaggio di errore.")?;
|
||||
|
||||
|
||||
log::trace!("Successfully handled unknown command!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn handle_error(&self, bot: &Bot, message: &Message, error: &Error) -> CommandResult {
|
||||
log::debug!(
|
||||
"Command message in {:?} with id {:?} and contents {:?} errored out with `{:?}`",
|
||||
|
@ -156,11 +158,11 @@ impl Command {
|
|||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare il messaggio di errore.")?;
|
||||
|
||||
|
||||
log::trace!("Successfully handled errored command!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
async fn handle_fatal(&self, _bot: &Bot, message: &Message, error1: &Error, error2: &Error) -> CommandResult {
|
||||
log::error!(
|
||||
"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,
|
||||
error2,
|
||||
);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,26 +22,26 @@ pub struct ReminderArgs {
|
|||
|
||||
impl FromStr for ReminderArgs {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[(?<target>.*)]\s*(?<reminder>.+)$").unwrap());
|
||||
|
||||
|
||||
let captures = REGEX.captures(s)
|
||||
.context("Sintassi del comando incorretta.")?;
|
||||
|
||||
|
||||
let target = captures.name("target")
|
||||
.unwrap()
|
||||
.as_str();
|
||||
|
||||
|
||||
let reminder = captures.name("reminder")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string();
|
||||
|
||||
|
||||
let target = parse_datetime_at_date(chrono::Local::now(), target)
|
||||
.context("Impossibile determinare la data in cui l'attesa avrà termine.")?
|
||||
.with_timezone(&chrono::Local);
|
||||
|
||||
|
||||
Ok(
|
||||
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(),
|
||||
reminder.clone().escape_telegram_html()
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la conferma del promemoria.")?;
|
||||
|
||||
|
||||
sleep_chrono(target).await;
|
||||
|
||||
|
||||
let text = format!(
|
||||
"🕒 <b>Promemoria attivato</b>\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(),
|
||||
reminder.escape_telegram_html()
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare il promemoria.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -9,20 +9,20 @@ use teloxide::types::ReplyParameters;
|
|||
use crate::services::telegram::commands::CommandResult;
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult {
|
||||
let mut rng = rand::rngs::SmallRng::from_entropy();
|
||||
|
||||
if rng.gen_range(1..1001) == 1 {
|
||||
let _reply = bot
|
||||
let mut rng = rand::rngs::SmallRng::from_entropy();
|
||||
|
||||
if rng.gen_range(1..1001) == 1 {
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, "🎶 Roll? Rick roll! https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]+[0-9]+)?"#).unwrap();
|
||||
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]+[0-9]+)?"#).unwrap();
|
||||
|
||||
let captures = re.captures(roll)
|
||||
.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>())
|
||||
.unwrap_or(Ok(1))
|
||||
.context("La quantità di dadi da lanciare deve essere un numero intero positivo diverso da 0.")?;
|
||||
|
||||
|
||||
let die = captures.name("die")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse::<u32>()
|
||||
.context("La dimensione del dado da lanciare deve essere un numero intero positivo.")?;
|
||||
|
||||
|
||||
let modifier = captures.name("modifier")
|
||||
.map(|m| m.as_str())
|
||||
.map(|m| m.parse::<i32>())
|
||||
.unwrap_or(Ok(0))
|
||||
.context("Il modificatore dei dadi lanciati deve essere un numero intero.")?;
|
||||
|
||||
if die == 0 {
|
||||
|
||||
if die == 0 {
|
||||
anyhow::bail!("Non è stato specificato nessun dado.")
|
||||
}
|
||||
|
||||
if qty < 1 {
|
||||
anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.")
|
||||
}
|
||||
|
||||
let mut nums_rolled = Vec::<u32>::new();
|
||||
for _ in 0..qty {
|
||||
nums_rolled.push(
|
||||
}
|
||||
|
||||
if qty < 1 {
|
||||
anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.")
|
||||
}
|
||||
|
||||
let mut nums_rolled = Vec::<u32>::new();
|
||||
for _ in 0..qty {
|
||||
nums_rolled.push(
|
||||
rng.gen_range(1..=die)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let roll_string = nums_rolled
|
||||
.iter()
|
||||
.map(|n| n.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.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
|
||||
.send_message(message.chat.id, answer)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -9,38 +9,38 @@ use super::CommandResult;
|
|||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
|
||||
|
||||
let author_username = match author.username.as_ref() {
|
||||
None => {
|
||||
author.first_name.clone()
|
||||
},
|
||||
}
|
||||
Some(username) => {
|
||||
format!("@{}", &username)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let me = bot
|
||||
.get_me()
|
||||
.await
|
||||
.context("Non è stato possibile recuperare informazioni sul bot.")?;
|
||||
|
||||
|
||||
let me_username = me.username.as_ref()
|
||||
.context("Non è stato possibile determinare l'username del bot.")?;
|
||||
|
||||
|
||||
let version = crate::utils::version::VERSION;
|
||||
|
||||
|
||||
let text = format!(
|
||||
"👋 Ciao {author_username}! Sono @{me_username}, il robot tuttofare della RYG!\n\n\
|
||||
Sto eseguendo la versione {version}.\n\n\
|
||||
Puoi vedere l'elenco delle mie funzionalità dal menu in basso.\n\n\
|
||||
Cosa posso fare per te oggi?",
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@ use super::CommandResult;
|
|||
pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult {
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
|
||||
|
||||
let mut database = database.connect()?;
|
||||
|
||||
|
||||
let royalnet_user: RoyalnetUser = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::interfaces::database::schema::telegram::dsl::*;
|
||||
use crate::interfaces::database::schema::users::dsl::*;
|
||||
|
||||
|
||||
telegram
|
||||
.filter(telegram_id.eq::<i64>(
|
||||
author.id.0.try_into()
|
||||
|
@ -32,20 +32,20 @@ pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface)
|
|||
.get_result(&mut database)
|
||||
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?
|
||||
};
|
||||
|
||||
|
||||
let username = &royalnet_user.username;
|
||||
|
||||
|
||||
let text = format!(
|
||||
"👤 Nel database RYG, tu hai l'username <code>{}</code>.",
|
||||
username.escape_telegram_html(),
|
||||
);
|
||||
|
||||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ impl DatabaseInterface {
|
|||
pub fn new(database_url: String) -> Self {
|
||||
Self { database_url }
|
||||
}
|
||||
|
||||
|
||||
pub fn connect(&self) -> AnyResult<PgConnection> {
|
||||
crate::interfaces::database::connect(&self.database_url)
|
||||
.context("Impossibile connettersi al database RYG.")
|
||||
|
|
|
@ -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 {
|
||||
use MatchmakingTelegramKeyboardCallback::*;
|
||||
|
||||
|
||||
let mut database = database.connect()
|
||||
.context("Non è stato possibile connettersi al database RYG.")?;
|
||||
|
||||
|
||||
let royalnet_user = RoyalnetUser::from_telegram_userid(&mut database, query.from.id)
|
||||
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?;
|
||||
|
||||
|
||||
match callback {
|
||||
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)?,
|
||||
|
@ -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)?,
|
||||
Wont => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Wont)?,
|
||||
};
|
||||
|
||||
|
||||
let messages_telegram = MatchmakingMessageTelegram::get_all(&mut database, matchmaking_id)
|
||||
.context("Non è stato possibile recuperare i messaggi di matchmaking inviati su Telegram.")?;
|
||||
|
||||
|
||||
for message_telegram in messages_telegram {
|
||||
message_telegram.make_text_and_send_edit(&mut database, bot)
|
||||
.await
|
||||
.context("Non è stato possibile aggiornare un messaggio di matchmaking su Telegram.")?;
|
||||
}
|
||||
|
||||
|
||||
let _ = bot.answer_callback_query(query.id)
|
||||
.text("Ricevuto!")
|
||||
.await
|
||||
.context("Non è stato possibile rispondere alla pressione del bottone su Telegram.")?;
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -19,24 +19,24 @@ pub enum KeyboardCallback {
|
|||
|
||||
impl FromStr for KeyboardCallback {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (keyword, data) = s.split_once(":")
|
||||
.context("Impossibile dividere il payload in keyword e dati.")?;
|
||||
|
||||
|
||||
let (id, data) = data.split_once(":")
|
||||
.context("Impossibile dividere il payload in id e dati.")?;
|
||||
|
||||
|
||||
let id: MatchmakingId = id.parse()
|
||||
.context("Impossibile convertire l'id a un numero.")?;
|
||||
|
||||
|
||||
match keyword {
|
||||
"matchmaking" => {
|
||||
data
|
||||
.parse()
|
||||
.map(|c| Self::Matchmaking(id, c))
|
||||
.context("Impossibile processare i dati.")
|
||||
},
|
||||
}
|
||||
x => {
|
||||
anyhow::bail!("Keyword sconosciuta: {x:?}")
|
||||
}
|
||||
|
@ -47,33 +47,33 @@ impl FromStr for KeyboardCallback {
|
|||
impl KeyboardCallback {
|
||||
pub async fn handle_self(self, bot: Bot, query: CallbackQuery, database: Arc<DatabaseInterface>) -> KeyboardCallbackResult {
|
||||
log::debug!("Handling keyboard callback...");
|
||||
|
||||
|
||||
log::trace!(
|
||||
"Handling {:?} in {:?} with {:?}...",
|
||||
self,
|
||||
&query.message.as_ref().map(|q| q.chat().id),
|
||||
&query.id,
|
||||
);
|
||||
|
||||
|
||||
match self {
|
||||
Self::Matchmaking(matchmaking_id, callback) => {
|
||||
matchmaking::handler(&bot, query, matchmaking_id, callback, &database).await?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log::trace!("Successfully handled keyboard callback!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_unknown(bot: Bot, query: CallbackQuery) -> KeyboardCallbackResult {
|
||||
log::warn!("Received an unknown keyboard callback: {:#?}", &query.data);
|
||||
|
||||
|
||||
bot
|
||||
.answer_callback_query(query.id)
|
||||
.show_alert(true)
|
||||
.text("⚠️ Il tasto che hai premuto non è più valido.")
|
||||
.await?;
|
||||
|
||||
|
||||
log::trace!("Successfully handled unknown keyboard callback!");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -32,52 +32,52 @@ pub struct TelegramService {
|
|||
impl TelegramService {
|
||||
pub async fn new(database_url: String, token: String, notification_chat_id: Option<ChatId>) -> AnyResult<Self> {
|
||||
log::info!("Initializing a new Telegram service...");
|
||||
|
||||
|
||||
let bot = Bot::new(token);
|
||||
|
||||
|
||||
log::trace!("Using bot: {bot:#?}");
|
||||
|
||||
|
||||
let me = Self::get_me(&bot)
|
||||
.await?;
|
||||
|
||||
|
||||
log::trace!("Using self details: {me:#?}");
|
||||
|
||||
|
||||
let service = Self {
|
||||
database_url,
|
||||
bot,
|
||||
me,
|
||||
notification_chat_id
|
||||
notification_chat_id,
|
||||
};
|
||||
|
||||
|
||||
log::trace!("Created service: {service:#?}");
|
||||
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
|
||||
|
||||
async fn get_me(bot: &Bot) -> AnyResult<Me> {
|
||||
log::debug!("Getting self details...");
|
||||
bot.get_me().await
|
||||
.context("Recupero dettagli sul bot non riuscito.")
|
||||
}
|
||||
|
||||
|
||||
async fn send_start_notification(&self) -> AnyResult<Message> {
|
||||
log::debug!("Sending start notification...");
|
||||
|
||||
|
||||
let notification_chat_id = self.notification_chat_id
|
||||
.context("La chat di notifica non è abilitata.")?;
|
||||
|
||||
|
||||
let version = crate::utils::version::VERSION
|
||||
.escape_telegram_html();
|
||||
|
||||
|
||||
let username = self.me.username
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.escape_telegram_html();
|
||||
|
||||
|
||||
let id = self.me.user.id
|
||||
.to_string()
|
||||
.escape_telegram_html();
|
||||
|
||||
|
||||
let text = format!(
|
||||
"💠 <b>Servizio Telegram avviato</b>\n\
|
||||
\n\
|
||||
|
@ -85,35 +85,35 @@ impl TelegramService {
|
|||
\n\
|
||||
@{username} [<code>{id}</code>]"
|
||||
);
|
||||
|
||||
|
||||
log::trace!("Sending start notification message...");
|
||||
let msg = self.bot.send_message(notification_chat_id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.await
|
||||
.context("Invio della notifica di avvio non riuscito.")?;
|
||||
|
||||
|
||||
log::trace!("Successfully sent start notification message!");
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
|
||||
async fn set_commands(&mut self) -> AnyResult<()> {
|
||||
log::debug!("Setting self commands...");
|
||||
Command::set_commands(&mut self.bot).await
|
||||
.context("Aggiornamento dei comandi del bot non riuscito.")
|
||||
}
|
||||
|
||||
|
||||
fn dispatcher(&mut self) -> Dispatcher<Bot, AnyError, DefaultKey> {
|
||||
log::debug!("Building dispatcher...");
|
||||
|
||||
|
||||
let bot_name = self.me.user.username.as_ref().unwrap();
|
||||
log::trace!("Bot username is: @{bot_name:?}");
|
||||
|
||||
|
||||
log::trace!("Determining pseudo-command regex...");
|
||||
let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap();
|
||||
log::trace!("Pseudo-command regex is: {regex:?}");
|
||||
|
||||
|
||||
let database = Arc::new(DatabaseInterface::new(self.database_url.clone()));
|
||||
|
||||
|
||||
log::trace!("Building dispatcher...");
|
||||
Dispatcher::builder(
|
||||
self.bot.clone(),
|
||||
|
@ -152,8 +152,8 @@ impl TelegramService {
|
|||
.endpoint(KeyboardCallback::handle_self)
|
||||
)
|
||||
.endpoint(KeyboardCallback::handle_unknown)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
.dependencies(
|
||||
dptree::deps![
|
||||
database
|
||||
|
@ -164,11 +164,11 @@ impl TelegramService {
|
|||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
async fn dispatch(&mut self) -> AnyResult<()> {
|
||||
log::debug!("Starting Telegram dispatcher...");
|
||||
self.dispatcher().dispatch().await;
|
||||
|
||||
|
||||
anyhow::bail!("Telegram dispatcher has exited unexpectedly.")
|
||||
}
|
||||
}
|
||||
|
@ -176,13 +176,13 @@ impl TelegramService {
|
|||
impl RoyalnetService for TelegramService {
|
||||
async fn run(&mut self) -> AnyResult<()> {
|
||||
log::info!("Starting Telegram service...");
|
||||
|
||||
|
||||
let _ = self.set_commands()
|
||||
.await;
|
||||
|
||||
|
||||
let _ = self.send_start_notification()
|
||||
.await;
|
||||
|
||||
|
||||
self.dispatch()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ impl RoyalnetUser {
|
|||
pub fn from_telegram_userid(database: &mut PgConnection, user_id: UserId) -> AnyResult<Self> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use schema::{telegram, users};
|
||||
|
||||
|
||||
log::trace!("Retrieving RoyalnetUser with {user_id:?}");
|
||||
|
||||
|
||||
telegram::table
|
||||
.filter(telegram::telegram_id.eq::<i64>(
|
||||
user_id.0.try_into()
|
||||
|
|
|
@ -2,8 +2,9 @@ use std::fmt::{Error, Write};
|
|||
|
||||
pub trait TelegramWrite {
|
||||
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
|
||||
where T: Write;
|
||||
|
||||
where
|
||||
T: Write;
|
||||
|
||||
fn to_string_telegram(&self) -> String {
|
||||
let mut result = String::new();
|
||||
self.write_telegram(&mut result).unwrap();
|
||||
|
@ -16,7 +17,8 @@ pub trait TelegramEscape {
|
|||
}
|
||||
|
||||
impl<T> TelegramEscape for T
|
||||
where String: From<T>
|
||||
where
|
||||
String: From<T>,
|
||||
{
|
||||
fn escape_telegram_html(self) -> String {
|
||||
String::from(self)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub fn chrono_to_tokio_duration(duration: chrono::TimeDelta) -> Option<tokio::time::Duration> {
|
||||
let nanos = duration.num_nanoseconds()?;
|
||||
|
||||
|
||||
Some(
|
||||
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>) {
|
||||
let now = chrono::Local::now();
|
||||
|
||||
|
||||
let duration = until.signed_duration_since(now);
|
||||
|
||||
|
||||
let duration = chrono_to_tokio_duration(duration)
|
||||
.expect("Nanoseconds to not overflow u64");
|
||||
|
||||
|
||||
tokio::time::sleep(duration).await;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue