1
Fork 0
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:
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")]
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(())
}

View file

@ -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 {}
}
}

View file

@ -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()

View file

@ -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())
}

View file

@ -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(())
}
}

View file

@ -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,

View file

@ -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)

View file

@ -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! {

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::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)
}

View file

@ -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

View file

@ -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.");
}

View file

@ -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;
}
}

View file

@ -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.");
}
}

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.
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(())
}

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.")?
.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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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(())
}

View file

@ -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.")

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 {
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(())
}

View file

@ -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(())
}

View file

@ -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
}

View file

@ -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()

View file

@ -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)

View file

@ -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;
}