last touches

This commit is contained in:
August 2026-05-06 18:39:11 -04:00
parent f1880424ab
commit 7511f3c7d4
Signed by: shibedrill
SSH Key Fingerprint: SHA256:M0m3JW1s38BgO2t0fG146Yxd9OJ2IOqkvCAsuRHQ6Pw
10 changed files with 200 additions and 67 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target /target
.env .env
config.json

50
Cargo.lock generated
View File

@ -492,12 +492,13 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -506,9 +507,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -516,9 +517,20 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-io" name = "futures-io"
@ -528,9 +540,9 @@ checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -539,21 +551,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.31" version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -563,7 +575,6 @@ dependencies = [
"futures-task", "futures-task",
"memchr", "memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils",
"slab", "slab",
] ]
@ -1189,17 +1200,12 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "playerbot" name = "playerbot"
version = "0.6.1" version = "1.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures",
"poise", "poise",
"reqwest 0.13.3", "reqwest 0.13.3",
"serde", "serde",

View File

@ -1,10 +1,11 @@
[package] [package]
name = "playerbot" name = "playerbot"
version = "0.6.1" version = "1.0.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.102" anyhow = "1.0.102"
futures = "0.3.32"
poise = "0.6.2" poise = "0.6.2"
reqwest = {version = "0.13.3", features = ["json"]} reqwest = {version = "0.13.3", features = ["json"]}
serde = {version = "1.0.215", features = ["derive", "serde_derive"]} serde = {version = "1.0.215", features = ["derive", "serde_derive"]}

View File

@ -4,7 +4,7 @@
{ {
"handler_type": "minecraft", "handler_type": "minecraft",
"address": "https://dawn.shibedrill.site", "address": "https://dawn.shibedrill.site",
"token": "foobar" "token": "MTI5Nzc1NjA3ODE2MjgzNzU0NA.GGjPC_.oy075omzc4IxkX84xc_H7qoYrtil7T0d7ampJ0"
} }
] ]
} }

View File

@ -1,7 +1,135 @@
use crate::types::*;
use poise;
struct BotRunner {
use crate::request::request;
use crate::types::*;
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{ActivityData, ClientBuilder, GatewayIntents};
use tokio::sync::Mutex;
pub struct BotRunner {
client: poise::serenity_prelude::Client, client: poise::serenity_prelude::Client,
server_data: dyn ServerInfo, }
pub struct Data {
server: Box<dyn ServerInfo>,
cached_reply: Mutex<Option<ServerResponse>>,
}
impl BotRunner {
pub async fn new(server: Box<dyn ServerInfo>) -> Self {
let token = server.app_token();
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: vec![players(), join()],
event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data))
},
..Default::default()
})
.setup(|ctx, _ready, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {
server,
cached_reply: Mutex::new(None),
})
})
})
.build();
let client = ClientBuilder::new(token, GatewayIntents::non_privileged())
.framework(framework)
.activity(ActivityData::custom("Waiting on first heartbeat"))
.await
.unwrap();
BotRunner { client }
}
pub async fn run(&mut self) -> Result<(), serenity::Error> {
self.client.start().await
}
}
pub async fn event_handler(
ctx: &serenity::Context,
event: &serenity::FullEvent,
_framework: poise::FrameworkContext<'_, Data, serenity::Error>,
data: &Data,
) -> Result<(), serenity::Error> {
match event {
serenity::FullEvent::Ready {
data_about_bot: _bot_data,
} => loop {
println!("Checking status: {}", data.server.api_address());
let http_response = request(data.server.api_address()).await.unwrap();
let results = data.server.parse(http_response.text().await.unwrap());
match &results {
ServerResponse::Offline => {
ctx.set_presence(
Some(ActivityData::custom("Server offline!")),
serenity::OnlineStatus::DoNotDisturb,
);
}
ServerResponse::Online(online_info) => {
ctx.set_presence(
Some(ActivityData::custom(format!(
"{}/{} online, v{}",
online_info.players_online,
online_info.player_limit,
online_info.version
))),
if online_info.players_online > 0 {
serenity::OnlineStatus::Online
} else {
serenity::OnlineStatus::Idle
},
);
}
}
//println!("{:#?}", &results);
*data.cached_reply.lock().await = Some(results);
tokio::time::sleep(std::time::Duration::from_secs(30)).await;
},
_ => Ok(()),
}
}
#[poise::command(slash_command)]
pub async fn players(
ctx: poise::Context<'_, Data, serenity::Error>,
) -> Result<(), serenity::Error> {
if let Some(reply) = &*ctx.data().cached_reply.lock().await {
match reply {
ServerResponse::Offline => ctx.say("Server is offline!").await?,
ServerResponse::Online(on) => {
if let Some(list) = &on.players {
ctx.say(format!(
"{} out of {} players are online.\nPlayers online: {}",
on.players_online,
on.player_limit,
list.join(", ")
))
.await?
} else {
ctx.say(format!(
"{} out of {} players are online.",
on.players_online, on.player_limit
))
.await?
}
}
};
} else {
ctx.say("No status response yet. Please wait 30 seconds.")
.await?;
}
Ok(())
}
#[poise::command(slash_command)]
pub async fn join(
ctx: poise::Context<'_, Data, serenity::Error>,
) -> Result<(), serenity::Error> {
ctx.say(format!("To join, type `{}` in the server URL box or search bar.", ctx.data().server.addressable_name())).await?;
Ok(())
} }

