1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 11:34:18 +00:00

Add /terraria command

This commit is contained in:
Steffo 2024-10-31 01:01:22 +01:00
parent dfd9c982a9
commit 261d314e83
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
12 changed files with 254 additions and 5 deletions

View file

@ -1,5 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run (telegram)" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Run"> <configuration default="false" name="Run (telegram)" type="CargoCommandRunConfiguration" factoryName="Cargo Command" folderName="Run">
<option name="buildProfileId" value="dev" />
<option name="command" value="run --package royalnet --bin royalnet --no-default-features --features interface_database,service_telegram" /> <option name="command" value="run --package royalnet --bin royalnet --no-default-features --features interface_database,service_telegram" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs> <envs>

View file

@ -103,6 +103,7 @@ optional = true
default = [ default = [
"interface_database", "interface_database",
"interface_stratz", "interface_stratz",
"interface_tshock",
"service_brooch", "service_brooch",
"service_telegram", "service_telegram",
] ]
@ -112,9 +113,13 @@ interface_database = [
] ]
interface_stratz = [ interface_stratz = [
"graphql_client" "graphql_client"
]
interface_tshock = [
] ]
service_telegram = [ service_telegram = [
"interface_database", "interface_database",
"interface_tshock",
"teloxide", "teloxide",
"rand", "rand",
"parse_datetime", "parse_datetime",

View file

@ -0,0 +1,11 @@
DELETE
FROM telegram
WHERE user_id = 45;
DELETE
FROM steam
WHERE user_id = 45;
DELETE
FROM users
WHERE id = 45;

View file

@ -0,0 +1,8 @@
INSERT INTO users (id, username)
VALUES (45, 'fluiddruid');
INSERT INTO telegram
VALUES (45, 524944901);
INSERT INTO steam
VALUES (45, 1090217987);

View file

@ -21,6 +21,9 @@ pub mod service_telegram {
TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId, TELEGRAM_NOTIFICATION_CHATID?: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId,
// TODO: Unimplemented // TODO: Unimplemented
TELEGRAM_MATCHMAKING_CHATID?: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId, TELEGRAM_MATCHMAKING_CHATID?: String > i64 -> crate::instance::config::ChatIdConversionHack -> teloxide::types::ChatId,
TSHOCK_BASE_URL?: String > reqwest::Url,
TSHOCK_TOKEN?: String,
} }
} }

View file

