mirror of
https://git.funtimes909.xyz/ServerSeekerV2/ServerSeekerV2.git
synced 2026-10-11 09:58:10 +08:00
Initial commit (still very WIP)
This commit is contained in:
commit
b7c879b985
6 files changed
+304
No files matched your search
@@ -0,0 +1,51 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs::File;
|
||||
use std::io::{ErrorKind, Read};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub database: Database,
|
||||
pub rescanner: Rescanner
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Database {
|
||||
pub url: String,
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Rescanner {
|
||||
pub rescan_delay: u16,
|
||||
pub speed: u16,
|
||||
pub repeat: bool,
|
||||
pub port_range: String
|
||||
}
|
||||
|
||||
impl Default for Rescanner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rescan_delay: 60,
|
||||
speed: 1000,
|
||||
repeat: true,
|
||||
port_range: "25560 - 25570".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_config(path: String) -> Config {
|
||||
let mut file = match File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::NotFound => panic!("{} not found!", &path),
|
||||
ErrorKind::PermissionDenied => panic!("File permissions invalid for {}!", &path),
|
||||
_ => panic!("Unknown config error!"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).unwrap_or_default();
|
||||
|
||||
toml::from_str(&contents).unwrap()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use crate::response::Server;
|
||||
use diesel::{Connection, PgConnection};
|
||||
use diesel::connection::SimpleConnection;
|
||||
|
||||
pub fn connect(database_url: &str) -> PgConnection {
|
||||
PgConnection::establish(&database_url).unwrap_or_else(|err| {panic!("Fatal Error! {}", err)})
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use diesel::prelude::*;
|
||||
|
||||
#[derive(Queryable, Selectable)]
|
||||
#[diesel(table_name = crate::schema::servers)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
#[allow(dead_code)]
|
||||
pub struct Server {
|
||||
pub id: i32,
|
||||
pub address: String,
|
||||
pub port: bigdecimal::BigDecimal,
|
||||
pub firstseen: i32,
|
||||
pub lastseen: i32,
|
||||
pub country: String,
|
||||
pub asn: String,
|
||||
pub reversedns: String,
|
||||
pub organization: String,
|
||||
pub version: String,
|
||||
pub protocol: i32,
|
||||
pub fmlnetworkversion: i32,
|
||||
pub motd: String,
|
||||
pub icon: String,
|
||||
pub timesseen: i32,
|
||||
pub preventsreports: bool,
|
||||
pub enforcesecure: bool,
|
||||
pub whitelist: bool,
|
||||
pub cracked: bool,
|
||||
pub maxplayers: i32,
|
||||
pub onlineplayers: i32,
|
||||
pub software: String,
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
use crate::response::{parse_response, Server};
|
||||
use std::io::{Error, ErrorKind, Read, Write};
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
const REQUEST: [u8; 9] = [
|
||||
6, // Size: Amount of bytes in the message
|
||||
0, // ID: Has to be 0
|
||||
0, // Protocol Version: Can be anything as long as it's a valid varint
|
||||
0, // Server address
|
||||
0, 0, // Port: Can be anything (Notchian servers don't this)
|
||||
1, // Next state: 1 for status, 2 for login. Therefore, has to be 1
|
||||
1, // Size
|
||||
0, // ID
|
||||
];
|
||||
|
||||
pub async fn ping_server(address: &str, port: &u16) -> Result<Server, Error> {
|
||||
let address = format!("{}:{}", address, port);
|
||||
let socket = SocketAddr::from_str(address.as_str()).unwrap();
|
||||
|
||||
match TcpStream::connect_timeout(&socket, Duration::from_secs(3)) {
|
||||
Ok(mut stream) => {
|
||||
stream.write(&REQUEST)?;
|
||||
let mut buff: [u8; 1024] = [0; 1024];
|
||||
|
||||
// Read the first buffer, getting the initial bytes needed
|
||||
let mut total_read = stream.read(&mut buff)?;
|
||||
// Grab the final buffer size
|
||||
let buff_size = decode(&buff.to_vec());
|
||||
// and use that to calculate the total amount of bytes needed (in the entire packet)
|
||||
let bytes_needed = buff_size.0 + (buff_size.1 as usize);
|
||||
let mut out_buff = vec![];
|
||||
out_buff.extend_from_slice(&buff[..total_read]);
|
||||
// Just incase, we can also calculate the final size of the json
|
||||
let json_bytes = decode(&(buff[(buff_size.1+1).into()..]));
|
||||
|
||||
// Repeat until we read everything
|
||||
while total_read < bytes_needed {
|
||||
let read = stream.read(&mut buff)?;
|
||||
out_buff.extend_from_slice(&buff[..read]);
|
||||
total_read += read;
|
||||
}
|
||||
|
||||
let response: String = String::from_utf8_lossy(
|
||||
&out_buff[
|
||||
(buff_size.1 + 1 + json_bytes.1).into()
|
||||
..
|
||||
]
|
||||
).to_string();
|
||||
|
||||
Ok(parse_response(response.as_str()))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(Error::new(ErrorKind::NetworkUnreachable, "Server did not respond."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode(bytes: &[u8]) -> (usize, u8) {
|
||||
let mut val: usize = 0;
|
||||
let mut count: u8 = 0;
|
||||
|
||||
for b in bytes {
|
||||
val |= ((b & 0x7f) as usize) << count;
|
||||
|
||||
if (b >> 7) != 1 {
|
||||
break;
|
||||
}
|
||||
|
||||
count += 7;
|
||||
}
|
||||
|
||||
(val, (count / 7) + 1)
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::str::FromStr;
|
||||
use diesel::row::NamedRow;
|
||||
use serde_json::Value;
|
||||
|
||||
pub struct Server {
|
||||
pub version: Option<String>,
|
||||
pub protocol: Option<i32>,
|
||||
pub icon: Option<String>,
|
||||
pub motd: Option<String>,
|
||||
pub prevents_reports: Option<bool>,
|
||||
pub enforces_secure_chat: Option<bool>,
|
||||
pub mods: Option<Vec<Mod>>,
|
||||
pub players: Option<Vec<Player>>,
|
||||
}
|
||||
|
||||
pub struct Player {
|
||||
pub username: String,
|
||||
// Replace this with a UUID type
|
||||
pub uuid: String
|
||||
}
|
||||
|
||||
pub struct Mod {
|
||||
pub mod_id: String,
|
||||
pub mod_name: String,
|
||||
}
|
||||
|
||||
// None values should insert NULL into the database if no value is found
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
Server {
|
||||
version: None,
|
||||
protocol: None,
|
||||
icon: None,
|
||||
motd: None,
|
||||
prevents_reports: None,
|
||||
enforces_secure_chat: None,
|
||||
mods: None,
|
||||
players: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lifetimes will solve this mess
|
||||
fn parse_motd(motd: Value) -> Result<String, ErrorKind::InvalidData> {
|
||||
if motd.is_array() {
|
||||
for value in motd.as_array() {
|
||||
// Recursion
|
||||
if value.is_object() || value.is_array() {
|
||||
parse_motd(value.clone())?;
|
||||
} else {}
|
||||
}
|
||||
} else {
|
||||
if let Some(object) = motd.as_object() {
|
||||
for value in object {
|
||||
// Recursion
|
||||
if value.is_object() || value.is_array() {
|
||||
parse_motd(value.clone())?;
|
||||
}
|
||||
|
||||
if let Some(color) = value.get("color") {}
|
||||
|
||||
if let Some(bold) = value.get("bold") {}
|
||||
|
||||
if let Some(text) = value.get("text") {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I no longer care about trying to make the parsing code good,
|
||||
// there is no proper standard for how servers should respond so this is what you get
|
||||
pub fn parse_response(response: &str) -> Result<Server, Error> {
|
||||
let json = Value::from_str(response)?;
|
||||
|
||||
let mut version = ();
|
||||
let mut protocol = ();
|
||||
|
||||
// TODO! I think I can make this all better
|
||||
if let Some(value) = json.get("version") {
|
||||
if let Some(name) = json.get("name") {
|
||||
version = version.as_str();
|
||||
}
|
||||
|
||||
if let Some(value) = json.get("protocol") {
|
||||
protocol = value.as_i64()
|
||||
}
|
||||
}
|
||||
|
||||
let protocol = json["version"]["protocol"].as_i64();
|
||||
let icon = json["favicon"].to_string();
|
||||
let prevents_reports = json["preventsReports"].as_bool();
|
||||
let enforces_secure_chat = json["enforceSecure"].as_bool();
|
||||
|
||||
let motd: String;
|
||||
if json["description"].is_object() {
|
||||
// motd = parse_motd(json.get("description").clone(), &mut "".to_string());
|
||||
} else {
|
||||
motd = json.get("description").unwrap().to_string();
|
||||
}
|
||||
|
||||
println!("{}", motd);
|
||||
|
||||
Ok(Server {
|
||||
version: None,
|
||||
protocol: None,
|
||||
icon: None,
|
||||
motd: None,
|
||||
prevents_reports: None,
|
||||
enforces_secure_chat,
|
||||
mods: None,
|
||||
players: None,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
diesel::table! {
|
||||
servers (id) {
|
||||
id -> Integer,
|
||||
address -> Text,
|
||||
port -> Numeric,
|
||||
firstseen -> Integer,
|
||||
lastseen -> Integer,
|
||||
country -> Text,
|
||||
asn -> Text,
|
||||
reversedns -> Text,
|
||||
organization -> Text,
|
||||
version -> Text,
|
||||
protocol -> Integer,
|
||||
fmlnetworkversion -> Integer,
|
||||
motd -> Text,
|
||||
icon -> Text,
|
||||
timesseen -> Integer,
|
||||
preventsreports -> Bool,
|
||||
enforcesecure -> Bool,
|
||||
whitelist -> Bool,
|
||||
cracked -> Bool,
|
||||
maxplayers -> Integer,
|
||||
onlineplayers -> Integer,
|
||||
#[sql_name = "type"]
|
||||
software -> Text,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user