mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-21 18:44:19 +00:00
Add /matchmaking
command and refactor whole codebase (#12)
This commit is contained in:
parent
af3ab04604
commit
56e3baa8c0
51 changed files with 1531 additions and 374 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
@ -49,15 +49,16 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
|||
|
||||
[[package]]
|
||||
name = "aquamarine"
|
||||
version = "0.1.12"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a941c39708478e8eea39243b5983f1c42d2717b3620ee91f4a52115fd02ac43f"
|
||||
checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e"
|
||||
dependencies = [
|
||||
"include_dir",
|
||||
"itertools",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -888,6 +889,25 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||
dependencies = [
|
||||
"include_dir_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include_dir_macros"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
|
@ -917,9 +937,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
@ -1069,12 +1089,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "never"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
@ -1656,6 +1670,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_with_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.2"
|
||||
|
@ -1786,21 +1810,21 @@ checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
|
|||
|
||||
[[package]]
|
||||
name = "teloxide"
|
||||
version = "0.12.2"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c63345cf32a8850ebddcdd769dc2d5193d5e231262d5dada264b79da01a664da"
|
||||
checksum = "5f79dd283eb21b90451c03fa7c7f83b9985130efb876b33bad89a2c208ccbc16"
|
||||
dependencies = [
|
||||
"aquamarine",
|
||||
"bytes",
|
||||
"derive_more",
|
||||
"dptree",
|
||||
"either",
|
||||
"futures",
|
||||
"log",
|
||||
"mime",
|
||||
"pin-project",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"teloxide-core",
|
||||
"teloxide-macros",
|
||||
"thiserror",
|
||||
|
@ -1812,9 +1836,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "teloxide-core"
|
||||
version = "0.9.1"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "303db260110c238e3af77bb9dff18bf7a5b5196f783059b0852aab75f91d5a16"
|
||||
checksum = "9e1642a7ef10e7af63b8298c8d13c0f986d4fc646d42649ff060359607f62f69"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytes",
|
||||
|
@ -1824,14 +1848,13 @@ dependencies = [
|
|||
"futures",
|
||||
"log",
|
||||
"mime",
|
||||
"never",
|
||||
"once_cell",
|
||||
"pin-project",
|
||||
"rc-box",
|
||||
"reqwest 0.11.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"serde_with",
|
||||
"take_mut",
|
||||
"takecell",
|
||||
"thiserror",
|
||||
|
@ -1843,9 +1866,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "teloxide-macros"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f1d653b093dba5e44cada57a516f572167df37b8a619443e59c8c517bb6d804"
|
||||
checksum = "7e2d33d809c3e7161a9ab18bedddf98821245014f0a78fa4d2c9430b2ec018c1"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
|
|
|
@ -66,7 +66,8 @@ features = ["derive"]
|
|||
|
||||
[dependencies.diesel]
|
||||
version = "2.2.1"
|
||||
features = ["postgres", "chrono"]
|
||||
default-features = false
|
||||
features = ["postgres", "chrono", "with-deprecated"]
|
||||
optional = true
|
||||
|
||||
[dependencies.diesel_migrations]
|
||||
|
@ -74,7 +75,7 @@ version = "2.2.0"
|
|||
optional = true
|
||||
|
||||
[dependencies.teloxide]
|
||||
version = "0.12.2"
|
||||
version = "0.13.0"
|
||||
default-features = false
|
||||
features = ["native-tls", "macros"]
|
||||
optional = true
|
||||
|
@ -86,7 +87,6 @@ optional = true
|
|||
|
||||
[dependencies.chrono]
|
||||
version = "0.4.38"
|
||||
optional = true
|
||||
|
||||
[dependencies.parse_datetime]
|
||||
version = "0.6.0"
|
||||
|
@ -108,7 +108,6 @@ default = [
|
|||
interface_database = [
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"chrono",
|
||||
]
|
||||
interface_stratz = [
|
||||
"graphql_client"
|
||||
|
@ -118,7 +117,6 @@ service_telegram = [
|
|||
"teloxide",
|
||||
"rand",
|
||||
"parse_datetime",
|
||||
"chrono",
|
||||
]
|
||||
service_brooch = [
|
||||
"interface_database",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
DROP TABLE IF EXISTS matchmaking_messages_telegram;
|
||||
|
||||
DROP TABLE IF EXISTS matchmaking_replies;
|
||||
|
||||
DROP TABLE IF EXISTS matchmaking_events;
|
||||
|
||||
DROP TYPE IF EXISTS matchmaking_choice;
|
34
migrations/2024-08-14-091307_add_matchmaking_events/up.sql
Normal file
34
migrations/2024-08-14-091307_add_matchmaking_events/up.sql
Normal file
|
@ -0,0 +1,34 @@
|
|||
CREATE TYPE matchmaking_choice AS ENUM (
|
||||
'yes',
|
||||
'late',
|
||||
'maybe',
|
||||
'dontw',
|
||||
'cant',
|
||||
'wont'
|
||||
);
|
||||
|
||||
CREATE TABLE matchmaking_events (
|
||||
id INTEGER PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
|
||||
|
||||
text VARCHAR NOT NULL,
|
||||
|
||||
starts_at TIMESTAMP NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE matchmaking_replies (
|
||||
matchmaking_id INTEGER REFERENCES matchmaking_events(id) NOT NULL,
|
||||
user_id INTEGER REFERENCES users(id) NOT NULL,
|
||||
|
||||
choice matchmaking_choice NOT NULL,
|
||||
late_mins INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
PRIMARY KEY(matchmaking_id, user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE matchmaking_messages_telegram (
|
||||
matchmaking_id INTEGER REFERENCES matchmaking_events(id) NOT NULL,
|
||||
telegram_chat_id BIGINT NOT NULL,
|
||||
telegram_message_id INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY(matchmaking_id, telegram_chat_id, telegram_message_id)
|
||||
)
|
|
@ -11,7 +11,6 @@ pub mod interface_database {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
pub mod service_telegram {
|
||||
use micronfig::config;
|
||||
|
@ -40,14 +39,17 @@ pub mod brooch {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
pub struct ChatIdConversionHack(i64);
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
impl From<i64> for ChatIdConversionHack {
|
||||
fn from(value: i64) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
impl From<ChatIdConversionHack> for teloxide::types::ChatId {
|
||||
fn from(value: ChatIdConversionHack) -> Self {
|
||||
Self(value.0)
|
||||
|
|
|
@ -3,22 +3,26 @@ use crate::services::RoyalnetService;
|
|||
|
||||
pub(self) mod config;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
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: (),
|
||||
}
|
||||
|
||||
impl RoyalnetInstance {
|
||||
pub async fn new() -> Self {
|
||||
let service_telegram = Self::setup_telegram_service().await;
|
||||
let service_brooch = Self::setup_brooch_service();
|
||||
|
||||
Self {
|
||||
service_telegram,
|
||||
service_brooch,
|
||||
service_telegram: Self::setup_telegram_service().await,
|
||||
service_brooch: Self::setup_brooch_service().await,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,12 +54,16 @@ impl RoyalnetInstance {
|
|||
|
||||
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"))]
|
||||
|
@ -88,12 +96,12 @@ impl RoyalnetInstance {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "service_telegram"))]
|
||||
fn get_telegram_future(service: &mut crate::services::telegram::TelegramService) -> impl Future<Output = ()> + '_ {
|
||||
fn get_telegram_future(_service: &mut ()) -> impl Future<Output = ()> + '_ {
|
||||
async {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "service_brooch")]
|
||||
fn setup_brooch_service() -> crate::services::brooch::BroochService {
|
||||
async fn setup_brooch_service() -> crate::services::brooch::BroochService {
|
||||
log::debug!("Setting up Brooch service...");
|
||||
|
||||
crate::services::brooch::BroochService::new(
|
||||
|
@ -109,7 +117,7 @@ impl RoyalnetInstance {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "service_brooch"))]
|
||||
fn setup_brooch_service() -> () {
|
||||
async fn setup_brooch_service() -> () {
|
||||
log::warn!("Brooch service is not compiled in.");
|
||||
|
||||
()
|
||||
|
@ -121,7 +129,7 @@ impl RoyalnetInstance {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "service_brooch"))]
|
||||
fn get_brooch_future(service: &mut crate::services::brooch::BroochService) -> impl Future<Output = ()> + '_ {
|
||||
fn get_brooch_future(_service: &mut ()) -> impl Future<Output = ()> + '_ {
|
||||
async {}
|
||||
}
|
||||
}
|
5
src/interfaces/database/connect.rs
Normal file
5
src/interfaces/database/connect.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use diesel::{Connection, ConnectionResult, PgConnection};
|
||||
|
||||
pub fn connect(database_url: &str) -> ConnectionResult<PgConnection> {
|
||||
PgConnection::establish(database_url)
|
||||
}
|
59
src/interfaces/database/macros.rs
Normal file
59
src/interfaces/database/macros.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
#[macro_export]
|
||||
macro_rules! newtype_sql {
|
||||
($visibility: vis $newtype: ident: $sqltype: path as $rusttype: path) => {
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, diesel::FromSqlRow, diesel::AsExpression)]
|
||||
#[diesel(sql_type = $sqltype)]
|
||||
$visibility struct $newtype(pub $rusttype);
|
||||
|
||||
impl std::fmt::Display for $newtype {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$rusttype> for $newtype {
|
||||
fn from(value: $rusttype) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$newtype> for $rusttype {
|
||||
fn from(value: $newtype) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl diesel::serialize::ToSql<$sqltype, diesel::pg::Pg> for $newtype {
|
||||
fn to_sql<'a>(&'a self, out: &mut diesel::serialize::Output<'a, '_, diesel::pg::Pg>) -> diesel::serialize::Result {
|
||||
use diesel::serialize::ToSql;
|
||||
|
||||
ToSql::<$sqltype, diesel::pg::Pg>::to_sql(&self.0, out)
|
||||
}
|
||||
}
|
||||
|
||||
impl diesel::deserialize::FromSql<$sqltype, diesel::pg::Pg> for $newtype {
|
||||
fn from_sql(raw: diesel::pg::PgValue) -> diesel::deserialize::Result<Self> {
|
||||
use diesel::deserialize::FromSql;
|
||||
|
||||
FromSql::<$sqltype, diesel::pg::Pg>::from_sql(raw)
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for $newtype {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use anyhow::Context;
|
||||
|
||||
Ok(
|
||||
Self(
|
||||
s.parse::<$rusttype>()
|
||||
.context("Impossible convertire a newtype.")?
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
12
src/interfaces/database/migrations.rs
Normal file
12
src/interfaces/database/migrations.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use anyhow::anyhow;
|
||||
use diesel::migration::MigrationVersion;
|
||||
use diesel::PgConnection;
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||
|
||||
pub fn migrate(database: &mut PgConnection) -> AnyResult<Vec<MigrationVersion>> {
|
||||
database.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(|e| anyhow!("Failed to run pending migrations: {e:?}"))
|
||||
}
|
|
@ -1,19 +1,10 @@
|
|||
use anyhow::anyhow;
|
||||
use diesel::{Connection, ConnectionResult, PgConnection};
|
||||
use diesel::migration::MigrationVersion;
|
||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||
use crate::utils::result::AnyResult;
|
||||
|
||||
pub mod schema;
|
||||
pub mod models;
|
||||
pub mod query_prelude;
|
||||
|
||||
pub fn connect(database_url: &str) -> ConnectionResult<PgConnection> {
|
||||
PgConnection::establish(database_url)
|
||||
}
|
||||
mod migrations;
|
||||
mod macros;
|
||||
mod connect;
|
||||
|
||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||
|
||||
pub fn migrate(database: &mut PgConnection) -> AnyResult<Vec<MigrationVersion>> {
|
||||
database.run_pending_migrations(MIGRATIONS)
|
||||
.map_err(|e| anyhow!("Failed to run pending migrations: {e:?}"))
|
||||
}
|
||||
pub use connect::connect;
|
||||
pub use migrations::migrate;
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
use diesel::{Identifiable, Insertable, Queryable, Selectable, Associations};
|
||||
use diesel::pg::Pg;
|
||||
use super::schema::{users, telegram, discord, steam, brooch_match, diario};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct RoyalnetUser {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = telegram)]
|
||||
#[diesel(primary_key(telegram_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct TelegramUser {
|
||||
pub user_id: i32,
|
||||
pub telegram_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = discord)]
|
||||
#[diesel(primary_key(discord_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct DiscordUser {
|
||||
pub user_id: i32,
|
||||
pub discord_id: i64,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = steam)]
|
||||
#[diesel(primary_key(steam_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct SteamUser {
|
||||
pub user_id: i32,
|
||||
pub steam_id: i64,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "service_brooch")]
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = brooch_match)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct BroochMatch {
|
||||
pub id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Insertable)]
|
||||
#[diesel(table_name = diario)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct DiarioAddition {
|
||||
pub saver_id: Option<i32>,
|
||||
pub warning: Option<String>,
|
||||
pub quote: String,
|
||||
pub quoted_name: Option<String>,
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = diario)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct DiarioEntry {
|
||||
pub id: i32,
|
||||
pub saver_id: Option<i32>,
|
||||
pub saved_on: Option<chrono::NaiveDateTime>,
|
||||
pub quoted_id: Option<i32>,
|
||||
pub quoted_name: Option<String>,
|
||||
pub warning: Option<String>,
|
||||
pub quote: String,
|
||||
pub context: Option<String>,
|
||||
}
|
48
src/interfaces/database/models/brooch_match.rs
Normal file
48
src/interfaces/database/models/brooch_match.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use anyhow::Context;
|
||||
use diesel::{AsExpression, FromSqlRow, Identifiable, Insertable, PgConnection, Queryable, QueryDsl, RunQueryDsl, Selectable};
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::pg::{Pg, PgValue};
|
||||
use diesel::serialize::ToSql;
|
||||
use crate::newtype_sql;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
use super::super::schema::brooch_match;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = brooch_match)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct BroochMatch {
|
||||
pub id: DotaMatchId,
|
||||
}
|
||||
|
||||
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)
|
||||
.count()
|
||||
.execute(database)
|
||||
.context("Impossibile determinare se la partita è marcata come processata nel database RYG.")?
|
||||
.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()
|
||||
.get_result::<Self>(database)
|
||||
.context("Impossibile marcare la partita come processata nel database RYG.")
|
||||
}
|
||||
}
|
||||
|
||||
newtype_sql!(pub DotaMatchId: diesel::sql_types::Int8 as i64);
|
32
src/interfaces/database/models/diario.rs
Normal file
32
src/interfaces/database/models/diario.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use diesel::{Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::interfaces::database::models::users::RoyalnetUserId;
|
||||
use crate::newtype_sql;
|
||||
use super::super::schema::diario;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Insertable)]
|
||||
#[diesel(table_name = diario)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct DiarioAddition {
|
||||
pub saver_id: Option<RoyalnetUserId>,
|
||||
pub warning: Option<String>,
|
||||
pub quote: String,
|
||||
pub quoted_name: Option<String>,
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = diario)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct Diario {
|
||||
pub id: DiarioId,
|
||||
pub saver_id: Option<RoyalnetUserId>,
|
||||
pub saved_on: Option<chrono::NaiveDateTime>,
|
||||
pub quoted_id: Option<RoyalnetUserId>,
|
||||
pub quoted_name: Option<String>,
|
||||
pub warning: Option<String>,
|
||||
pub quote: String,
|
||||
pub context: Option<String>,
|
||||
}
|
||||
|
||||
newtype_sql!(pub DiarioId: diesel::sql_types::Int4 as i32);
|
17
src/interfaces/database/models/discord.rs
Normal file
17
src/interfaces/database/models/discord.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::newtype_sql;
|
||||
use super::super::schema::discord;
|
||||
use super::users::{RoyalnetUser, RoyalnetUserId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = discord)]
|
||||
#[diesel(primary_key(discord_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct DiscordUser {
|
||||
pub user_id: RoyalnetUserId,
|
||||
pub discord_id: DiscordUserId,
|
||||
}
|
||||
|
||||
newtype_sql!(pub DiscordUserId: diesel::sql_types::Int8 as i64);
|
47
src/interfaces/database/models/matchmaking_choice.rs
Normal file
47
src/interfaces/database/models/matchmaking_choice.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::io::Write;
|
||||
|
||||
use diesel::{AsExpression, FromSqlRow};
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::pg::{Pg, PgValue};
|
||||
use diesel::serialize::{IsNull, ToSql};
|
||||
|
||||
use super::super::schema::sql_types;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, FromSqlRow, AsExpression)]
|
||||
#[diesel(sql_type = sql_types::MatchmakingChoice)]
|
||||
pub enum MatchmakingChoice {
|
||||
Yes,
|
||||
Late,
|
||||
Maybe,
|
||||
DontWait,
|
||||
Cant,
|
||||
Wont,
|
||||
}
|
||||
|
||||
impl ToSql<sql_types::MatchmakingChoice, Pg> for MatchmakingChoice {
|
||||
fn to_sql(&self, out: &mut diesel::serialize::Output<Pg>) -> diesel::serialize::Result {
|
||||
match *self {
|
||||
Self::Yes => out.write_all(b"yes")?,
|
||||
Self::Late => out.write_all(b"late")?,
|
||||
Self::Maybe => out.write_all(b"maybe")?,
|
||||
Self::DontWait => out.write_all(b"dontw")?,
|
||||
Self::Cant => out.write_all(b"cant")?,
|
||||
Self::Wont => out.write_all(b"wont")?,
|
||||
};
|
||||
Ok(IsNull::No)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql<sql_types::MatchmakingChoice, Pg> for MatchmakingChoice {
|
||||
fn from_sql(raw: PgValue) -> diesel::deserialize::Result<Self> {
|
||||
match raw.as_bytes() {
|
||||
b"yes" => Ok(Self::Yes),
|
||||
b"late" => Ok(Self::Late),
|
||||
b"maybe" => Ok(Self::Maybe),
|
||||
b"dontw" => Ok(Self::DontWait),
|
||||
b"cant" => Ok(Self::Cant),
|
||||
b"wont" => Ok(Self::Wont),
|
||||
_ => Err("Unknown MatchmakingReply".into())
|
||||
}
|
||||
}
|
||||
}
|
54
src/interfaces/database/models/matchmaking_events.rs
Normal file
54
src/interfaces/database/models/matchmaking_events.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use anyhow::Context;
|
||||
use diesel::{AsExpression, FromSqlRow, Identifiable, Insertable, PgConnection, Queryable, QueryId, Selectable};
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::pg::{Pg, PgValue};
|
||||
use diesel::serialize::ToSql;
|
||||
use crate::newtype_sql;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
use super::super::schema::matchmaking_events;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = matchmaking_events)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct MatchmakingEvent {
|
||||
pub id: MatchmakingId,
|
||||
pub text: String,
|
||||
pub starts_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
impl MatchmakingEvent {
|
||||
/// Create a new [MatchmakingEvent].
|
||||
pub fn create(database: &mut PgConnection, text: &str, starts_at: &chrono::DateTime<chrono::Local>) -> AnyResult<Self> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
|
||||
insert_into(matchmaking_events::table)
|
||||
.values(&(
|
||||
matchmaking_events::text.eq(text),
|
||||
matchmaking_events::starts_at.eq(starts_at.naive_utc()),
|
||||
))
|
||||
.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> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
|
||||
matchmaking_events::table
|
||||
.filter(matchmaking_events::id.eq(matchmaking_id.0))
|
||||
.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())
|
||||
}
|
||||
}
|
||||
|
||||
newtype_sql!(pub MatchmakingId: diesel::sql_types::Int4 as i32);
|
||||
|
||||
impl MatchmakingId {
|
||||
pub fn callback_data(&self, data: &str) -> String {
|
||||
format!("matchmaking:{}:{}", &self.0, data)
|
||||
}
|
||||
}
|
355
src/interfaces/database/models/matchmaking_messages_telegram.rs
Normal file
355
src/interfaces/database/models/matchmaking_messages_telegram.rs
Normal file
|
@ -0,0 +1,355 @@
|
|||
use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::interfaces::database::models::MatchmakingId;
|
||||
use crate::interfaces::database::models::telegram::{TelegramChatId, TelegramMessageId};
|
||||
use super::matchmaking_events::MatchmakingEvent;
|
||||
use super::super::schema::matchmaking_messages_telegram;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(MatchmakingEvent, foreign_key = matchmaking_id))]
|
||||
#[diesel(table_name = matchmaking_messages_telegram)]
|
||||
#[diesel(primary_key(matchmaking_id, telegram_chat_id, telegram_message_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct MatchmakingMessageTelegram {
|
||||
pub matchmaking_id: MatchmakingId,
|
||||
pub telegram_chat_id: TelegramChatId,
|
||||
pub telegram_message_id: TelegramMessageId,
|
||||
}
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
pub(crate) mod telegram_ext {
|
||||
use std::cmp::Ordering;
|
||||
use std::str::FromStr;
|
||||
use anyhow::Context;
|
||||
use super::*;
|
||||
use diesel::PgConnection;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::payloads::EditMessageTextSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::ParseMode;
|
||||
use crate::interfaces::database::models::{MatchmakingChoice, MatchmakingId, MatchmakingReply, RoyalnetUser, TelegramUser};
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
use crate::utils::telegram_string::TelegramEscape;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MatchmakingTelegramKeyboardCallback {
|
||||
Yes,
|
||||
Plus5Min,
|
||||
Plus15Min,
|
||||
Plus60Min,
|
||||
Maybe,
|
||||
DontWait,
|
||||
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 {
|
||||
"yes" => Self::Yes,
|
||||
"5min" => Self::Plus5Min,
|
||||
"15min" => Self::Plus15Min,
|
||||
"60min" => Self::Plus60Min,
|
||||
"maybe" => Self::Maybe,
|
||||
"dontw" => Self::DontWait,
|
||||
"cant" => Self::Cant,
|
||||
"wont" => Self::Wont,
|
||||
x => anyhow::bail!("Unknown keyboard callback: {x:?}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MatchmakingTelegramKeyboardCallback> for &'static str {
|
||||
fn from(value: MatchmakingTelegramKeyboardCallback) -> Self {
|
||||
match value {
|
||||
MatchmakingTelegramKeyboardCallback::Yes => "yes",
|
||||
MatchmakingTelegramKeyboardCallback::Plus5Min => "5min",
|
||||
MatchmakingTelegramKeyboardCallback::Plus15Min => "15min",
|
||||
MatchmakingTelegramKeyboardCallback::Plus60Min => "60min",
|
||||
MatchmakingTelegramKeyboardCallback::Maybe => "maybe",
|
||||
MatchmakingTelegramKeyboardCallback::DontWait => "dontw",
|
||||
MatchmakingTelegramKeyboardCallback::Cant => "cant",
|
||||
MatchmakingTelegramKeyboardCallback::Wont => "wont",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
let button_60min = Plus60Min.inline_button(matchmaking_id, "🕛 +60 min");
|
||||
let button_maybe = Maybe.inline_button(matchmaking_id, "❔ Forse...");
|
||||
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],
|
||||
vec![button_maybe, button_dontw],
|
||||
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.format("%c").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 {
|
||||
i32::MIN..=5 => "🕐",
|
||||
6..=10 => "🕑",
|
||||
11..=15 => "🕒",
|
||||
16..=20 => "🕓",
|
||||
21..=25 => "🕔",
|
||||
26..=30 => "🕕",
|
||||
31..=35 => "🕖",
|
||||
36..=40 => "🕗",
|
||||
41..=45 => "🕘",
|
||||
46..=50 => "🕙",
|
||||
51..=55 => "🕚",
|
||||
56..=i32::MAX => "🕛",
|
||||
},
|
||||
Maybe => "❔",
|
||||
DontWait => "❓",
|
||||
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,
|
||||
bot: &teloxide::Bot,
|
||||
chat_id: teloxide::types::ChatId,
|
||||
reply_to: Option<teloxide::types::MessageId>,
|
||||
) -> 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)
|
||||
.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,
|
||||
reply: &teloxide::types::Message,
|
||||
)
|
||||
-> AnyResult<Self>
|
||||
{
|
||||
use diesel::prelude::*;
|
||||
use diesel::dsl::*;
|
||||
use crate::interfaces::database::schema::matchmaking_messages_telegram;
|
||||
|
||||
insert_into(matchmaking_messages_telegram::table)
|
||||
.values(&MatchmakingMessageTelegram {
|
||||
matchmaking_id,
|
||||
telegram_chat_id: reply.chat.id.into(),
|
||||
telegram_message_id: reply.id.into(),
|
||||
})
|
||||
.on_conflict_do_nothing()
|
||||
.get_result::<MatchmakingMessageTelegram>(database)
|
||||
.context("L'inserimento nel database RYG è fallito.")
|
||||
}
|
||||
|
||||
pub async fn send_new_and_create(
|
||||
database: &mut PgConnection,
|
||||
matchmaking_id: MatchmakingId,
|
||||
bot: &teloxide::Bot,
|
||||
chat_id: teloxide::types::ChatId,
|
||||
reply_to: Option<teloxide::types::MessageId>,
|
||||
)
|
||||
-> AnyResult<Self>
|
||||
{
|
||||
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,
|
||||
text: &str,
|
||||
with_keyboard: bool,
|
||||
)
|
||||
-> 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,
|
||||
bot: &teloxide::Bot,
|
||||
)
|
||||
-> AnyResult<()>
|
||||
{
|
||||
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,
|
||||
)
|
||||
-> AnyResult<teloxide::types::True>
|
||||
{
|
||||
bot
|
||||
.delete_message::<teloxide::types::ChatId>(self.telegram_chat_id.into(), self.telegram_message_id.into())
|
||||
.await
|
||||
.context("La richiesta di eliminazione messaggio alla Bot API di Telegram è fallita.")
|
||||
}
|
||||
|
||||
fn destroy(
|
||||
&self,
|
||||
database: &mut PgConnection,
|
||||
)
|
||||
-> AnyResult<usize>
|
||||
{
|
||||
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))
|
||||
.filter(matchmaking_messages_telegram::telegram_message_id.eq(self.telegram_message_id))
|
||||
.execute(database)
|
||||
.context("La rimozione dal database RYG è fallita.")
|
||||
}
|
||||
|
||||
pub async fn destroy_and_send_delete(
|
||||
self,
|
||||
database: &mut PgConnection,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
79
src/interfaces/database/models/matchmaking_replies.rs
Normal file
79
src/interfaces/database/models/matchmaking_replies.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::ops::Add;
|
||||
use anyhow::Context;
|
||||
use diesel::{Associations, Identifiable, Insertable, PgConnection, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::interfaces::database::models::{MatchmakingId, RoyalnetUserId, TelegramUser};
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
use super::matchmaking_choice::MatchmakingChoice;
|
||||
use super::matchmaking_events::MatchmakingEvent;
|
||||
use super::super::schema::matchmaking_replies;
|
||||
use super::users::RoyalnetUser;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(MatchmakingEvent, foreign_key = matchmaking_id))]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = matchmaking_replies)]
|
||||
#[diesel(primary_key(matchmaking_id, user_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct MatchmakingReply {
|
||||
pub matchmaking_id: MatchmakingId,
|
||||
pub user_id: RoyalnetUserId,
|
||||
pub choice: MatchmakingChoice,
|
||||
pub late_mins: i32,
|
||||
}
|
||||
|
||||
impl MatchmakingReply {
|
||||
pub fn get_all_telegram(database: &mut PgConnection, matchmaking_id: MatchmakingId) -> AnyResult<Vec<(Self, RoyalnetUser, TelegramUser)>> {
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use schema::{matchmaking_replies, users, telegram};
|
||||
|
||||
matchmaking_replies::table
|
||||
.filter(matchmaking_replies::matchmaking_id.eq(matchmaking_id))
|
||||
.inner_join(users::table.on(matchmaking_replies::user_id.eq(users::id)))
|
||||
.inner_join(telegram::table.on(users::id.eq(telegram::user_id)))
|
||||
.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 crate::interfaces::database::query_prelude::*;
|
||||
use schema::matchmaking_replies;
|
||||
|
||||
insert_into(matchmaking_replies::table)
|
||||
.values(&Self {
|
||||
matchmaking_id,
|
||||
user_id,
|
||||
choice,
|
||||
late_mins: 0,
|
||||
})
|
||||
.on_conflict(on_constraint("matchmaking_replies_pkey"))
|
||||
.do_update()
|
||||
.set((
|
||||
matchmaking_replies::choice.eq(choice),
|
||||
matchmaking_replies::late_mins.eq(0),
|
||||
))
|
||||
.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 crate::interfaces::database::query_prelude::*;
|
||||
use schema::matchmaking_replies;
|
||||
|
||||
insert_into(matchmaking_replies::table)
|
||||
.values(&Self {
|
||||
matchmaking_id,
|
||||
user_id,
|
||||
choice: MatchmakingChoice::Late,
|
||||
late_mins: increase_by,
|
||||
})
|
||||
.on_conflict(on_constraint("matchmaking_replies_pkey"))
|
||||
.do_update()
|
||||
.set((
|
||||
matchmaking_replies::choice.eq(MatchmakingChoice::Late),
|
||||
matchmaking_replies::late_mins.eq(matchmaking_replies::late_mins.add(increase_by)),
|
||||
))
|
||||
.get_result::<Self>(database)
|
||||
.context("Non è stato possibile aumentare il ritardo nella risposta nel database RYG.")
|
||||
}
|
||||
}
|
28
src/interfaces/database/models/mod.rs
Normal file
28
src/interfaces/database/models/mod.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::serialize::ToSql;
|
||||
|
||||
mod users;
|
||||
mod telegram;
|
||||
mod discord;
|
||||
mod steam;
|
||||
mod brooch_match;
|
||||
mod diario;
|
||||
mod matchmaking_events;
|
||||
mod matchmaking_replies;
|
||||
mod matchmaking_messages_telegram;
|
||||
mod matchmaking_choice;
|
||||
|
||||
pub use users::{RoyalnetUser, RoyalnetUserId};
|
||||
pub use telegram::{TelegramUser, TelegramChatId, TelegramMessageId, TelegramUserId};
|
||||
pub use discord::{DiscordUser, DiscordUserId};
|
||||
pub use steam::{SteamUser, SteamId64};
|
||||
pub use brooch_match::{BroochMatch, DotaMatchId};
|
||||
pub use diario::{Diario, DiarioId};
|
||||
pub use matchmaking_events::{MatchmakingEvent, MatchmakingId};
|
||||
pub use matchmaking_replies::MatchmakingReply;
|
||||
pub use matchmaking_messages_telegram::{MatchmakingMessageTelegram, telegram_ext::MatchmakingTelegramKeyboardCallback};
|
||||
pub use matchmaking_choice::MatchmakingChoice;
|
17
src/interfaces/database/models/steam.rs
Normal file
17
src/interfaces/database/models/steam.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::newtype_sql;
|
||||
use super::super::schema::steam;
|
||||
use super::users::{RoyalnetUser, RoyalnetUserId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = steam)]
|
||||
#[diesel(primary_key(steam_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct SteamUser {
|
||||
pub user_id: RoyalnetUserId,
|
||||
pub steam_id: SteamId64,
|
||||
}
|
||||
|
||||
newtype_sql!(pub SteamId64: diesel::sql_types::Int8 as i64);
|
62
src/interfaces/database/models/telegram.rs
Normal file
62
src/interfaces/database/models/telegram.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use diesel::{Associations, Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::pg::Pg;
|
||||
use crate::newtype_sql;
|
||||
use super::super::schema::telegram;
|
||||
use super::users::{RoyalnetUser, RoyalnetUserId};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable, Associations)]
|
||||
#[diesel(belongs_to(RoyalnetUser, foreign_key = user_id))]
|
||||
#[diesel(table_name = telegram)]
|
||||
#[diesel(primary_key(telegram_id))]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct TelegramUser {
|
||||
pub user_id: RoyalnetUserId,
|
||||
pub telegram_id: TelegramUserId,
|
||||
}
|
||||
|
||||
newtype_sql!(pub TelegramUserId: diesel::sql_types::Int8 as i64);
|
||||
newtype_sql!(pub TelegramChatId: diesel::sql_types::Int8 as i64);
|
||||
newtype_sql!(pub TelegramMessageId: diesel::sql_types::Int4 as i32);
|
||||
|
||||
#[cfg(feature = "service_telegram")]
|
||||
mod telegram_ext {
|
||||
use super::*;
|
||||
|
||||
impl From<teloxide::types::ChatId> for TelegramChatId {
|
||||
fn from(value: teloxide::types::ChatId) -> Self {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
16
src/interfaces/database/models/users.rs
Normal file
16
src/interfaces/database/models/users.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use diesel::{Identifiable, Insertable, Queryable, Selectable};
|
||||
use diesel::deserialize::FromSql;
|
||||
use diesel::pg::Pg;
|
||||
use diesel::serialize::ToSql;
|
||||
use crate::newtype_sql;
|
||||
use super::super::schema::users;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||
#[diesel(table_name = users)]
|
||||
#[diesel(check_for_backend(Pg))]
|
||||
pub struct RoyalnetUser {
|
||||
pub id: RoyalnetUserId,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
newtype_sql!(pub RoyalnetUserId: diesel::sql_types::Int4 as i32);
|
10
src/interfaces/database/query_prelude.rs
Normal file
10
src/interfaces/database/query_prelude.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
pub use diesel::dsl::*;
|
||||
pub use diesel::query_dsl::*;
|
||||
pub use diesel::upsert::*;
|
||||
pub use diesel::OptionalExtension;
|
||||
pub use diesel::ExpressionMethods;
|
||||
pub use diesel::SelectableHelper;
|
||||
pub use super::schema;
|
||||
pub use super::models;
|
|
@ -1,5 +1,11 @@
|
|||
// @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;
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
brooch_match (id) {
|
||||
id -> Int8,
|
||||
|
@ -26,6 +32,34 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
matchmaking_events (id) {
|
||||
id -> Int4,
|
||||
text -> Varchar,
|
||||
starts_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
matchmaking_messages_telegram (matchmaking_id, telegram_chat_id, telegram_message_id) {
|
||||
matchmaking_id -> Int4,
|
||||
telegram_chat_id -> Int8,
|
||||
telegram_message_id -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::MatchmakingChoice;
|
||||
|
||||
matchmaking_replies (matchmaking_id, user_id) {
|
||||
matchmaking_id -> Int4,
|
||||
user_id -> Int4,
|
||||
choice -> MatchmakingChoice,
|
||||
late_mins -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
steam (steam_id) {
|
||||
user_id -> Int4,
|
||||
|
@ -48,6 +82,9 @@ diesel::table! {
|
|||
}
|
||||
|
||||
diesel::joinable!(discord -> users (user_id));
|
||||
diesel::joinable!(matchmaking_messages_telegram -> matchmaking_events (matchmaking_id));
|
||||
diesel::joinable!(matchmaking_replies -> matchmaking_events (matchmaking_id));
|
||||
diesel::joinable!(matchmaking_replies -> users (user_id));
|
||||
diesel::joinable!(steam -> users (user_id));
|
||||
diesel::joinable!(telegram -> users (user_id));
|
||||
|
||||
|
@ -55,6 +92,9 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
brooch_match,
|
||||
diario,
|
||||
discord,
|
||||
matchmaking_events,
|
||||
matchmaking_messages_telegram,
|
||||
matchmaking_replies,
|
||||
steam,
|
||||
telegram,
|
||||
users,
|
||||
|
|
|
@ -14,6 +14,7 @@ async fn main() {
|
|||
// Create instance
|
||||
let instance = RoyalnetInstance::new().await;
|
||||
|
||||
log::trace!("Starting {instance:?}!");
|
||||
instance.run().await;
|
||||
|
||||
log::error!("No services configured.");
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
use std::cmp::PartialEq;
|
||||
use std::time::Duration;
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, TimeDelta, TimeZone, Utc};
|
||||
use diesel::PgConnection;
|
||||
use chrono::{DateTime, Local, TimeDelta, TimeZone};
|
||||
use diesel::{PgConnection};
|
||||
use reqwest::Url;
|
||||
use teloxide::prelude::*;
|
||||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{ChatId, LinkPreviewOptions, Message};
|
||||
use tokio::time::sleep;
|
||||
use crate::interfaces::database;
|
||||
use crate::interfaces::database::models::{BroochMatch};
|
||||
use crate::interfaces::database::models::{BroochMatch, DotaMatchId, TelegramUserId};
|
||||
use crate::services::RoyalnetService;
|
||||
use crate::utils::result::AnyResult;
|
||||
use crate::interfaces::stratz::{Byte, guild_matches, Long, Short};
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
use crate::interfaces::stratz::{Byte, guild_matches, Short};
|
||||
use crate::interfaces::stratz::guild_matches::{GameMode, Lane, LobbyType, Match, Player, Role, Steam};
|
||||
use crate::utils::telegramdisplay::TelegramEscape;
|
||||
use crate::utils::telegram_string::TelegramEscape;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BroochService {
|
||||
|
@ -27,7 +30,18 @@ pub struct BroochService {
|
|||
|
||||
impl BroochService {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(database_url: String, graphql_base_url: &str, stratz_token: &str, watched_guild_id: i64, min_players_to_process: usize, telegram_bot_token: String, notification_chat_id: ChatId, max_imp_wait: TimeDelta) -> AnyResult<Self> {
|
||||
pub fn new(
|
||||
database_url: String,
|
||||
graphql_base_url: &str,
|
||||
stratz_token: &str,
|
||||
watched_guild_id: i64,
|
||||
min_players_to_process: usize,
|
||||
telegram_bot_token: String,
|
||||
notification_chat_id: ChatId,
|
||||
max_imp_wait: TimeDelta
|
||||
)
|
||||
-> AnyResult<Self>
|
||||
{
|
||||
log::info!("Initializing a new Brooch service...");
|
||||
|
||||
let mut graphql_url = Url::parse(graphql_base_url)
|
||||
|
@ -134,58 +148,54 @@ impl BroochService {
|
|||
Ok(matches)
|
||||
}
|
||||
|
||||
fn get_match_id(&self, r#match: &Match) -> AnyResult<Long> {
|
||||
fn get_match_id(&self, r#match: &Match) -> AnyResult<DotaMatchId> {
|
||||
log::trace!("Getting match id...");
|
||||
|
||||
r#match.id
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")
|
||||
}
|
||||
|
||||
fn get_database_match(&self, database: &mut PgConnection, match_id: Long) -> AnyResult<Option<BroochMatch>> {
|
||||
log::trace!("Getting match {match_id} from the database...");
|
||||
|
||||
let match_royalnet = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use crate::interfaces::database::schema::brooch_match::dsl::*;
|
||||
|
||||
brooch_match
|
||||
.filter(id.eq(match_id))
|
||||
.select(BroochMatch::as_select())
|
||||
.get_result(database)
|
||||
.optional()
|
||||
.context("Non è stato possibile recuperare la partita restituita da STRATZ dal database RYG.")?
|
||||
};
|
||||
|
||||
Ok(match_royalnet)
|
||||
}
|
||||
|
||||
fn should_process_match_exists(&self, database: &mut PgConnection, match_id: Long) -> AnyResult<bool> {
|
||||
log::trace!("Determining whether {match_id} should be processed...");
|
||||
|
||||
Ok(
|
||||
self.get_database_match(database, match_id)?
|
||||
.is_none()
|
||||
r#match.id
|
||||
.context("La richiesta è riuscita, ma non è stato ricevuto da STRATZ l'ID della partita.")?
|
||||
.into()
|
||||
)
|
||||
}
|
||||
|
||||
fn get_match_datetime(&self, r#match: &Match) -> AnyResult<DateTime<Utc>> {
|
||||
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 DateTime<Utc> object...");
|
||||
log::trace!("Converting match datetime to local datetime...");
|
||||
|
||||
Utc.timestamp_opt(match_date, 0)
|
||||
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<Utc>) -> TimeDelta {
|
||||
fn get_match_timedelta(&self, datetime: &DateTime<Local>) -> TimeDelta {
|
||||
log::trace!("Getting current time...");
|
||||
|
||||
let now = Utc::now();
|
||||
let now = Local::now();
|
||||
|
||||
log::trace!("Getting match timedelta...");
|
||||
|
||||
|
@ -421,7 +431,7 @@ impl BroochService {
|
|||
.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<i64>> {
|
||||
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, users, telegram};
|
||||
|
@ -461,7 +471,7 @@ impl BroochService {
|
|||
steam::steam_id.eq(player_steam_id_y1)
|
||||
)
|
||||
.select(TelegramUser::as_select())
|
||||
.get_result(database)
|
||||
.get_result::<TelegramUser>(database)
|
||||
.optional()
|
||||
.context("Non è stato possibile connettersi al database RYG.")?
|
||||
.map(|t| t.telegram_id)
|
||||
|
@ -608,9 +618,10 @@ impl BroochService {
|
|||
hero_name.escape_telegram_html(),
|
||||
)),
|
||||
Some(telegram_id) => lines.push(format!(
|
||||
"<u><a href=\"tg://user?id={telegram_id}\"><b>{}</b></a> ({})</u>",
|
||||
name.escape_telegram_html(),
|
||||
hero_name.escape_telegram_html(),
|
||||
"<u><a href=\"tg://user?id={}\"><b>{}</b></a> ({})</u>",
|
||||
telegram_id.to_string().escape_telegram_html(),
|
||||
name.to_string().escape_telegram_html(),
|
||||
hero_name.to_string().escape_telegram_html(),
|
||||
)),
|
||||
}
|
||||
|
||||
|
@ -641,7 +652,7 @@ impl BroochService {
|
|||
Ok(lines.join("\n"))
|
||||
}
|
||||
|
||||
fn stringify_match(&self, database: &mut PgConnection, r#match: Match) -> AnyResult<(Long, Option<String>)> {
|
||||
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)?;
|
||||
|
@ -699,39 +710,30 @@ impl BroochService {
|
|||
}
|
||||
|
||||
lines.push(format!(
|
||||
"Partita <code>{match_id}</code> · <a href=\"https://stratz.com/matches/{match_id}\">Apri su STRATZ</a>"
|
||||
"Partita <code>{}</code>",
|
||||
match_id,
|
||||
));
|
||||
|
||||
Ok((match_id, Some(lines.join("\n"))))
|
||||
}
|
||||
|
||||
async fn send_notification(&self, text: &str) -> AnyResult<Message> {
|
||||
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)
|
||||
.disable_web_page_preview(true)
|
||||
.link_preview_options(LinkPreviewOptions {
|
||||
is_disabled: false,
|
||||
url: Some(format!("https://stratz.com/matches/{}", match_id)),
|
||||
prefer_small_media: true,
|
||||
prefer_large_media: false,
|
||||
show_above_text: false,
|
||||
})
|
||||
.await
|
||||
.context("Impossibile inviare la notifica di una partita.")
|
||||
}
|
||||
|
||||
fn flag_match_id(&self, database: &mut PgConnection, match_id: Long) -> AnyResult<BroochMatch> {
|
||||
use diesel::prelude::*;
|
||||
use crate::interfaces::database::schema::brooch_match::dsl::*;
|
||||
use crate::interfaces::database::models::{BroochMatch};
|
||||
|
||||
log::debug!("Flagging as parsed match id: {match_id}");
|
||||
|
||||
let match_royalnet = BroochMatch { id: match_id };
|
||||
|
||||
diesel::insert_into(brooch_match)
|
||||
.values(&match_royalnet)
|
||||
.returning(BroochMatch::as_returning())
|
||||
.get_result(database)
|
||||
.context("Impossibile marcare la partita come processata nel database RYG.")
|
||||
}
|
||||
|
||||
async fn iteration(&self) -> AnyResult<()> {
|
||||
log::debug!("Now running an iteration of brooch!");
|
||||
|
||||
|
@ -746,14 +748,14 @@ impl BroochService {
|
|||
let results = matches
|
||||
.into_iter()
|
||||
.map(|r#match| self.stringify_match(&mut database, r#match))
|
||||
.collect::<Vec<AnyResult<(Long, Option<String>)>>>();
|
||||
.collect::<Vec<AnyResult<(DotaMatchId, Option<String>)>>>();
|
||||
|
||||
for result in results {
|
||||
let (match_id, message) = result?;
|
||||
|
||||
if let Some(message) = message {
|
||||
self.send_notification(&message).await?;
|
||||
self.flag_match_id(&mut database, match_id)?;
|
||||
self.send_notification(match_id, &message).await?;
|
||||
BroochMatch::flag(&mut database, match_id)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use crate::utils::result::AnyResult;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
|
||||
pub trait RoyalnetService {
|
||||
async fn run(&mut self) -> AnyResult<()>;
|
||||
|
|
|
@ -4,6 +4,7 @@ use rand::seq::SliceRandom;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::prelude::{Message, Requester};
|
||||
use teloxide::types::ReplyParameters;
|
||||
use crate::services::telegram::commands::{CommandResult};
|
||||
|
||||
// Cerchiamo di tenere bilanciate le tre colonne, o almeno le prime due.
|
||||
|
@ -84,7 +85,7 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, answer.to_string())
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use reqwest::Url;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendPhotoSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{InputFile, Message};
|
||||
use teloxide::types::{InputFile, Message, ReplyParameters};
|
||||
use serde::Deserialize;
|
||||
use super::{CommandResult};
|
||||
|
||||
|
@ -34,7 +34,7 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
|
||||
let _reply = bot
|
||||
.send_photo(message.chat.id, input)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare un gatto in risposta a questo messaggio.")?;
|
||||
|
||||
|
|
|
@ -6,11 +6,12 @@ use regex::Regex;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::prelude::Requester;
|
||||
use teloxide::types::{Message, ParseMode};
|
||||
use crate::interfaces::database::models::{DiarioAddition, DiarioEntry, RoyalnetUser};
|
||||
use teloxide::types::{Message, ParseMode, ReplyParameters};
|
||||
use crate::interfaces::database::models::Diario;
|
||||
use crate::interfaces::database::models::RoyalnetUser;
|
||||
use crate::services::telegram::commands::CommandResult;
|
||||
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||
use crate::utils::telegramdisplay::{TelegramEscape, TelegramWrite};
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
use crate::utils::telegram_string::{TelegramEscape, TelegramWrite};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiarioArgs {
|
||||
|
@ -60,7 +61,7 @@ impl FromStr for DiarioArgs {
|
|||
}
|
||||
}
|
||||
|
||||
impl TelegramWrite for DiarioEntry {
|
||||
impl TelegramWrite for Diario {
|
||||
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
|
||||
where T: Write
|
||||
{
|
||||
|
@ -96,8 +97,8 @@ impl TelegramWrite for DiarioEntry {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, args: DiarioArgs, database: &DatabaseInterface) -> CommandResult {
|
||||
let author = message.from()
|
||||
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()?;
|
||||
|
@ -120,22 +121,19 @@ pub async fn handler(bot: &Bot, message: &Message, args: DiarioArgs, database: &
|
|||
.context("Non è stato possibile recuperare il tuo utente Telegram dal database RYG.")?
|
||||
};
|
||||
|
||||
let addition = DiarioAddition {
|
||||
saver_id: Some(royalnet_user.id),
|
||||
warning: args.warning,
|
||||
quote: args.quote,
|
||||
quoted_name: args.quoted,
|
||||
context: args.context,
|
||||
};
|
||||
|
||||
let entry = {
|
||||
use diesel::prelude::*;
|
||||
use diesel::dsl::*;
|
||||
use crate::interfaces::database::schema::diario::dsl::*;
|
||||
use crate::interfaces::database::query_prelude::*;
|
||||
use schema::diario;
|
||||
|
||||
insert_into(diario)
|
||||
.values(&addition)
|
||||
.get_result::<DiarioEntry>(&mut database)
|
||||
insert_into(diario::table)
|
||||
.values(&(
|
||||
diario::saver_id.eq(Some(royalnet_user.id)),
|
||||
diario::warning.eq(args.warning.clone()),
|
||||
diario::quote.eq(args.quote.clone()),
|
||||
diario::quoted_name.eq(args.quoted.clone()),
|
||||
diario::context.eq(args.context.clone()),
|
||||
))
|
||||
.get_result::<Diario>(&mut database)
|
||||
.context("Non è stato possibile aggiungere la riga di diario al database RYG.")?
|
||||
};
|
||||
|
||||
|
@ -149,7 +147,7 @@ pub async fn handler(bot: &Bot, message: &Message, args: DiarioArgs, database: &
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
// teloxide does not support blockquotes yet and errors out on parsing the response
|
||||
// .context("Non è stato possibile inviare la risposta.")?
|
||||
|
|
|
@ -3,7 +3,7 @@ use reqwest::Url;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendPhotoSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{InputFile, Message};
|
||||
use teloxide::types::{InputFile, Message, ReplyParameters};
|
||||
use serde::Deserialize;
|
||||
use super::{CommandResult};
|
||||
|
||||
|
@ -30,7 +30,7 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
|
||||
let _reply = bot
|
||||
.send_photo(message.chat.id, input)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare un cane in risposta a questo messaggio.")?;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Context;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{Message};
|
||||
use teloxide::types::{Message, ReplyParameters};
|
||||
use super::{CommandResult};
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, text: &str) -> CommandResult {
|
||||
|
@ -12,7 +12,7 @@ pub async fn handler(bot: &Bot, message: &Message, text: &str) -> CommandResult
|
|||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ use rand::seq::SliceRandom;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::prelude::{Message, Requester};
|
||||
use teloxide::types::ReplyParameters;
|
||||
use crate::services::telegram::commands::{CommandResult};
|
||||
|
||||
// Tutte le fortune devono essere positive, o almeno neutrali, per poter essere aggiunte.
|
||||
|
@ -194,7 +195,7 @@ impl Hash for FortuneKey {
|
|||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let today = chrono::Local::now().date_naive();
|
||||
|
||||
let author = message.from()
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
let author_id = author.id;
|
||||
|
||||
|
@ -221,7 +222,7 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, fortune.to_string())
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::Context;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{BotCommand, Message, ParseMode};
|
||||
use teloxide::types::{BotCommand, Message, ParseMode, ReplyParameters};
|
||||
use teloxide::utils::command::BotCommands;
|
||||
use super::{CommandResult};
|
||||
|
||||
|
@ -14,7 +14,7 @@ pub async fn handler_all(bot: &Bot, message: &Message) -> CommandResult {
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
@ -68,7 +68,7 @@ pub async fn handler_specific(bot: &Bot, message: &Message, target: &str) -> Com
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
69
src/services/telegram/commands/matchmaking.rs
Normal file
69
src/services/telegram/commands/matchmaking.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::str::FromStr;
|
||||
use anyhow::Context;
|
||||
use once_cell::sync::Lazy;
|
||||
use parse_datetime::parse_datetime_at_date;
|
||||
use regex::Regex;
|
||||
use teloxide::Bot;
|
||||
use teloxide::prelude::Message;
|
||||
use crate::interfaces::database::models::MatchmakingEvent;
|
||||
use crate::interfaces::database::models::MatchmakingMessageTelegram;
|
||||
use crate::services::telegram::commands::CommandResult;
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
use crate::utils::time::sleep_chrono;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchmakingArgs {
|
||||
start: chrono::DateTime<chrono::Local>,
|
||||
text: String,
|
||||
}
|
||||
|
||||
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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
|
@ -2,25 +2,30 @@
|
|||
// https://github.com/teloxide/teloxide/blob/master/crates/teloxide/examples/dispatching_features.rs
|
||||
|
||||
use std::sync::Arc;
|
||||
use anyhow::{Context, Error, Result};
|
||||
use anyhow::{Context, Error};
|
||||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{ChatId, Message, MessageId};
|
||||
use teloxide::types::{Message, ReplyParameters};
|
||||
use teloxide::utils::command::BotCommands;
|
||||
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
|
||||
mod start;
|
||||
mod fortune;
|
||||
mod echo;
|
||||
mod help;
|
||||
mod whoami;
|
||||
mod answer;
|
||||
mod reminder;
|
||||
mod dog;
|
||||
mod cat;
|
||||
mod roll;
|
||||
mod diario;
|
||||
pub mod start;
|
||||
pub mod fortune;
|
||||
pub mod echo;
|
||||
pub mod help;
|
||||
pub mod whoami;
|
||||
pub mod answer;
|
||||
pub mod reminder;
|
||||
pub mod dog;
|
||||
pub mod cat;
|
||||
pub mod roll;
|
||||
pub mod diario;
|
||||
pub mod matchmaking;
|
||||
|
||||
|
||||
type CommandResult = AnyResult<()>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BotCommands)]
|
||||
#[command(rename_rule = "lowercase")]
|
||||
|
@ -47,87 +52,123 @@ pub enum Command {
|
|||
Roll(String),
|
||||
#[command(description = "Salva una citazione nel diario RYG.")]
|
||||
Diario(diario::DiarioArgs),
|
||||
#[command(description = "Chiedi chi è disponibile per giocare a qualcosa.")]
|
||||
Matchmaking(matchmaking::MatchmakingArgs),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub async fn set_commands(bot: &mut Bot) -> Result<()> {
|
||||
/// 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();
|
||||
|
||||
// This always returns true, for whatever reason
|
||||
log::trace!("Setting commands: {commands:#?}");
|
||||
let _ = bot.set_my_commands(commands).await
|
||||
.context("Impossibile aggiornare l'elenco comandi del bot.")?;
|
||||
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, bot: Bot, message: Message, database: Arc<DatabaseInterface>) -> CommandResult {
|
||||
log::trace!("Handling command: {self:?}");
|
||||
pub async fn handle_self(self, bot: Bot, message: Message, database: Arc<DatabaseInterface>) -> CommandResult {
|
||||
log::debug!("Handling command...");
|
||||
|
||||
let result = match self {
|
||||
Command::Start => start::handler(&bot, &message).await,
|
||||
Command::Help(target) => match target.as_str() {
|
||||
"" => help::handler_all(&bot, &message).await,
|
||||
_ => help::handler_specific(&bot, &message, &target).await,
|
||||
},
|
||||
Command::Fortune => fortune::handler(&bot, &message).await,
|
||||
Command::Echo(text) => echo::handler(&bot, &message, &text).await,
|
||||
Command::WhoAmI => whoami::handler(&bot, &message, &database).await,
|
||||
Command::Answer(_) => answer::handler(&bot, &message).await,
|
||||
Command::Reminder(args) => reminder::handler(&bot, &message, args).await,
|
||||
Command::Dog => dog::handler(&bot, &message).await,
|
||||
Command::Cat => cat::handler(&bot, &message).await,
|
||||
Command::Roll(roll) => roll::handler(&bot, &message, &roll).await,
|
||||
Command::Diario(args) => diario::handler(&bot, &message, args, &database).await,
|
||||
};
|
||||
log::trace!(
|
||||
"Handling {:?} in {:?} with {:?}...",
|
||||
self,
|
||||
&message.chat.id,
|
||||
&message.id,
|
||||
);
|
||||
|
||||
if result.is_ok() {
|
||||
return Ok(())
|
||||
}
|
||||
// FIXME: Quick hack to fix single thread
|
||||
log::trace!("Spawning task for future...");
|
||||
let _task = tokio::spawn(async move {
|
||||
log::trace!("Delegating command handling to handler...");
|
||||
let result1 = match self {
|
||||
Command::Start => start::handler(&bot, &message).await,
|
||||
Command::Help(ref target) => match target.as_str() {
|
||||
"" => help::handler_all(&bot, &message).await,
|
||||
_ => help::handler_specific(&bot, &message, target).await,
|
||||
},
|
||||
Command::Fortune => fortune::handler(&bot, &message).await,
|
||||
Command::Echo(ref text) => echo::handler(&bot, &message, text).await,
|
||||
Command::WhoAmI => whoami::handler(&bot, &message, &database).await,
|
||||
Command::Answer(_) => answer::handler(&bot, &message).await,
|
||||
Command::Reminder(ref args) => reminder::handler(&bot, &message, args).await,
|
||||
Command::Dog => dog::handler(&bot, &message).await,
|
||||
Command::Cat => cat::handler(&bot, &message).await,
|
||||
Command::Roll(ref roll) => roll::handler(&bot, &message, roll).await,
|
||||
Command::Diario(ref args) => diario::handler(&bot, &message, args, &database).await,
|
||||
Command::Matchmaking(ref args) => matchmaking::handler(&bot, &message, args, &database).await,
|
||||
};
|
||||
|
||||
let chat_id = message.chat.id;
|
||||
let message_id = message.id;
|
||||
let error = result.unwrap_err();
|
||||
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 result2 = error_command(&bot, chat_id, message_id, &error).await;
|
||||
let e1 = result1.unwrap_err();
|
||||
|
||||
if result2.is_ok() {
|
||||
return Ok(())
|
||||
}
|
||||
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
|
||||
};
|
||||
|
||||
let error2 = result2.unwrap_err();
|
||||
log::trace!("Successfully handled command!");
|
||||
});
|
||||
|
||||
log::error!("Command message {message_id:?} in {chat_id:?} errored out with `{error}`, and it was impossible to handle the error because of `{error2}`\n\n{error2:?}");
|
||||
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());
|
||||
|
||||
log::trace!("Sending error message...");
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, "⚠️ Comando sconosciuto o sintassi non valida.")
|
||||
.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 `{:?}`",
|
||||
&message.chat.id,
|
||||
&message.id,
|
||||
self,
|
||||
error,
|
||||
);
|
||||
|
||||
log::trace!("Sending error message...");
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, format!("⚠️ {error}"))
|
||||
.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 `{:?}`",
|
||||
&message.chat.id,
|
||||
&message.id,
|
||||
self,
|
||||
error1,
|
||||
error2,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn error_command(bot: &Bot, chat_id: ChatId, message_id: MessageId, error: &Error) -> CommandResult {
|
||||
log::debug!("Command message {message_id:?} in {chat_id:?} errored out with `{error}`");
|
||||
|
||||
let text = format!("⚠️ {error}");
|
||||
|
||||
let _reply = bot
|
||||
.send_message(chat_id, text)
|
||||
.reply_to_message_id(message_id)
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unknown_command(bot: Bot, message: Message) -> CommandResult {
|
||||
log::debug!("Received an unknown command.");
|
||||
|
||||
bot.send_message(message.chat.id, "⚠️ Comando sconosciuto o sintassi non valida.")
|
||||
.reply_to_message_id(message.id)
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
type CommandResult = Result<()>;
|
|
@ -3,23 +3,15 @@ use anyhow::Context;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{Message, ParseMode};
|
||||
use teloxide::types::{Message, ParseMode, ReplyParameters};
|
||||
use parse_datetime::parse_datetime_at_date;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use crate::utils::telegramdisplay::TelegramEscape;
|
||||
use super::{CommandResult};
|
||||
use crate::utils::telegram_string::TelegramEscape;
|
||||
use crate::utils::time::sleep_chrono;
|
||||
use super::CommandResult;
|
||||
|
||||
|
||||
fn determine_wait(target_chrono: chrono::DateTime<chrono::Local>) -> tokio::time::Duration {
|
||||
let now_chrono = chrono::Local::now();
|
||||
|
||||
let duration_chrono = target_chrono.signed_duration_since(now_chrono);
|
||||
let seconds = duration_chrono.num_seconds();
|
||||
|
||||
tokio::time::Duration::from_secs(seconds as u64)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ReminderArgs {
|
||||
target: chrono::DateTime<chrono::Local>,
|
||||
|
@ -54,7 +46,7 @@ impl FromStr for ReminderArgs {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, reminder}: ReminderArgs) -> CommandResult {
|
||||
pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, reminder }: &ReminderArgs) -> CommandResult {
|
||||
let text = format!(
|
||||
"🕒 <b>Promemoria impostato</b>\n\
|
||||
<i>{}</i>\n\
|
||||
|
@ -67,13 +59,11 @@ pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, remind
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la conferma.")?;
|
||||
.context("Non è stato possibile inviare la conferma del promemoria.")?;
|
||||
|
||||
let wait_duration = determine_wait(target);
|
||||
|
||||
tokio::time::sleep(wait_duration).await;
|
||||
sleep_chrono(target).await;
|
||||
|
||||
let text = format!(
|
||||
"🕒 <b>Promemoria attivato</b>\n\
|
||||
|
@ -87,7 +77,7 @@ pub async fn handler(bot: &Bot, message: &Message, ReminderArgs { target, remind
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare il promemoria.")?;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use teloxide::payloads::SendMessageSetters;
|
|||
use teloxide::prelude::{Message, Requester};
|
||||
use crate::services::telegram::commands::{CommandResult};
|
||||
use regex::Regex;
|
||||
|
||||
use teloxide::types::ReplyParameters;
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult {
|
||||
let mut rng = rand::rngs::SmallRng::from_entropy();
|
||||
|
@ -13,14 +13,14 @@ pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult
|
|||
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_to_message_id(message.id)
|
||||
.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();
|
||||
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.")?;
|
||||
|
@ -79,7 +79,7 @@ pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult
|
|||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, answer)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ use anyhow::Context;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{Message};
|
||||
use teloxide::types::{Message, ReplyParameters};
|
||||
use super::{CommandResult};
|
||||
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
||||
let author = message.from()
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
|
||||
let author_username = match author.username.as_ref() {
|
||||
|
@ -38,7 +38,7 @@ pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
|||
|
||||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -2,14 +2,14 @@ use anyhow::Context;
|
|||
use teloxide::Bot;
|
||||
use teloxide::payloads::SendMessageSetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::{Message, ParseMode};
|
||||
use crate::interfaces::database::models::{RoyalnetUser};
|
||||
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||
use crate::utils::telegramdisplay::TelegramEscape;
|
||||
use super::{CommandResult};
|
||||
use teloxide::types::{Message, ParseMode, ReplyParameters};
|
||||
use crate::interfaces::database::models::RoyalnetUser;
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
use crate::utils::telegram_string::TelegramEscape;
|
||||
use super::CommandResult;
|
||||
|
||||
pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult {
|
||||
let author = message.from()
|
||||
let author = message.from.as_ref()
|
||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||
|
||||
let mut database = database.connect()?;
|
||||
|
@ -19,7 +19,6 @@ pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface)
|
|||
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>(
|
||||
|
@ -42,7 +41,7 @@ pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface)
|
|||
let _reply = bot
|
||||
.send_message(message.chat.id, text)
|
||||
.parse_mode(ParseMode::Html)
|
||||
.reply_to_message_id(message.id)
|
||||
.reply_parameters(ReplyParameters::new(message.id))
|
||||
.await
|
||||
.context("Non è stato possibile inviare la risposta.")?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Context;
|
||||
use diesel::PgConnection;
|
||||
use crate::utils::result::AnyResult;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DatabaseInterface {
|
||||
|
@ -14,6 +14,6 @@ impl DatabaseInterface {
|
|||
|
||||
pub fn connect(&self) -> AnyResult<PgConnection> {
|
||||
crate::interfaces::database::connect(&self.database_url)
|
||||
.context("Impossibile connettersi al database RYG")
|
||||
.context("Impossibile connettersi al database RYG.")
|
||||
}
|
||||
}
|
45
src/services/telegram/keyboard_callbacks/matchmaking.rs
Normal file
45
src/services/telegram/keyboard_callbacks/matchmaking.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use anyhow::Context;
|
||||
use teloxide::Bot;
|
||||
use teloxide::payloads::AnswerCallbackQuerySetters;
|
||||
use teloxide::requests::Requester;
|
||||
use teloxide::types::CallbackQuery;
|
||||
use crate::interfaces::database::models::{MatchmakingChoice, MatchmakingId, MatchmakingMessageTelegram, MatchmakingReply, MatchmakingTelegramKeyboardCallback, RoyalnetUser};
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
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)?,
|
||||
Plus15Min => MatchmakingReply::add_late_minutes(&mut database, matchmaking_id, royalnet_user.id, 15)?,
|
||||
Plus60Min => MatchmakingReply::add_late_minutes(&mut database, matchmaking_id, royalnet_user.id, 60)?,
|
||||
Maybe => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::Maybe)?,
|
||||
DontWait => MatchmakingReply::set(&mut database, matchmaking_id, royalnet_user.id, MatchmakingChoice::DontWait)?,
|
||||
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(())
|
||||
}
|
80
src/services/telegram/keyboard_callbacks/mod.rs
Normal file
80
src/services/telegram/keyboard_callbacks/mod.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
mod matchmaking;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use anyhow::Context;
|
||||
use teloxide::Bot;
|
||||
use teloxide::payloads::AnswerCallbackQuerySetters;
|
||||
use teloxide::prelude::CallbackQuery;
|
||||
use teloxide::requests::Requester;
|
||||
use crate::interfaces::database::models::{MatchmakingId, MatchmakingTelegramKeyboardCallback};
|
||||
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum KeyboardCallback {
|
||||
Matchmaking(MatchmakingId, MatchmakingTelegramKeyboardCallback),
|
||||
}
|
||||
|
||||
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:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
type KeyboardCallbackResult = anyhow::Result<()>;
|
|
@ -1,3 +1,8 @@
|
|||
mod commands;
|
||||
mod dependencies;
|
||||
mod keyboard_callbacks;
|
||||
mod utils;
|
||||
|
||||
use std::sync::Arc;
|
||||
use anyhow::Context;
|
||||
use teloxide::prelude::*;
|
||||
|
@ -5,15 +10,13 @@ use teloxide::types::{Me, ParseMode};
|
|||
use regex::Regex;
|
||||
use teloxide::dispatching::DefaultKey;
|
||||
use teloxide::dptree::entry;
|
||||
use crate::services::telegram::commands::Command;
|
||||
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||
use crate::utils::result::{AnyError, AnyResult};
|
||||
use crate::utils::telegramdisplay::TelegramEscape;
|
||||
use commands::Command;
|
||||
use dependencies::interface_database::DatabaseInterface;
|
||||
use keyboard_callbacks::KeyboardCallback;
|
||||
use crate::utils::anyhow_result::{AnyError, AnyResult};
|
||||
use crate::utils::telegram_string::TelegramEscape;
|
||||
use super::RoyalnetService;
|
||||
|
||||
mod commands;
|
||||
mod deps;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TelegramService {
|
||||
database_url: String,
|
||||
|
@ -110,33 +113,51 @@ impl TelegramService {
|
|||
log::trace!("Building dispatcher...");
|
||||
Dispatcher::builder(
|
||||
self.bot.clone(),
|
||||
// Only process message updates
|
||||
Update::filter_message()
|
||||
// Pseudo-commands
|
||||
.branch(entry()
|
||||
// Only process commands matching the pseudo-command regex
|
||||
.filter(move |message: Message| -> bool {
|
||||
message
|
||||
.text()
|
||||
.is_some_and(|text| regex.is_match(text))
|
||||
})
|
||||
// Commands
|
||||
.branch(
|
||||
entry()
|
||||
// When an update is received
|
||||
entry()
|
||||
// Messages
|
||||
.branch(Update::filter_message()
|
||||
// Pseudo-commands
|
||||
.branch(entry()
|
||||
// Only process commands matching the pseudo-command regex
|
||||
.filter(move |message: Message| -> bool {
|
||||
message
|
||||
.text()
|
||||
.is_some_and(|text| regex.is_match(text))
|
||||
})
|
||||
// Commands
|
||||
.branch(entry()
|
||||
// Only process commands matching a valid command, and parse their arguments
|
||||
.filter_command::<Command>()
|
||||
// Delegate handling
|
||||
.endpoint(Command::handle)
|
||||
.endpoint(Command::handle_self)
|
||||
)
|
||||
// No valid command was found
|
||||
.endpoint(Command::handle_unknown)
|
||||
)
|
||||
// No valid command was found
|
||||
.endpoint(commands::unknown_command)
|
||||
)
|
||||
)
|
||||
// Inline keyboard
|
||||
.branch(Update::filter_callback_query()
|
||||
// Known callbacks
|
||||
.branch(entry()
|
||||
// Only process queries that match
|
||||
.filter_map(move |query: CallbackQuery| query.data
|
||||
// Parse the data string as a KeyboardCallback
|
||||
.and_then(|data| data.parse::<KeyboardCallback>().ok())
|
||||
)
|
||||
.endpoint(KeyboardCallback::handle_self)
|
||||
)
|
||||
.endpoint(KeyboardCallback::handle_unknown)
|
||||
)
|
||||
)
|
||||
.dependencies(
|
||||
dptree::deps![
|
||||
database
|
||||
]
|
||||
)
|
||||
.default_handler(|upd| async move {
|
||||
log::trace!("Unhandled update: {:?}", upd);
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
|
|
24
src/services/telegram/utils/database.rs
Normal file
24
src/services/telegram/utils/database.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use anyhow::Context;
|
||||
use diesel::PgConnection;
|
||||
use teloxide::types::UserId;
|
||||
use crate::interfaces::database::models::RoyalnetUser;
|
||||
use crate::utils::anyhow_result::AnyResult;
|
||||
|
||||
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()
|
||||
.context("Lo user_id specificato non può essere interpretato come un numero signed, il che lo rende incompatibile con il database RYG.")?
|
||||
))
|
||||
.inner_join(users::table)
|
||||
.select(RoyalnetUser::as_select())
|
||||
.get_result(database)
|
||||
.context("Non è stato possibile recuperare l'utente Telegram specificato dal database RYG.")
|
||||
}
|
||||
}
|
1
src/services/telegram/utils/mod.rs
Normal file
1
src/services/telegram/utils/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod database;
|
|
@ -1,4 +1,4 @@
|
|||
pub mod time;
|
||||
pub mod version;
|
||||
pub mod result;
|
||||
pub mod telegramdisplay;
|
||||
pub mod anyhow_result;
|
||||
pub mod telegram_string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt::Write;
|
||||
use std::fmt::{Error, Write};
|
||||
|
||||
pub trait TelegramWrite {
|
||||
fn write_telegram<T>(&self, f: &mut T) -> Result<(), std::fmt::Error>
|
||||
fn write_telegram<T>(&self, f: &mut T) -> Result<(), Error>
|
||||
where T: Write;
|
||||
|
||||
fn to_string_telegram(&self) -> String {
|
||||
|
@ -16,7 +16,7 @@ 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)
|
||||
|
@ -24,4 +24,4 @@ impl<T> TelegramEscape for T
|
|||
.replace(">", ">")
|
||||
.replace("&", "&")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
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)
|
||||
)
|
||||
}
|
||||
|
||||
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