@ -3,7 +3,7 @@ use std::future::Future;
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::services::RoyalnetService; use crate::services::RoyalnetService;
pub(self) mod config; pub mod config;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RoyalnetInstance { pub struct RoyalnetInstance {

View file

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

View file

@ -3,3 +3,6 @@ pub mod database;
#[cfg(feature = "interface_stratz")] #[cfg(feature = "interface_stratz")]
pub mod stratz; pub mod stratz;
#[cfg(feature = "interface_tshock")]
pub mod tshock;

View file

@ -0,0 +1,89 @@
use reqwest::Url;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerStatus {
// status
#[serde(rename = "name")]
pub server_name: String,
#[serde(rename = "serverversion")]
pub server_version: String,
#[serde(rename = "tshockversion")]
pub tshock_version: String,
pub port: u16,
#[serde(rename = "playercount")]
pub current_players: u8,
#[serde(rename = "maxplayers")]
pub max_players: u8,
#[serde(rename = "world")]
pub world_name: String,
// uptime
// serverpassword
pub players: Vec<PlayerStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerStatus {
pub nickname: String,
pub username: String,
pub group: String,
pub active: bool,
pub state: isize, // ???
pub team: isize,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("API request failed")]
Requesting(reqwest::Error),
#[error("API response parsing failed")]
Parsing(reqwest::Error),
}
pub async fn get_server_status(client: &reqwest::Client, base_url: Url, token: &str) -> Result<ServerStatus, Error> {
log::debug!("Getting TShock server status...");
log::trace!("Preparing request URL from base: {base_url:?}");
let mut url = base_url;
log::trace!("Building path...");
{
let mut path = url.path_segments_mut()
.expect("URL to have a valid path");
path.push("v2");
path.push("server");
path.push("status");
}
log::trace!("Building query parameters...");
{
let mut query = url.query_pairs_mut();
query.append_pair("token", token);
query.append_pair("players", "true");
}
log::trace!("Making request with: {url:?}");
let response = client.get(url)
.send()
.await
.map_err(Error::Requesting)?;
log::trace!("Parsing response: {response:#?}");
let data = response
.json::<ServerStatus>()
.await
.map_err(Error::Parsing)?;
Ok(data)
}

View file

@ -27,6 +27,7 @@ pub mod roll;
pub mod diario; pub mod diario;
pub mod matchmaking; pub mod matchmaking;
pub mod quote; pub mod quote;
pub mod terraria;
type CommandResult = AnyResult<()>; type CommandResult = AnyResult<()>;
@ -61,6 +62,8 @@ pub enum Command {
Matchmaking(matchmaking::MatchmakingArgs), Matchmaking(matchmaking::MatchmakingArgs),
#[command(description = "Invia una riga dal diario RYG.")] #[command(description = "Invia una riga dal diario RYG.")]
Quote(quote::QuoteArgs), Quote(quote::QuoteArgs),
#[command(description = "Vedi chi è online sul server attuale di Terraria.")]
Terraria,
} }
impl Command { impl Command {
@ -110,6 +113,7 @@ impl Command {
Command::Diario(ref args) => diario::handler(&bot, &message, args, &database).await, Command::Diario(ref args) => diario::handler(&bot, &message, args, &database).await,
Command::Matchmaking(ref args) => matchmaking::handler(&bot, &message, args, &database).await, Command::Matchmaking(ref args) => matchmaking::handler(&bot, &message, args, &database).await,
Command::Quote(ref id) => quote::handler(&bot, &message, id, &database).await, Command::Quote(ref id) => quote::handler(&bot, &message, id, &database).await,
Command::Terraria => terraria::handler(&bot, &message, &database).await
}; };
log::trace!("Delegating error handling to error handler..."); log::trace!("Delegating error handling to error handler...");

View file

@ -0,0 +1,125 @@
use crate::instance::config::service_telegram::{TSHOCK_BASE_URL, TSHOCK_TOKEN};
use crate::interfaces::database::models::TelegramUserId;
use crate::interfaces::tshock::{get_server_status, PlayerStatus, ServerStatus};
use crate::services::telegram::commands::CommandResult;
use crate::services::telegram::dependencies::interface_database::DatabaseInterface;
use crate::utils::anyhow_result::AnyResult;
use crate::utils::telegram_string::TelegramEscape;
use anyhow::Context;
use diesel::PgConnection;
use teloxide::payloads::SendMessageSetters;
use teloxide::prelude::{Message, Requester};
use teloxide::types::{ParseMode, ReplyParameters};
use teloxide::Bot;
fn stringify_team(value: isize) -> &'static str {
match value {
0 => "⬜️",
1 => "🟥",
2 => "🟩",
3 => "🟦",
4 => "🟨",
5 => "🟪",
_ => "⬛️",
}
}
fn get_player_telegram_id(database: &mut PgConnection, tshock_username: &str) -> AnyResult<Option<TelegramUserId>> {
use diesel::prelude::*;
use diesel::{ExpressionMethods, QueryDsl};
use crate::interfaces::database::schema::{telegram, users};
use crate::interfaces::database::models::TelegramUser;
Ok(
users::table
.inner_join(
telegram::table.on(
users::id.eq(telegram::user_id)
)
)
.filter(
users::username.eq(tshock_username)
)
.select(TelegramUser::as_select())
.get_result::<TelegramUser>(database)
.optional()
.context("Non è stato possibile connettersi al database RYG.")?
.map(|t| t.telegram_id)
)
}
fn stringify_player(database: &mut PgConnection, tshock: PlayerStatus) -> AnyResult<String> {
log::debug!("Stringifying player...");
let telegram_id = match tshock.username.as_str() {
"" => None,
username => {
get_player_telegram_id(database, username)
.context("Non è stato possibile trovare l'username di un giocatore.")?
}
};
let string = match telegram_id {
None => format!(
"{} <b>{}</b>",
stringify_team(tshock.team),
tshock.nickname.escape_telegram_html(),
),
Some(telegram_id) => format!(
"{} <a href=\"tg://user?id={}\"><b>{}</b></a>",
stringify_team(tshock.team),
telegram_id,
tshock.nickname.escape_telegram_html(),
),
};
Ok(string)
}
fn stringify_status(database: &mut PgConnection, tshock: ServerStatus) -> AnyResult<String> {
let mut players_strings = Vec::new();
for player in tshock.players {
players_strings.push(stringify_player(database, player)?);
}
Ok(
format!(
"🌳 <b><u>{}</u></b>\n\
{} giocator{} online\n\
\n\
{}",
tshock.world_name,
tshock.current_players,
if tshock.current_players == 1 { 'e' } else { 'i' },
players_strings.join("\n"),
)
)
}
pub async fn handler(bot: &Bot, message: &Message, database: &DatabaseInterface) -> CommandResult {
let mut database = database.connect()?;
let client = reqwest::Client::new();
let url = TSHOCK_BASE_URL().clone()
.context("Il bot non è configurato per connettersi a un server di Terraria.")?;
let token = TSHOCK_TOKEN().as_ref()
.context("Il bot non è configurato per autenticarsi a un server di Terraria.")?;
let status = get_server_status(&client, url, token)
.await
.context("Non è stato possibile effettuare la richiesta al server. Forse il server è spento?")?;
let text = stringify_status(&mut database, status)
.context("Non è stato possibile trasformare in testo lo stato corrente del server.")?;
let _reply = bot
.send_message(message.chat.id, text)
.parse_mode(ParseMode::Html)
.reply_parameters(ReplyParameters::new(message.id))
.await
.context("Non è stato possibile inviare la risposta.")?;
Ok(())
}