mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-22 11:04:21 +00:00
Massive amount of changes
This commit is contained in:
parent
8afbb1c421
commit
f8c77ef264
35 changed files with 1276 additions and 769 deletions
|
@ -5,7 +5,7 @@
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
<option name="channel" value="DEFAULT" />
|
<option name="channel" value="DEFAULT" />
|
||||||
<option name="requiredFeatures" value="true" />
|
<option name="requiredFeatures" value="false" />
|
||||||
<option name="allFeatures" value="false" />
|
<option name="allFeatures" value="false" />
|
||||||
<option name="withSudo" value="false" />
|
<option name="withSudo" value="false" />
|
||||||
<option name="buildTarget" value="REMOTE" />
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Run (debug)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
|
||||||
<option name="command" value="run --package royalnet --bin royalnet" />
|
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
|
||||||
<envs>
|
|
||||||
<env name="RUST_LOG" value="royalnet" />
|
|
||||||
</envs>
|
|
||||||
<option name="emulateTerminal" value="true" />
|
|
||||||
<option name="channel" value="DEFAULT" />
|
|
||||||
<option name="requiredFeatures" value="true" />
|
|
||||||
<option name="allFeatures" value="false" />
|
|
||||||
<option name="withSudo" value="false" />
|
|
||||||
<option name="buildTarget" value="REMOTE" />
|
|
||||||
<option name="backtrace" value="SHORT" />
|
|
||||||
<option name="isRedirectInput" value="false" />
|
|
||||||
<option name="redirectInputPath" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
28
Cargo.lock
generated
28
Cargo.lock
generated
|
@ -137,9 +137,9 @@ checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.3"
|
version = "1.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18e2d530f35b40a84124146478cd16f34225306a8441998836466a2e2961c950"
|
checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -1423,7 +1423,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "royalnet"
|
name = "royalnet"
|
||||||
version = "0.3.2"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1543,9 +1543,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.0"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
|
@ -1556,9 +1556,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework-sys"
|
name = "security-framework-sys"
|
||||||
version = "2.11.0"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
|
checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1625,15 +1625,6 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "signal-hook-registry"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1877,9 +1868,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.38.0"
|
version = "1.38.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
|
checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1887,7 +1878,6 @@ dependencies = [
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
|
112
Cargo.toml
112
Cargo.toml
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "royalnet"
|
name = "royalnet"
|
||||||
description = "Fun software suite for the RYG community"
|
description = "Fun software suite for the RYG community"
|
||||||
version = "0.3.2"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"Stefano Pigozzi <me@steffo.eu>"
|
"Stefano Pigozzi <me@steffo.eu>"
|
||||||
|
@ -26,23 +26,101 @@ exclude = [
|
||||||
"/.env"
|
"/.env"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
#============#
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.86"
|
|
||||||
chrono = "0.4.38"
|
[dependencies.anyhow]
|
||||||
diesel = { version = "2.2.1", features = ["postgres"] }
|
version = "1.0.86"
|
||||||
log = { version = "0.4.22", features = ["release_max_level_debug"] }
|
|
||||||
micronfig = "0.3.0"
|
[dependencies.thiserror]
|
||||||
pretty_env_logger = "0.5.0"
|
version = "1.0.62"
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
|
||||||
teloxide = { version = "0.12.2", features = ["ctrlc_handler", "native-tls", "macros"], default-features = false }
|
[dependencies.tokio]
|
||||||
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "time"] }
|
version = "1.38.0"
|
||||||
parse_datetime = "0.6.0"
|
features = ["macros", "rt-multi-thread", "time"]
|
||||||
regex = "1.10.5"
|
|
||||||
once_cell = "1.19.0"
|
[dependencies.log]
|
||||||
reqwest = { version = "0.12.5", features = ["json"] }
|
version = "0.4.22"
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
features = ["release_max_level_debug"]
|
||||||
graphql_client = "0.14.0"
|
|
||||||
thiserror = "1.0.62"
|
[dependencies.pretty_env_logger]
|
||||||
|
version = "0.5.0"
|
||||||
|
|
||||||
|
[dependencies.micronfig]
|
||||||
|
version = "0.3.0"
|
||||||
|
|
||||||
|
[dependencies.once_cell]
|
||||||
|
version = "1.19.0"
|
||||||
|
|
||||||
|
[dependencies.regex]
|
||||||
|
version = "1.10.5"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.12.5"
|
||||||
|
features = ["json"]
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0.204"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
|
[dependencies.diesel]
|
||||||
|
version = "2.2.1"
|
||||||
|
features = ["postgres"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.teloxide]
|
||||||
|
version = "0.12.2"
|
||||||
|
default-features = false
|
||||||
|
features = ["native-tls", "macros"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.rand]
|
||||||
|
version = "0.8.5"
|
||||||
|
features = ["small_rng"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.chrono]
|
||||||
|
version = "0.4.38"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.parse_datetime]
|
||||||
|
version = "0.6.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.graphql_client]
|
||||||
|
version = "0.14.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
#============#
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [
|
||||||
|
"interface_database",
|
||||||
|
"interface_stratz",
|
||||||
|
"service_brooch",
|
||||||
|
"service_telegram",
|
||||||
|
]
|
||||||
|
interface_database = [
|
||||||
|
"diesel"
|
||||||
|
]
|
||||||
|
interface_stratz = [
|
||||||
|
"graphql_client"
|
||||||
|
]
|
||||||
|
service_telegram = [
|
||||||
|
"interface_database",
|
||||||
|
"teloxide",
|
||||||
|
"rand",
|
||||||
|
"chrono",
|
||||||
|
"parse_datetime"
|
||||||
|
]
|
||||||
|
service_brooch = [
|
||||||
|
"interface_database",
|
||||||
|
"interface_stratz",
|
||||||
|
"graphql_client"
|
||||||
|
]
|
||||||
|
|
||||||
|
#============#
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "royalnet"
|
name = "royalnet"
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
use micronfig::config;
|
|
||||||
|
|
||||||
config! {
|
|
||||||
DATABASE_URL,
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
use diesel::{Connection, ConnectionResult, PgConnection};
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
pub mod schema;
|
|
||||||
pub mod models;
|
|
||||||
|
|
||||||
pub fn connect() -> ConnectionResult<PgConnection> {
|
|
||||||
PgConnection::establish(config::DATABASE_URL())
|
|
||||||
}
|
|
60
src/instance/config.rs
Normal file
60
src/instance/config.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#![allow(unused_attributes, unused_qualifications, clippy::needless_pub_self)]
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "service_telegram")]
|
||||||
|
pub mod service_telegram {
|
||||||
|
use micronfig::config;
|
||||||
|
|
||||||
|
config! {
|
||||||
|
TELEGRAM_DATABASE_URL: String,
|
||||||
|
TELEGRAM_BOT_TOKEN: String,
|
||||||
|
TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
|
pub mod brooch {
|
||||||
|
use micronfig::config;
|
||||||
|
|
||||||
|
#[allow(unused_qualifications)]
|
||||||
|
config! {
|
||||||
|
BROOCH_DATABASE_URL: String,
|
||||||
|
BROOCH_GRAPHQL_URL: String,
|
||||||
|
BROOCH_STRATZ_TOKEN: String,
|
||||||
|
BROOCH_TELEGRAM_BOT_TOKEN: String,
|
||||||
|
BROOCH_WATCHED_GUILD_ID: String > i64,
|
||||||
|
BROOCH_MIN_PLAYERS_TO_PROCESS: String > usize,
|
||||||
|
BROOCH_NOTIFICATION_CHAT_ID: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId,
|
||||||
|
BROOCH_MAX_IMP_WAIT_SECS: String > i64 -> crate::instance::config::TimeDeltaConversionHack => chrono::TimeDelta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChatIdConversionHack(i64);
|
||||||
|
|
||||||
|
impl From<i64> for ChatIdConversionHack {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChatIdConversionHack> for teloxide::types::ChatId {
|
||||||
|
fn from(value: ChatIdConversionHack) -> Self {
|
||||||
|
Self(value.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TimeDeltaConversionHack(i64);
|
||||||
|
|
||||||
|
impl From<i64> for TimeDeltaConversionHack {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<TimeDeltaConversionHack> for chrono::TimeDelta {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: TimeDeltaConversionHack) -> Result<Self, Self::Error> {
|
||||||
|
Self::new(value.0, 0).ok_or(())
|
||||||
|
}
|
||||||
|
}
|
102
src/instance/mod.rs
Normal file
102
src/instance/mod.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use std::future::Future;
|
||||||
|
use crate::services::RoyalnetService;
|
||||||
|
|
||||||
|
pub(self) mod config;
|
||||||
|
|
||||||
|
pub struct RoyalnetInstance {
|
||||||
|
#[cfg(feature = "service_telegram")]
|
||||||
|
service_telegram: crate::services::telegram::TelegramService,
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
|
service_brooch: crate::services::brooch::BroochService,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(mut self) {
|
||||||
|
let future_telegram = async move {
|
||||||
|
Self::get_telegram_future(&mut self.service_telegram).await;
|
||||||
|
};
|
||||||
|
let future_brooch = async move {
|
||||||
|
Self::get_brooch_future(&mut self.service_brooch).await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let task_telegram = tokio::spawn(future_telegram);
|
||||||
|
let task_brooch = tokio::spawn(future_brooch);
|
||||||
|
|
||||||
|
let _ = tokio::join!(
|
||||||
|
task_telegram,
|
||||||
|
task_brooch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_telegram")]
|
||||||
|
async fn setup_telegram_service() -> crate::services::telegram::TelegramService {
|
||||||
|
log::debug!("Setting up Telegram service...");
|
||||||
|
|
||||||
|
crate::services::telegram::TelegramService::new(
|
||||||
|
config::service_telegram::TELEGRAM_DATABASE_URL().clone(),
|
||||||
|
config::service_telegram::TELEGRAM_BOT_TOKEN().clone(),
|
||||||
|
config::service_telegram::TELEGRAM_NOTIFICATION_CHATID().clone(),
|
||||||
|
).await.expect("Unable to setup Telegram service.")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "service_telegram"))]
|
||||||
|
async fn setup_telegram_service() -> () {
|
||||||
|
log::warn!("Telegram service is disabled.");
|
||||||
|
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_telegram")]
|
||||||
|
fn get_telegram_future(service: &mut crate::services::telegram::TelegramService) -> impl Future<Output = ()> + '_ {
|
||||||
|
service.run_loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "service_telegram"))]
|
||||||
|
fn get_telegram_future(service: &mut crate::services::telegram::TelegramService) -> impl Future<Output = ()> + '_ {
|
||||||
|
async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
|
fn setup_brooch_service() -> crate::services::brooch::BroochService {
|
||||||
|
log::debug!("Setting up Brooch service...");
|
||||||
|
|
||||||
|
crate::services::brooch::BroochService::new(
|
||||||
|
config::brooch::BROOCH_DATABASE_URL().clone(),
|
||||||
|
config::brooch::BROOCH_GRAPHQL_URL(),
|
||||||
|
config::brooch::BROOCH_STRATZ_TOKEN(),
|
||||||
|
config::brooch::BROOCH_WATCHED_GUILD_ID().clone(),
|
||||||
|
config::brooch::BROOCH_MIN_PLAYERS_TO_PROCESS().clone(),
|
||||||
|
config::brooch::BROOCH_TELEGRAM_BOT_TOKEN().clone(),
|
||||||
|
config::brooch::BROOCH_NOTIFICATION_CHAT_ID().clone(),
|
||||||
|
config::brooch::BROOCH_MAX_IMP_WAIT_SECS().clone(),
|
||||||
|
).expect("Unable to setup Brooch service.")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "service_brooch"))]
|
||||||
|
fn setup_brooch_service() -> () {
|
||||||
|
log::warn!("Brooch service is disabled.");
|
||||||
|
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
|
fn get_brooch_future(service: &mut crate::services::brooch::BroochService) -> impl Future<Output = ()> + '_ {
|
||||||
|
service.run_loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "service_brooch"))]
|
||||||
|
fn get_brooch_future(service: &mut crate::services::brooch::BroochService) -> impl Future<Output = ()> + '_ {
|
||||||
|
async {}
|
||||||
|
}
|
||||||
|
}
|
8
src/interfaces/database/mod.rs
Normal file
8
src/interfaces/database/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use diesel::{Connection, ConnectionResult, PgConnection};
|
||||||
|
|
||||||
|
pub mod schema;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
|
pub fn connect(database_url: &str) -> ConnectionResult<PgConnection> {
|
||||||
|
PgConnection::establish(database_url)
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ pub struct SteamUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
#[derive(Debug, Clone, PartialEq, Identifiable, Queryable, Selectable, Insertable)]
|
||||||
#[diesel(table_name = brooch_match)]
|
#[diesel(table_name = brooch_match)]
|
||||||
#[diesel(check_for_backend(Pg))]
|
#[diesel(check_for_backend(Pg))]
|
|
@ -1,5 +1,6 @@
|
||||||
// @generated automatically by Diesel CLI.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
brooch_match (id) {
|
brooch_match (id) {
|
||||||
id -> Int8,
|
id -> Int8,
|
5
src/interfaces/mod.rs
Normal file
5
src/interfaces/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#[cfg(feature = "interface_database")]
|
||||||
|
pub mod database;
|
||||||
|
|
||||||
|
#[cfg(feature = "interface_stratz")]
|
||||||
|
pub mod stratz;
|
2
src/interfaces/stratz/graphql.config.yml
Normal file
2
src/interfaces/stratz/graphql.config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
schema: "schema.json"
|
||||||
|
documents: "**/*.gql"
|
54
src/interfaces/stratz/guild_matches.rs
Normal file
54
src/interfaces/stratz/guild_matches.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
use reqwest::Url;
|
||||||
|
pub use super::Short;
|
||||||
|
pub use super::Long;
|
||||||
|
pub use super::Byte;
|
||||||
|
pub use super::QueryError as Error;
|
||||||
|
|
||||||
|
#[derive(graphql_client::GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "src/interfaces/stratz/schema.json",
|
||||||
|
query_path = "src/interfaces/stratz/query_guild_matches.gql",
|
||||||
|
response_derives = "Debug, Clone"
|
||||||
|
)]
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
pub type QueryResponse = graphql_client::Response<query::ResponseData>;
|
||||||
|
pub type QueryResult = Result<QueryResponse, Error>;
|
||||||
|
|
||||||
|
pub use query::LobbyTypeEnum as LobbyType;
|
||||||
|
pub use query::GameModeEnumType as GameMode;
|
||||||
|
pub use query::MatchLaneType as Lane;
|
||||||
|
pub use query::MatchPlayerRoleType as Role;
|
||||||
|
pub use query::QueryGuild as Guild;
|
||||||
|
pub use query::QueryGuildMatches as Match;
|
||||||
|
pub use query::QueryGuildMatchesPlayers as Player;
|
||||||
|
pub use query::QueryGuildMatchesPlayersHero as Hero;
|
||||||
|
pub use query::QueryGuildMatchesPlayersSteamAccount as Steam;
|
||||||
|
pub use query::QueryGuildMatchesPlayersStatsMatchPlayerBuffEvent as Buff;
|
||||||
|
|
||||||
|
pub async fn query(client: &reqwest::Client, url: Url, guild_id: i64) -> QueryResult {
|
||||||
|
log::debug!("Querying guild_matches of guild {guild_id}...");
|
||||||
|
log::trace!("Using client: {client:?}");
|
||||||
|
log::trace!("Using API at: {url:?}");
|
||||||
|
|
||||||
|
log::trace!("Configuring query variables...");
|
||||||
|
let vars = query::Variables { guild_id };
|
||||||
|
|
||||||
|
log::trace!("Building query...");
|
||||||
|
let body = Query::build_query(vars);
|
||||||
|
|
||||||
|
log::trace!("Making request...");
|
||||||
|
let response = client.post(url)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::Requesting)?
|
||||||
|
.json::<QueryResponse>()
|
||||||
|
.await
|
||||||
|
.map_err(|_| Error::Parsing)?;
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
15
src/interfaces/stratz/mod.rs
Normal file
15
src/interfaces/stratz/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Short = i16;
|
||||||
|
pub type Long = i64;
|
||||||
|
pub type Byte = u8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum QueryError {
|
||||||
|
#[error("GraphQL request failed")]
|
||||||
|
Requesting,
|
||||||
|
#[error("GraphQL response parsing failed")]
|
||||||
|
Parsing,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod guild_matches;
|
|
@ -1,4 +1,4 @@
|
||||||
query GuildMatchesQuery($guild_id: Int!) {
|
query Query($guild_id: Int!) {
|
||||||
guild(id: $guild_id) {
|
guild(id: $guild_id) {
|
||||||
id
|
id
|
||||||
matches(take: 10) {
|
matches(take: 10) {
|
||||||
|
@ -7,9 +7,9 @@ query GuildMatchesQuery($guild_id: Int!) {
|
||||||
gameMode
|
gameMode
|
||||||
durationSeconds
|
durationSeconds
|
||||||
endDateTime
|
endDateTime
|
||||||
didRadiantWin
|
|
||||||
players(steamAccountId: null) {
|
players(steamAccountId: null) {
|
||||||
isRadiant
|
isRadiant
|
||||||
|
isVictory
|
||||||
imp
|
imp
|
||||||
kills
|
kills
|
||||||
deaths
|
deaths
|
39
src/main.rs
39
src/main.rs
|
@ -1,41 +1,20 @@
|
||||||
use anyhow::Result;
|
use crate::instance::RoyalnetInstance;
|
||||||
use crate::services::RoyalnetService;
|
|
||||||
|
|
||||||
pub(crate) mod database;
|
mod instance;
|
||||||
pub(crate) mod utils;
|
mod interfaces;
|
||||||
mod services;
|
mod services;
|
||||||
mod stratz;
|
pub(crate) mod utils;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() {
|
||||||
// Logging setup
|
// Logging setup
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
log::debug!("Logging initialized successfully!");
|
log::debug!("Logging initialized successfully!");
|
||||||
|
|
||||||
// Telegram setup
|
// Create instance
|
||||||
log::trace!("Setting up Telegram bot service...");
|
let instance = RoyalnetInstance::new().await;
|
||||||
let telegram = services::telegram::BotService::from_config();
|
|
||||||
|
|
||||||
// Brooch setup
|
instance.run().await;
|
||||||
log::trace!("Setting up Brooch service...");
|
|
||||||
let brooch = services::brooch::BroochService::from_config();
|
|
||||||
|
|
||||||
// Run all services concurrently
|
log::error!("No services configured.");
|
||||||
log::info!("Starting services...");
|
|
||||||
let result = tokio::try_join![
|
|
||||||
telegram.run(),
|
|
||||||
brooch.run(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// This should never happen, but just in case...
|
|
||||||
match result {
|
|
||||||
Err(error) => {
|
|
||||||
log::error!("A service has exited with an error, bailing out: {error:?}");
|
|
||||||
anyhow::bail!("A service has exited with an error.")
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
log::error!("All service have exited successfully, bailing out...");
|
|
||||||
anyhow::bail!("All service have exited successfully.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
use micronfig::config;
|
|
||||||
|
|
||||||
config! {
|
|
||||||
BROOCH_TELEGRAM_BOT_TOKEN,
|
|
||||||
BROOCH_WATCHED_GUILD_ID: String > i64 -> crate::stratz::GuildId,
|
|
||||||
BROOCH_NOTIFICATION_CHAT_ID: String > i64 -> crate::utils::hacks::ChatIdConversionHack -> teloxide::types::ChatId
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,40 @@
|
||||||
use std::convert::Infallible;
|
use std::time::Duration;
|
||||||
use anyhow::Result;
|
use tokio::time::sleep;
|
||||||
|
use crate::utils::result::AnyResult;
|
||||||
pub mod telegram;
|
|
||||||
pub mod brooch;
|
|
||||||
|
|
||||||
pub trait RoyalnetService {
|
pub trait RoyalnetService {
|
||||||
async fn run(self) -> Result<Infallible>;
|
async fn run(&mut self) -> AnyResult<()>;
|
||||||
|
|
||||||
|
async fn run_loop(&mut self) {
|
||||||
|
let mut backoff = Duration::new(1, 0);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = self.run().await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Service exited with error: {e:?}.")
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
log::debug!("Service exited successfully!")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let backoff_secs = backoff.as_secs();
|
||||||
|
|
||||||
|
log::debug!("Backing off for {backoff_secs} seconds before restarting...");
|
||||||
|
sleep(backoff).await;
|
||||||
|
|
||||||
|
log::trace!("Doubling backoff value...");
|
||||||
|
backoff *= 2;
|
||||||
|
|
||||||
|
log::trace!("Backoff value is now {backoff_secs} seconds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "service_telegram")]
|
||||||
|
pub mod telegram;
|
||||||
|
|
||||||
|
#[cfg(feature = "service_brooch")]
|
||||||
|
pub mod brooch;
|
||||||
|
|
|
@ -7,6 +7,7 @@ use teloxide::payloads::SendMessageSetters;
|
||||||
use teloxide::requests::Requester;
|
use teloxide::requests::Requester;
|
||||||
use teloxide::types::{ChatId, Message, MessageId};
|
use teloxide::types::{ChatId, Message, MessageId};
|
||||||
use teloxide::utils::command::BotCommands;
|
use teloxide::utils::command::BotCommands;
|
||||||
|
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||||
|
|
||||||
mod start;
|
mod start;
|
||||||
mod fortune;
|
mod fortune;
|
||||||
|
@ -58,7 +59,7 @@ impl Command {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle(self, bot: Bot, message: Message) -> CommandResult {
|
pub async fn handle(self, bot: Bot, message: Message, database: &DatabaseInterface) -> CommandResult {
|
||||||
log::trace!("Handling command: {self:?}");
|
log::trace!("Handling command: {self:?}");
|
||||||
|
|
||||||
let result = match self {
|
let result = match self {
|
||||||
|
@ -69,7 +70,7 @@ impl Command {
|
||||||
},
|
},
|
||||||
Command::Fortune => fortune::handler(&bot, &message).await,
|
Command::Fortune => fortune::handler(&bot, &message).await,
|
||||||
Command::Echo(text) => echo::handler(&bot, &message, &text).await,
|
Command::Echo(text) => echo::handler(&bot, &message, &text).await,
|
||||||
Command::WhoAmI => whoami::handler(&bot, &message).await,
|
Command::WhoAmI => whoami::handler(&bot, &message, &database).await,
|
||||||
Command::Answer(_) => answer::handler(&bot, &message).await,
|
Command::Answer(_) => answer::handler(&bot, &message).await,
|
||||||
Command::Reminder(args) => reminder::handler(&bot, &message, args).await,
|
Command::Reminder(args) => reminder::handler(&bot, &message, args).await,
|
||||||
Command::Dog => dog::handler(&bot, &message).await,
|
Command::Dog => dog::handler(&bot, &message).await,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use teloxide::types::{Message, ParseMode};
|
||||||
use parse_datetime::parse_datetime_at_date;
|
use parse_datetime::parse_datetime_at_date;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use crate::services::telegram::escape::EscapableInTelegramHTML;
|
use crate::utils::escape::EscapableInTelegramHTML;
|
||||||
use super::{CommandResult};
|
use super::{CommandResult};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,39 +7,43 @@ use crate::services::telegram::commands::{CommandResult};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult {
|
pub async fn handler(bot: &Bot, message: &Message, roll: &str) -> CommandResult {
|
||||||
let mut rng = rand::rngs::SmallRng::from_entropy();
|
let mut rng = rand::rngs::SmallRng::from_entropy();
|
||||||
|
|
||||||
if rng.gen_range(1..1001) == 1 {
|
if rng.gen_range(1..1001) == 1 {
|
||||||
let _reply = bot
|
let _reply = bot
|
||||||
.send_message(message.chat.id, "🎶 Roll? Rick roll! https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
.send_message(message.chat.id, "🎶 Roll? Rick roll! https://www.youtube.com/watch?v=dQw4w9WgXcQ")
|
||||||
.reply_to_message_id(message.id)
|
.reply_to_message_id(message.id)
|
||||||
.await
|
.await
|
||||||
.context("Non è stato possibile inviare la risposta.")?;
|
.context("Non è stato possibile inviare la risposta.")?;
|
||||||
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]?[0-9]*)?"#).unwrap();
|
let re = Regex::new(r#"(?P<qty>[0-9]*)?d(?P<die>[0-9]+)(?P<modifier>[+-]?[0-9]*)?"#).unwrap();
|
||||||
let qty = captures.name("qty") // Prova a vedere se c'è il gruppo "qty"
|
|
||||||
.map(|m| m.as_str()) // `map`: se c'è, trasforma il suo contenuto in stringa
|
|
||||||
.map(|m| m.parse::<u32>()) // `map`: se c'è, trasforma la stringa in un u32
|
|
||||||
.map(|m| m.context("La quantità di dadi da lanciare deve essere un numero intero positivo diverso da 0.")?) // `map`: se c'è, ma il parsing ha dato errore, restituiscilo e fai terminare la funzione qui
|
|
||||||
.unwrap_or(1); // `unwrap_or`: se c'è, restituisci il valore, altrimenti, defaulta a 1
|
|
||||||
|
|
||||||
let die = captures.name("die") // Prova a vedere se c'è il gruppo "die"
|
let captures = re.captures(roll)
|
||||||
.unwrap() // `unwrap`: possiamo asserire che il gruppo "die" sia sempre presente se la regex ha matchato
|
.context("Sintassi dei dadi non corretta.")?;
|
||||||
.as_str() // trasforma il suo contenuto in stringa
|
|
||||||
.parse::<u32>() // trasforma la stringa in un u32
|
|
||||||
.context("La dimensione del dado da lanciare deve essere un numero intero positivo.")?; // se il parsing ha dato errore, restituiscilo e fai terminare la funzione qui
|
|
||||||
|
|
||||||
let modifier = captures.name("modifier") // Prova a vedere se c'è il gruppo "modifier"
|
let qty = captures.name("qty")
|
||||||
.map(|m| m.as_str()) // `map`: se c'è, trasforma il suo contenuto in stringa
|
.map(|m| m.as_str())
|
||||||
.map(|m| m.parse::<i32>()) // `map`: se c'è, trasforma la stringa in un i32
|
.map(|m| m.parse::<u32>())
|
||||||
.map(|m| m.context("Il modificatore dei dadi lanciati deve essere un numero intero.")?) // `map`: se c'è, ma il parsing ha dato errore, restituiscilo e fai terminare la funzione qui
|
.unwrap_or(Ok(1))
|
||||||
.unwrap_or(0); // `unwrap_or`: se c'è, restituisci il valore, altrimenti, defaulta a 0
|
.context("La quantità di dadi da lanciare deve essere un numero intero positivo diverso da 0.")?;
|
||||||
|
|
||||||
if die <= 0 {
|
let die = captures.name("die")
|
||||||
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
|
.parse::<u32>()
|
||||||
|
.context("La dimensione del dado da lanciare deve essere un numero intero positivo.")?;
|
||||||
|
|
||||||
|
let modifier = captures.name("modifier")
|
||||||
|
.map(|m| m.as_str())
|
||||||
|
.map(|m| m.parse::<i32>())
|
||||||
|
.unwrap_or(Ok(0))
|
||||||
|
.context("Il modificatore dei dadi lanciati deve essere un numero intero.")?;
|
||||||
|
|
||||||
|
if die == 0 {
|
||||||
anyhow::bail!("Non è stato specificato nessun dado.")
|
anyhow::bail!("Non è stato specificato nessun dado.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,36 +51,31 @@ let qty = captures.name("qty") // Prova a vedere se c'è il gruppo "qty"
|
||||||
anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.")
|
anyhow::bail!("La quantità di dadi specificata deve essere un intero positivo.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut nums_rolled = Vec::<u32>::new();
|
||||||
|
|
||||||
let mut nums_rolled = Vec::<i32>::new();
|
|
||||||
for _ in 0..qty {
|
for _ in 0..qty {
|
||||||
nums_rolled.push(rng.gen_range(1..die+1));
|
nums_rolled.push(
|
||||||
|
rng.gen_range(1..=die)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roll_string = nums_rolled
|
||||||
|
.iter()
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
let mut answer = String::from("🎲 [");
|
let mut answer = format!("🎲 [{roll_string}]");
|
||||||
for i in 0..qty {
|
|
||||||
if i > 0 { answer.push_str("+")}
|
|
||||||
answer.push_str( &nums_rolled[i].to_string() );
|
|
||||||
}
|
|
||||||
answer.push_str("] ");
|
|
||||||
|
|
||||||
if modifier != 0 {
|
if modifier != 0 {
|
||||||
if modifier > 0 {
|
answer.push_str(&format!("{modifier:+}"))
|
||||||
answer.push_str("+");
|
|
||||||
}
|
|
||||||
answer.push_str( &modifier.to_string() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
answer.push_str(" = ");
|
answer.push_str(" = ");
|
||||||
|
|
||||||
let mut sum: i32 = nums_rolled.iter().sum();
|
let sum: u32 = nums_rolled.iter().sum();
|
||||||
sum = sum + modifier;
|
let sum: i32 = sum as i32 + modifier;
|
||||||
|
|
||||||
|
|
||||||
answer.push_str( &sum.to_string() );
|
|
||||||
|
|
||||||
|
answer.push_str(&sum.to_string());
|
||||||
|
|
||||||
let _reply = bot
|
let _reply = bot
|
||||||
.send_message(message.chat.id, answer)
|
.send_message(message.chat.id, answer)
|
||||||
|
|
|
@ -3,23 +3,23 @@ use teloxide::Bot;
|
||||||
use teloxide::payloads::SendMessageSetters;
|
use teloxide::payloads::SendMessageSetters;
|
||||||
use teloxide::requests::Requester;
|
use teloxide::requests::Requester;
|
||||||
use teloxide::types::{Message, ParseMode};
|
use teloxide::types::{Message, ParseMode};
|
||||||
use crate::database::models::{RoyalnetUser};
|
use crate::interfaces::database::models::{RoyalnetUser};
|
||||||
use crate::services::telegram::escape::EscapableInTelegramHTML;
|
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||||
|
use crate::utils::escape::EscapableInTelegramHTML;
|
||||||
use super::{CommandResult};
|
use super::{CommandResult};
|
||||||
|
|
||||||
pub async fn handler(bot: &Bot, message: &Message) -> CommandResult {
|
pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult {
|
||||||
let author = message.from()
|
let author = message.from()
|
||||||
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
.context("Non è stato possibile determinare chi ha inviato questo comando.")?;
|
||||||
|
|
||||||
let mut database = crate::database::connect().
|
let mut database = database.connect()?;
|
||||||
context("Non è stato possibile connettersi al database RYG.")?;
|
|
||||||
|
|
||||||
let royalnet_user: RoyalnetUser = {
|
let royalnet_user: RoyalnetUser = {
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::{ExpressionMethods, QueryDsl};
|
use diesel::{ExpressionMethods, QueryDsl};
|
||||||
use crate::database::schema::telegram::dsl::*;
|
use crate::interfaces::database::schema::telegram::dsl::*;
|
||||||
use crate::database::schema::users::dsl::*;
|
use crate::interfaces::database::schema::users::dsl::*;
|
||||||
use crate::database::models::RoyalnetUser;
|
use crate::interfaces::database::models::RoyalnetUser;
|
||||||
|
|
||||||
telegram
|
telegram
|
||||||
.filter(telegram_id.eq::<i64>(
|
.filter(telegram_id.eq::<i64>(
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
use micronfig::config;
|
|
||||||
|
|
||||||
// Everything ok, RustRover?
|
|
||||||
config! {
|
|
||||||
TELEGRAM_BOT_TOKEN,
|
|
||||||
TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> crate::utils::hacks::ChatIdConversionHack -> teloxide::types::ChatId,
|
|
||||||
}
|
|
19
src/services/telegram/deps/interface_database.rs
Normal file
19
src/services/telegram/deps/interface_database.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use diesel::PgConnection;
|
||||||
|
use crate::utils::result::AnyResult;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DatabaseInterface {
|
||||||
|
database_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatabaseInterface {
|
||||||
|
pub fn new(database_url: String) -> Self {
|
||||||
|
Self { database_url }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect(&self) -> AnyResult<PgConnection> {
|
||||||
|
crate::interfaces::database::connect(&self.database_url)
|
||||||
|
.context("Impossibile connettersi al database RYG")
|
||||||
|
}
|
||||||
|
}
|
2
src/services/telegram/deps/mod.rs
Normal file
2
src/services/telegram/deps/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#[cfg(feature = "interface_database")]
|
||||||
|
pub mod interface_database;
|
|
@ -1,113 +1,164 @@
|
||||||
use std::convert::Infallible;
|
use anyhow::Context;
|
||||||
use teloxide::{Bot, dptree};
|
use teloxide::prelude::*;
|
||||||
use anyhow::{Context, Error, Result};
|
use teloxide::types::{Me, ParseMode};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use teloxide::dispatching::{DefaultKey, Dispatcher, HandlerExt, UpdateFilterExt};
|
use teloxide::dispatching::DefaultKey;
|
||||||
use teloxide::dptree::entry;
|
use teloxide::dptree::entry;
|
||||||
use teloxide::payloads::SendMessageSetters;
|
use crate::services::telegram::commands::Command;
|
||||||
use teloxide::requests::Requester;
|
use crate::services::telegram::deps::interface_database::DatabaseInterface;
|
||||||
use teloxide::types::{Me, Message, ParseMode, Update};
|
use crate::utils::result::{AnyError, AnyResult};
|
||||||
use crate::services::telegram::escape::EscapableInTelegramHTML;
|
use crate::utils::escape::EscapableInTelegramHTML;
|
||||||
use super::RoyalnetService;
|
use super::RoyalnetService;
|
||||||
|
|
||||||
#[allow(clippy::needless_pub_self)]
|
|
||||||
pub(self) mod config;
|
|
||||||
mod commands;
|
mod commands;
|
||||||
pub(self) mod escape;
|
|
||||||
|
|
||||||
pub struct BotService {
|
pub(self) mod deps;
|
||||||
pub bot: Bot
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TelegramService {
|
||||||
|
database_url: String,
|
||||||
|
bot: Bot,
|
||||||
|
me: Me,
|
||||||
|
notification_chat_id: Option<ChatId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BotService {
|
impl TelegramService {
|
||||||
pub fn from_config() -> Self {
|
pub async fn new(database_url: String, token: String, notification_chat_id: Option<ChatId>) -> AnyResult<Self> {
|
||||||
Self {
|
log::info!("Initializing a new Telegram service...");
|
||||||
bot: Bot::new(config::TELEGRAM_BOT_TOKEN())
|
|
||||||
}
|
let bot = Bot::new(token);
|
||||||
|
|
||||||
|
log::trace!("Using bot: {bot:#?}");
|
||||||
|
|
||||||
|
let me = Self::get_me(&bot)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
log::trace!("Using self details: {me:#?}");
|
||||||
|
|
||||||
|
let service = Self {
|
||||||
|
database_url,
|
||||||
|
bot,
|
||||||
|
me,
|
||||||
|
notification_chat_id
|
||||||
|
};
|
||||||
|
|
||||||
|
log::trace!("Created service: {service:#?}");
|
||||||
|
|
||||||
|
Ok(service)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_start_notification(&mut self, me: &Me) -> Result<()> {
|
async fn get_me(bot: &Bot) -> AnyResult<Me> {
|
||||||
let chat_id = config::TELEGRAM_NOTIFICATION_CHATID()
|
log::debug!("Getting self details...");
|
||||||
.context("Variabile d'ambiente TELEGRAM_NOTIFICATION_CHATID mancante.")?;
|
bot.get_me().await
|
||||||
|
.context("Recupero dettagli sul bot non riuscito.")
|
||||||
|
}
|
||||||
|
|
||||||
let version = crate::utils::version::VERSION;
|
async fn send_start_notification(&self) -> AnyResult<Message> {
|
||||||
let username = &me.username.as_ref().unwrap();
|
log::debug!("Sending start notification...");
|
||||||
let id = &me.user.id;
|
|
||||||
|
let notification_chat_id = self.notification_chat_id
|
||||||
|
.context("La chat di notifica non è abilitata.")?;
|
||||||
|
|
||||||
|
let version = crate::utils::version::VERSION
|
||||||
|
.escape_telegram_html();
|
||||||
|
|
||||||
|
let username = self.me.username
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.escape_telegram_html();
|
||||||
|
|
||||||
|
let id = self.me.user.id
|
||||||
|
.to_string()
|
||||||
|
.escape_telegram_html();
|
||||||
|
|
||||||
let text = format!(
|
let text = format!(
|
||||||
"💠 <b>Servizio Telegram avviato</b>\n\
|
"💠 <b>Servizio Telegram avviato</b>\n\
|
||||||
\n\
|
\n\
|
||||||
Royalnet <a href='https://github.com/RYGhub/royalnet/releases/tag/v{}'>v{}</a>\n\
|
Royalnet <a href='https://github.com/RYGhub/royalnet/releases/tag/v{version}'>v{version}</a>\n\
|
||||||
\n\
|
\n\
|
||||||
@{} [<code>{}</code>]",
|
@{username} [<code>{id}</code>]"
|
||||||
version.escape_telegram_html(),
|
|
||||||
version.escape_telegram_html(),
|
|
||||||
username.escape_telegram_html(),
|
|
||||||
id.to_string().escape_telegram_html(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
self.bot.send_message(chat_id, text)
|
log::trace!("Sending start notification message...");
|
||||||
|
let msg = self.bot.send_message(notification_chat_id, text)
|
||||||
.parse_mode(ParseMode::Html)
|
.parse_mode(ParseMode::Html)
|
||||||
.await
|
.await
|
||||||
.context("Invio della notifica di avvio non riuscito.")?;
|
.context("Invio della notifica di avvio non riuscito.")?;
|
||||||
|
|
||||||
Ok(())
|
log::trace!("Successfully sent start notification message!");
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_commands(&mut self) -> AnyResult<()> {
|
||||||
|
log::debug!("Setting self commands...");
|
||||||
|
Command::set_commands(&mut self.bot).await
|
||||||
|
.context("Aggiornamento dei comandi del bot non riuscito.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatcher(&mut self) -> Dispatcher<Bot, AnyError, DefaultKey> {
|
||||||
|
log::debug!("Building dispatcher...");
|
||||||
|
|
||||||
|
let bot_name = self.me.user.username.as_ref().unwrap();
|
||||||
|
log::trace!("Bot username is: @{bot_name:?}");
|
||||||
|
|
||||||
|
log::trace!("Determining pseudo-command regex...");
|
||||||
|
let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap();
|
||||||
|
log::trace!("Pseudo-command regex is: {regex:?}");
|
||||||
|
|
||||||
|
let database = DatabaseInterface::new(self.database_url.clone());
|
||||||
|
|
||||||
|
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()
|
||||||
|
// Only process commands matching a valid command, and parse their arguments
|
||||||
|
.filter_command::<Command>()
|
||||||
|
// Delegate handling
|
||||||
|
.endpoint(Command::handle)
|
||||||
|
)
|
||||||
|
// No valid command was found
|
||||||
|
.endpoint(commands::unknown_command)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.dependencies(
|
||||||
|
dptree::deps![
|
||||||
|
database
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn dispatch(&mut self) -> AnyResult<()> {
|
||||||
|
log::debug!("Starting Telegram dispatcher...");
|
||||||
|
self.dispatcher().dispatch().await;
|
||||||
|
|
||||||
|
anyhow::bail!("Telegram dispatcher has exited unexpectedly.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoyalnetService for BotService {
|
impl RoyalnetService for TelegramService {
|
||||||
async fn run(mut self) -> Result<Infallible> {
|
async fn run(&mut self) -> AnyResult<()> {
|
||||||
log::info!("Starting Telegram service...");
|
log::info!("Starting Telegram service...");
|
||||||
|
|
||||||
log::debug!("Getting bot information...");
|
let _ = self.set_commands()
|
||||||
let me = self.bot.get_me().await
|
.await;
|
||||||
.context("Failed to get information about self")?;
|
|
||||||
|
|
||||||
log::debug!("Setting bot commands...");
|
let _ = self.send_start_notification()
|
||||||
match commands::Command::set_commands(&mut self.bot).await {
|
.await;
|
||||||
Err(e) => log::warn!("Failed to set bot commands: {e}"),
|
|
||||||
_ => log::trace!("Bot commands set successfully!"),
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("Sending start notification...");
|
self.dispatch()
|
||||||
match self.send_start_notification(&me).await {
|
.await
|
||||||
Err(e) => log::warn!("Failed to send start notification: {e}"),
|
|
||||||
_ => log::trace!("Start notification sent successfully!"),
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("Starting Telegram dispatcher...");
|
|
||||||
dispatcher(self.bot, me).dispatch().await;
|
|
||||||
|
|
||||||
log::error!("Telegram dispatcher has exited, bailing out...");
|
|
||||||
anyhow::bail!("Telegram dispatcher has exited.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatcher(bot: Bot, me: Me) -> Dispatcher<Bot, Error, DefaultKey> {
|
|
||||||
let bot_name = me.user.username.unwrap();
|
|
||||||
log::trace!("Bot name is: {bot_name:?}");
|
|
||||||
|
|
||||||
let regex = Regex::new(&format!(r"^/[a-z0-9_]+(?:@{bot_name})?(?:\s+.*)?$")).unwrap();
|
|
||||||
log::trace!("Pseudo-command regex is: {regex:?}");
|
|
||||||
|
|
||||||
log::trace!("Building dispatcher...");
|
|
||||||
Dispatcher::builder(
|
|
||||||
bot,
|
|
||||||
Update::filter_message()
|
|
||||||
.branch(entry()
|
|
||||||
.filter(move |message: Message| -> bool {
|
|
||||||
message.text().is_some_and(|text| regex.is_match(text))
|
|
||||||
})
|
|
||||||
.branch(
|
|
||||||
entry()
|
|
||||||
.filter_command::<commands::Command>()
|
|
||||||
.endpoint(commands::Command::handle)
|
|
||||||
)
|
|
||||||
.endpoint(commands::unknown_command)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.dependencies(
|
|
||||||
dptree::deps![] // No deps needed at the moment.
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
use micronfig::config;
|
|
||||||
|
|
||||||
config! {
|
|
||||||
STRATZ_TOKEN: String,
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
use graphql_client::GraphQLQuery;
|
|
||||||
use reqwest::Client;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub(self) mod config;
|
|
||||||
|
|
||||||
const STRATZ_GRAPHQL_API_URL: &str = "https://api.stratz.com/graphql";
|
|
||||||
|
|
||||||
// Bind these weird types used in the STRATZ API
|
|
||||||
type Short = i16;
|
|
||||||
type Long = i64;
|
|
||||||
type Byte = u8;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct GuildId(pub i64);
|
|
||||||
|
|
||||||
impl From<i64> for GuildId {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(GraphQLQuery)]
|
|
||||||
#[graphql(schema_path="src/stratz/schema.json", query_path="src/stratz/query_guild_matches.gql", response_derives="Debug, Clone")]
|
|
||||||
pub struct GuildMatchesQuery;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Error)]
|
|
||||||
pub enum QueryError {
|
|
||||||
#[error("GraphQL request failed")]
|
|
||||||
Requesting,
|
|
||||||
#[error("GraphQL response parsing failed")]
|
|
||||||
Parsing,
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildMatchesQueryResponse = graphql_client::Response<guild_matches_query::ResponseData>;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::LobbyTypeEnum as LobbyType;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GameModeEnumType as GameMode;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuild as Guild;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuildMatches as Match;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayers as Player;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersHero as Hero;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersSteamAccount as Steam;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::MatchPlayerRoleType as Role;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::MatchLaneType as Lane;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub use guild_matches_query::GuildMatchesQueryGuildMatchesPlayersStatsMatchPlayerBuffEvent as Buff;
|
|
||||||
|
|
||||||
|
|
||||||
/// Get the latest 10 matches of a certain Dota 2 guild.
|
|
||||||
pub async fn query_guild_matches(client: &Client, guild_id: &GuildId) -> Result<GuildMatchesQueryResponse, QueryError> {
|
|
||||||
log::debug!("Querying guild matches with {client:?} for {guild_id:?}...");
|
|
||||||
|
|
||||||
log::trace!("Configuring query variables...");
|
|
||||||
let params = guild_matches_query::Variables {
|
|
||||||
guild_id: guild_id.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
log::trace!("Building query...");
|
|
||||||
let body = GuildMatchesQuery::build_query(params);
|
|
||||||
|
|
||||||
log::trace!("Building API URL...");
|
|
||||||
let url = format!("{}?jwt={}", STRATZ_GRAPHQL_API_URL, config::STRATZ_TOKEN());
|
|
||||||
log::trace!("STRATZ API URL is: {url:?}");
|
|
||||||
|
|
||||||
log::trace!("Making request...");
|
|
||||||
let response = client.post(url)
|
|
||||||
.json(&body)
|
|
||||||
.send().await
|
|
||||||
.map_err(|_| QueryError::Requesting)?
|
|
||||||
.json::<GuildMatchesQueryResponse>().await
|
|
||||||
.map_err(|_| QueryError::Parsing)?;
|
|
||||||
|
|
||||||
log::trace!("Request successful!");
|
|
||||||
Ok(response)
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
use teloxide::types::ChatId;
|
|
||||||
|
|
||||||
pub struct ChatIdConversionHack(i64);
|
|
||||||
|
|
||||||
impl From<i64> for ChatIdConversionHack {
|
|
||||||
fn from(value: i64) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ChatIdConversionHack> for ChatId {
|
|
||||||
fn from(value: ChatIdConversionHack) -> Self {
|
|
||||||
Self(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ChatIdConversionHack> for i64 {
|
|
||||||
fn from(value: ChatIdConversionHack) -> Self {
|
|
||||||
value.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ChatId> for ChatIdConversionHack {
|
|
||||||
fn from(value: ChatId) -> Self {
|
|
||||||
Self(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
pub mod hacks;
|
pub mod result;
|
||||||
|
pub mod escape;
|
||||||
|
|
2
src/utils/result.rs
Normal file
2
src/utils/result.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub use anyhow::Result as AnyResult;
|
||||||
|
pub use anyhow::Error as AnyError;
|
Loading…
Reference in a new issue