mirror of
https://git.funtimes909.xyz/ServerSeekerV2/ServerSeekerV2.git
synced 2026-10-12 02:18:11 +08:00
Merge pull request 'Response parsing rewrite' (#3) from response-rewrite into main
Reviewed-on: https://git.funtimes909.xyz/ServerSeekerV2/ServerSeekerV2-Rust/pulls/3
This commit is contained in:
commit
9d3f0e5ce9
5 files changed
+138
-106
No files matched your search
Generated
+7
@@ -6,6 +6,7 @@ version = 4
|
||||
name = "ServerSeekerV2-Rust"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indicatif",
|
||||
@@ -38,6 +39,12 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
|
||||
@@ -15,6 +15,7 @@ sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio"] }
|
||||
indicatif = { version = "0.17.11" }
|
||||
futures-core = "0.3.31"
|
||||
futures-util = "0.3.31"
|
||||
anyhow = "1.0.98"
|
||||
#anyhow = "1.0.97"
|
||||
#futures = "0.3.31"
|
||||
|
||||
|
||||
+36
-20
@@ -24,13 +24,23 @@ pub async fn fetch_count(pool: &Pool<Postgres>) -> i64 {
|
||||
.get(0)
|
||||
}
|
||||
|
||||
pub async fn update(server: Server, conn: &PgPool) -> Result<(), Error> {
|
||||
pub async fn update(server: Server, conn: &PgPool, host: &(String, u16)) -> anyhow::Result<()> {
|
||||
let mut transaction = conn.begin().await?;
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system time is before the unix epoch")
|
||||
.as_secs() as i32;
|
||||
let mut transaction = conn.begin().await?;
|
||||
let address: &str = server.address.as_str();
|
||||
|
||||
let (address, port) = host;
|
||||
// Can't be used in the database yet
|
||||
let server_type = server.get_type();
|
||||
|
||||
let players = server
|
||||
.players
|
||||
.ok_or(anyhow::Error::msg("Players object is missing!"))?;
|
||||
let forge_data = server
|
||||
.forge_data
|
||||
.ok_or(anyhow::Error::msg("ForgeData object is missing!"))?;
|
||||
|
||||
// Update server
|
||||
sqlx::query(
|
||||
@@ -47,30 +57,33 @@ pub async fn update(server: Server, conn: &PgPool) -> Result<(), Error> {
|
||||
WHERE address = $10
|
||||
AND port = $11",
|
||||
)
|
||||
.bind(server.version)
|
||||
.bind(server.protocol)
|
||||
.bind(server.icon)
|
||||
.bind(server.motd)
|
||||
.bind(server.version.name)
|
||||
.bind(server.version.protocol)
|
||||
.bind(server.favicon)
|
||||
.bind(server.description)
|
||||
.bind(server.prevents_reports)
|
||||
.bind(server.enforces_secure_chat)
|
||||
.bind(timestamp)
|
||||
.bind(server.online_players)
|
||||
.bind(server.max_players)
|
||||
.bind(players.online)
|
||||
.bind(players.max)
|
||||
.bind(address)
|
||||
.bind(server.port)
|
||||
.bind(*port as i64)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
// Upsert players
|
||||
for player in server.players {
|
||||
// Update players
|
||||
for player in players
|
||||
.sample
|
||||
.ok_or(anyhow::Error::msg("Players sample is missing!"))?
|
||||
{
|
||||
sqlx::query("INSERT INTO playerhistory (address, port, playeruuid, playername, firstseen, lastseen) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (address, port, playeruuid) DO UPDATE SET
|
||||
lastseen = EXCLUDED.lastseen,
|
||||
playername = EXCLUDED.playername")
|
||||
.bind(address)
|
||||
.bind(server.port)
|
||||
.bind(player.uuid)
|
||||
.bind(player.username)
|
||||
.bind(*port as i64)
|
||||
.bind(player.id)
|
||||
.bind(player.name)
|
||||
.bind(timestamp)
|
||||
.bind(timestamp)
|
||||
.execute(&mut *transaction)
|
||||
@@ -78,17 +91,20 @@ pub async fn update(server: Server, conn: &PgPool) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
// Update mods
|
||||
for mods in server.mods {
|
||||
for mods in forge_data.mods {
|
||||
sqlx::query("INSERT INTO mods (address, port, modid, modmarker) VALUES ($1, $2, $3, $4) ON CONFLICT (address, port, modid) DO NOTHING")
|
||||
.bind(address)
|
||||
.bind(server.port)
|
||||
.bind(mods.mod_id)
|
||||
.bind(mods.mod_name)
|
||||
.bind(*port as i64)
|
||||
.bind(mods.id)
|
||||
.bind(mods.marker)
|
||||
.bind(timestamp)
|
||||
.bind(timestamp)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|_| anyhow::Error::msg("Failed to commit transaction!"))
|
||||
}
|
||||
+2
-2
@@ -144,8 +144,8 @@ enum RunError {
|
||||
|
||||
async fn run(host: (String, u16), state: Arc<State>) -> Result<(), RunError> {
|
||||
let results = ping::ping_server(&host).await?;
|
||||
let response = response::parse_response(results, &host)?;
|
||||
database::update(response, &state.pool).await?;
|
||||
let response = response::parse_response(results)?;
|
||||
let _ = database::update(response, &state.pool, &host).await;
|
||||
state.progress_bar.inc(1);
|
||||
|
||||
Ok(())
|
||||
|
||||
+92
-84
@@ -1,104 +1,112 @@
|
||||
use serde_json::Value;
|
||||
use std::str::FromStr;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerType {
|
||||
JAVA,
|
||||
NEOFORGE,
|
||||
LEXFORGE,
|
||||
PAPER,
|
||||
SPIGOT,
|
||||
BUKKIT,
|
||||
PURPUR,
|
||||
FOLIA,
|
||||
PUFFERFISH,
|
||||
VELOCITY,
|
||||
LEAVES,
|
||||
WATERFALL,
|
||||
BUNGEECORD,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Server {
|
||||
pub address: String,
|
||||
pub port: i32,
|
||||
pub version: Option<String>,
|
||||
pub protocol: Option<i32>,
|
||||
pub motd: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub version: Version,
|
||||
pub favicon: Option<String>,
|
||||
pub description: Option<String>,
|
||||
#[serde(rename = "preventsChatReports")]
|
||||
pub prevents_reports: Option<bool>,
|
||||
#[serde(rename = "enforcesSecureChat")]
|
||||
pub enforces_secure_chat: Option<bool>,
|
||||
pub online_players: Option<i32>,
|
||||
pub max_players: Option<i32>,
|
||||
pub software: Option<String>,
|
||||
pub mods: Vec<Mod>,
|
||||
pub players: Vec<Player>,
|
||||
pub players: Option<Players>,
|
||||
#[serde(rename = "isModded")]
|
||||
pub modded: Option<bool>,
|
||||
#[serde(rename = "forgeData")]
|
||||
pub forge_data: Option<ForgeData>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Version {
|
||||
pub name: String,
|
||||
pub protocol: i32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum Description {
|
||||
Plain(String),
|
||||
Object { text: String },
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Players {
|
||||
pub max: i32,
|
||||
pub online: i32,
|
||||
pub sample: Option<Vec<Player>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Player {
|
||||
pub username: Option<String>,
|
||||
// TODO! Replace this with a UUID type
|
||||
pub uuid: Option<String>,
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ForgeData {
|
||||
pub mods: Vec<Mod>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Mod {
|
||||
pub mod_id: Option<String>,
|
||||
pub mod_name: Option<String>,
|
||||
#[serde(rename = "modId")]
|
||||
pub id: String,
|
||||
#[serde(rename = "modmarker")]
|
||||
pub marker: String,
|
||||
}
|
||||
|
||||
pub fn parse_response(response: String, host: &(String, u16)) -> Result<Server, serde_json::Error> {
|
||||
let json = Value::from_str(response.as_str())?;
|
||||
impl Server {
|
||||
pub fn get_type(&self) -> ServerType {
|
||||
// Neoforge sends a "isModded" field
|
||||
if self.modded.is_some() {
|
||||
return ServerType::NEOFORGE;
|
||||
}
|
||||
|
||||
let mut version: Option<String> = None;
|
||||
let mut protocol: Option<i32> = None;
|
||||
let mut online_players: Option<i32> = None;
|
||||
let mut max_players: Option<i32> = None;
|
||||
let mut players = vec![];
|
||||
let mut mods = vec![];
|
||||
// Forge sends a "forgeData" object
|
||||
if self.forge_data.is_some() {
|
||||
return ServerType::LEXFORGE;
|
||||
}
|
||||
|
||||
let icon = json["icon"].as_str().map(String::from);
|
||||
let prevents_reports = json["preventsChatReports"].as_bool();
|
||||
let enforces_secure_chat = json["enforcesChat"].as_bool();
|
||||
|
||||
// Version object
|
||||
if let Some(value) = json.get("version") {
|
||||
version = value["name"].as_str().map(String::from);
|
||||
|
||||
if let Some(p) = value["protocol"].as_i64() {
|
||||
protocol = Some(p as i32)
|
||||
match self.version.name.split(' ').nth(0) {
|
||||
Some("Paper") => ServerType::PAPER,
|
||||
Some("Spigot") => ServerType::SPIGOT,
|
||||
Some("Bukkit") => ServerType::BUKKIT,
|
||||
Some("Purpur") => ServerType::PURPUR,
|
||||
Some("Folia") => ServerType::FOLIA,
|
||||
Some("Pufferfish") => ServerType::PUFFERFISH,
|
||||
Some("Velocity") => ServerType::VELOCITY,
|
||||
Some("Leaves") => ServerType::LEAVES,
|
||||
Some("Waterfall") => ServerType::WATERFALL,
|
||||
Some("Bungeecord") => ServerType::BUNGEECORD,
|
||||
Some(_) => ServerType::JAVA,
|
||||
None => ServerType::JAVA,
|
||||
}
|
||||
}
|
||||
|
||||
// Players object
|
||||
if let Some(value) = json.get("players") {
|
||||
if let Some(online) = value["online"].as_i64() {
|
||||
online_players = Some(online as i32);
|
||||
}
|
||||
|
||||
if let Some(max) = value["max"].as_i64() {
|
||||
max_players = Some(max as i32)
|
||||
}
|
||||
|
||||
if let Some(player_sample) = value["sample"].as_array() {
|
||||
player_sample.iter().for_each(|player| {
|
||||
players.push(Player {
|
||||
username: player["name"].as_str().map(String::from),
|
||||
uuid: player["id"].as_str().map(String::from),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mods_array) = json["mods"].as_array() {
|
||||
mods_array.iter().for_each(|m| {
|
||||
mods.push(Mod {
|
||||
mod_name: m["modid"].as_str().map(String::from),
|
||||
mod_id: m["version"].as_str().map(String::from),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Server {
|
||||
address: host.0.to_string(),
|
||||
port: host.1 as i32,
|
||||
version,
|
||||
protocol,
|
||||
motd: None,
|
||||
icon,
|
||||
prevents_reports,
|
||||
enforces_secure_chat,
|
||||
online_players,
|
||||
max_players,
|
||||
software: None,
|
||||
mods,
|
||||
players,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_response(response: String) -> Result<Server, serde_json::Error> {
|
||||
Ok(serde_json::from_str(&response)?)
|
||||
}
|
||||
Reference in new issue
Block a user