View File

@ -1,6 +1,6 @@
use std::{fs::File, path::Path};
use crate::{handlers, types::ServerInfo}; use crate::{handlers, types::ServerInfo};
use serde::Deserialize; use serde::Deserialize;
use std::{fs::File, path::Path};
use url::Url; use url::Url;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -22,13 +22,15 @@ pub fn parse_configs(path: &Path) -> Result<Config, anyhow::Error> {
} }
pub fn build_handlers(conf: Config) -> Vec<Box<dyn ServerInfo>> { pub fn build_handlers(conf: Config) -> Vec<Box<dyn ServerInfo>> {
let mut results: Vec<Box<dyn ServerInfo>> = vec!(); assert_eq!(conf.version, "0.1.0");
let mut results: Vec<Box<dyn ServerInfo>> = vec![];
for item in conf.entries { for item in conf.entries {
match item.handler_type.as_str() { match item.handler_type.as_str() {
"minecraft" => { "minecraft" => results.push(Box::new(handlers::minecraft::Server::new(
results.push(Box::new(handlers::minecraft::Server::new(item.token, item.address))) item.token,
}, item.address,
_ => {} ))),
_ => {}
} }
} }
results results

View File

@ -84,15 +84,21 @@ impl ServerInfo for Server {
players_online: players.online, players_online: players.online,
player_limit: players.max, player_limit: players.max,
version: parsed_data.version.unwrap(), version: parsed_data.version.unwrap(),
players: Some(players players: Some(
.list players
.unwrap_or_default() .list
.iter() .unwrap_or_default()
.map(|e| e.name.clone()) .iter()
.collect()), .map(|e| e.name.clone())
.collect(),
),
}) })
} }
false => ServerResponse::Offline, false => ServerResponse::Offline,
} }
} }
fn supports_playerlist(&self) -> bool {
true
}
} }

View File

@ -1,35 +1,22 @@
#![feature(impl_trait_in_bindings)]
mod bot_runner; mod bot_runner;
mod config_parser; mod config_parser;
mod handlers; mod handlers;
mod types;
mod request; mod request;
mod types;
use crate::bot_runner::BotRunner;
use futures::{self, future::try_join_all};
use std::path::Path; use std::path::Path;
use crate::{
request::request, types::ServerResponse
};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let config = config_parser::parse_configs(Path::new("config.json")).unwrap(); let config = config_parser::parse_configs(Path::new("config.json")).unwrap();
let handlers = config_parser::build_handlers(config); let handlers = config_parser::build_handlers(config);
let mut bots: Vec<bot_runner::BotRunner> = vec![];
let http_response = request(handlers[0].api_address()).await.unwrap(); for item in handlers {
let results = handlers[0].parse(http_response.text().await.unwrap()); bots.push(BotRunner::new(item).await);
match results {
ServerResponse::Offline => println!("Offline"),
ServerResponse::Online(online_info) => {
println!(
"Name: {}\nAddress: {}\nVersion: {}\nPlayers: {}/{}",
online_info.readable_name,
online_info.searchable_name,
online_info.version,
online_info.players_online,
online_info.player_limit
);
println!("Players online: {}", online_info.players.unwrap().join(", "));
}
} }
let futures: Vec<_> = bots.iter_mut().map(|b| b.run()).collect();
let _ = try_join_all(futures).await;
} }

View File

@ -1,5 +1,5 @@
use reqwest::{header::HeaderValue, Response};
use url::Url; use url::Url;
use reqwest::{Response, header::HeaderValue};
pub async fn request(url: Url) -> Result<Response, reqwest::Error> { pub async fn request(url: Url) -> Result<Response, reqwest::Error> {
let client = reqwest::Client::new(); let client = reqwest::Client::new();

View File

@ -7,6 +7,7 @@ pub enum ServerResponse {
} }
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)]
pub struct ServerOnlineResponse { pub struct ServerOnlineResponse {
pub players_online: u64, pub players_online: u64,
pub player_limit: u64, pub player_limit: u64,
@ -19,7 +20,7 @@ pub struct ServerOnlineResponse {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub trait ServerInfo { pub trait ServerInfo: Send + Sync {
fn new(token: String, addr: Url) -> Self fn new(token: String, addr: Url) -> Self
where where
Self: Sized; Self: Sized;
@ -35,4 +36,5 @@ pub trait ServerInfo {
/// The app token used to send status updates. /// The app token used to send status updates.
fn app_token(&self) -> String; fn app_token(&self) -> String;
fn supports_playerlist(&self) -> bool;
} }