inital working setup

This commit is contained in:
2025-09-01 00:47:38 +02:00
commit a10f466538
19 changed files with 3939 additions and 0 deletions

256
src/services/esi_static.rs Normal file
View File

@@ -0,0 +1,256 @@
use image::load_from_memory;
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fmt;
use crate::model::alliance::Alliance;
use crate::model::character::Character;
use crate::model::corporation::Corporation;
use crate::model::ship::Ship;
impl EsiClient {
pub fn new() -> Self {
let client = Client::builder()
.user_agent("killpaper-hw,oli1111@web.de")
.gzip(true)
.brotli(true)
.build()
.unwrap();
Self {
reqwest_client: client,
}
}
pub fn get_alliance(&self, id: u32) -> Result<Alliance, EsiError> {
#[derive(Deserialize)]
struct AllianceInfo {
name: String,
ticker: String,
}
let info_url = format!("https://esi.evetech.net/latest/alliances/{id}/");
println!("REQ ESI{}", info_url);
let info: AllianceInfo = self.reqwest_client.get(&info_url).send()?.json()?;
let logo_url = format!("https://images.evetech.net/alliances/{id}/logo?size=64");
println!("REQ ESI{}", logo_url);
let bytes = self.reqwest_client.get(&logo_url).send()?.bytes()?;
let img = load_from_memory(&bytes)?.to_rgb8();
Ok(Alliance {
alliance_id: id,
name: info.name,
short: info.ticker,
logo: img,
})
}
pub fn get_corporation(&self, id: u32) -> Result<Corporation, EsiError> {
#[derive(Deserialize)]
struct CorporationInfo {
name: String,
ticker: String,
}
let info_url = format!("https://esi.evetech.net/latest/corporations/{id}/");
println!("REQ ESI{}", info_url);
let info: CorporationInfo = self.reqwest_client.get(&info_url).send()?.json()?;
let logo_url = format!("https://images.evetech.net/corporations/{id}/logo?size=64");
println!("REQ ESI{}", logo_url);
let bytes = self.reqwest_client.get(&logo_url).send()?.bytes()?;
let img = load_from_memory(&bytes)?.to_rgb8();
Ok(Corporation {
corporation_id: id,
name: info.name,
short: info.ticker,
logo: img,
})
}
pub fn get_character(&self, id: u32) -> Result<Character, EsiError> {
#[derive(Deserialize)]
struct CharacterInfo {
name: String,
}
let info_url = format!("https://esi.evetech.net/latest/characters/{id}/");
println!("REQ ESI{}", info_url);
let info: CharacterInfo = self.reqwest_client.get(&info_url).send()?.json()?;
let portrait_url = format!("https://images.evetech.net/characters/{id}/portrait?size=64");
println!("REQ ESI{}", portrait_url);
let bytes = self.reqwest_client.get(&portrait_url).send()?.bytes()?;
let img = load_from_memory(&bytes)?.to_rgb8();
Ok(Character {
character_id: id,
name: info.name,
portrait: img,
})
}
pub fn get_ship(&self, id: u32) -> Result<Ship, EsiError> {
#[derive(Deserialize)]
struct ShipInfo {
name: String,
}
let info_url = format!("https://esi.evetech.net/latest/universe/types/{id}/");
println!("REQ ESI{}", info_url);
let info: ShipInfo = self.reqwest_client.get(&info_url).send()?.json()?;
let icon_url = format!("https://images.evetech.net/types/{id}/icon?size=64");
println!("REQ ESI{}", icon_url);
let bytes = self.reqwest_client.get(&icon_url).send()?.bytes()?;
let img = load_from_memory(&bytes)?.to_rgb8();
Ok(Ship {
ship_id: id,
name: info.name,
icon: img,
})
}
pub fn get_system(&self, id: u32) -> Result<String, EsiError> {
#[derive(Deserialize)]
struct SystemInfo {
name: String,
}
let url = format!("https://esi.evetech.net/latest/universe/systems/{id}/");
println!("REQ ESI{}", url);
let info: SystemInfo = self.reqwest_client.get(&url).send()?.json()?;
Ok(info.name)
}
}
#[derive(Debug)]
pub enum EsiError {
Http(reqwest::Error),
Image(image::ImageError),
Json(serde_json::Error),
}
impl fmt::Display for EsiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EsiError::Http(e) => write!(f, "HTTP request error: {}", e),
EsiError::Image(e) => write!(f, "Image decoding error: {}", e),
EsiError::Json(e) => write!(f, "JSON parsing error: {}", e),
}
}
}
// Conversions so we can use `?`
impl From<reqwest::Error> for EsiError {
fn from(e: reqwest::Error) -> Self {
EsiError::Http(e)
}
}
impl From<image::ImageError> for EsiError {
fn from(e: image::ImageError) -> Self {
EsiError::Image(e)
}
}
impl From<serde_json::Error> for EsiError {
fn from(e: serde_json::Error) -> Self {
EsiError::Json(e)
}
}
impl std::error::Error for EsiError {}
pub struct EsiClient {
pub(crate) reqwest_client: Client,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_alliance() -> Result<(), EsiError> {
let esi = EsiClient::new();
let alliance = esi.get_alliance(99002685)?;
assert_eq!(alliance.alliance_id, 99002685);
println!("Alliance name: {:?}", alliance.name);
println!("Alliance ticker: {:?}", alliance.short);
println!("image {} {}", alliance.logo.width(), alliance.logo.height());
assert!(alliance.logo.width() > 0);
assert!(alliance.logo.height() > 0);
Ok(())
}
#[test]
fn test_get_corporation() -> Result<(), EsiError> {
let esi = EsiClient::new();
let corp = esi.get_corporation(98330748)?;
assert_eq!(corp.corporation_id, 98330748);
println!("Corporation name: {:?}", corp.name);
println!("Corporation ticker: {:?}", corp.short);
println!("image {} {}", corp.logo.width(), corp.logo.height());
assert!(corp.logo.width() > 0);
assert!(corp.logo.height() > 0);
Ok(())
}
#[test]
fn test_get_character() -> Result<(), EsiError> {
let esi = EsiClient::new();
let character = esi.get_character(90951867)?;
assert_eq!(character.character_id, 90951867);
println!("Character name: {:?}", character.name);
println!(
"image {} {}",
character.portrait.width(),
character.portrait.height()
);
assert!(character.portrait.width() > 0);
assert!(character.portrait.height() > 0);
Ok(())
}
#[test]
fn test_get_ship() -> Result<(), EsiError> {
let esi = EsiClient::new();
let ship = esi.get_ship(587)?; // Rifter
assert_eq!(ship.ship_id, 587);
println!("Ship: {}", ship.name);
println!("image {} {}", ship.icon.width(), ship.icon.height());
assert!(ship.icon.width() > 0);
assert!(ship.icon.height() > 0);
Ok(())
}
#[test]
fn test_get_system() -> Result<(), EsiError> {
let esi = EsiClient::new();
let system_name = esi.get_system(30000142)?; // Jita
assert_eq!(system_name, "Jita");
println!("System name: {}", system_name);
Ok(())
}
}

2
src/services/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod esi_static;
pub mod zkill;

214
src/services/zkill.rs Normal file
View File

@@ -0,0 +1,214 @@
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fmt;
use crate::model::killmail::*;
#[derive(Debug, Deserialize)]
struct ZkbKill {
killmail_id: u32,
zkb: ZkbInfo,
}
#[derive(Debug, Deserialize)]
struct ZkbInfo {
hash: String,
totalValue: f64,
}
pub struct ZkillClient {
client: Client,
}
// ESI API deserialization structs
#[derive(Deserialize)]
struct EsiAttacker {
character_id: Option<u32>,
corporation_id: Option<u32>,
alliance_id: Option<u32>,
ship_type_id: Option<u32>,
damage_done: Option<u32>,
final_blow: Option<bool>,
}
#[derive(Deserialize)]
struct EsiVictim {
character_id: Option<u32>,
corporation_id: Option<u32>,
alliance_id: Option<u32>,
ship_type_id: Option<u32>,
}
#[derive(Deserialize)]
struct EsiKillmail {
solar_system_id: u32,
attackers: Vec<EsiAttacker>,
victim: EsiVictim,
}
// Custom error type
#[derive(Debug)]
pub enum ZkillError {
Reqwest(reqwest::Error),
Json(serde_json::Error),
NotFound(String),
}
impl fmt::Display for ZkillError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ZkillError::Reqwest(e) => write!(f, "Request failed: {}", e),
ZkillError::Json(e) => write!(f, "JSON parsing failed: {}", e),
ZkillError::NotFound(msg) => write!(f, "Not found: {}", msg),
}
}
}
impl std::error::Error for ZkillError {}
impl From<reqwest::Error> for ZkillError {
fn from(err: reqwest::Error) -> Self {
ZkillError::Reqwest(err)
}
}
impl From<serde_json::Error> for ZkillError {
fn from(err: serde_json::Error) -> Self {
ZkillError::Json(err)
}
}
impl ZkillClient {
pub fn new() -> Self {
let client = Client::builder()
.user_agent("killpaper-hw,oli1111@web.de")
.gzip(true)
.brotli(true)
.build()
.unwrap();
ZkillClient { client }
}
pub fn get_corporation_kills(
&self,
corp_id: u32,
past_seconds: u32,
) -> Result<Vec<KillmailRequest>, ZkillError> {
let url = format!(
"https://zkillboard.com/api/kills/corporationID/{}/pastSeconds/{}/",
corp_id, past_seconds
);
println!("REQ ZKILL {url}");
let response = self.client.get(&url).send()?.text()?;
let kills: Vec<ZkbKill> = serde_json::from_str(&response)?;
let result = kills
.into_iter()
.map(|k| KillmailRequest {
id: k.killmail_id,
hash: k.zkb.hash,
value: k.zkb.totalValue,
})
.collect();
Ok(result)
}
pub fn get_corporation_losses(
&self,
corp_id: u32,
past_seconds: u32,
) -> Result<Vec<KillmailRequest>, ZkillError> {
let url = format!(
"https://zkillboard.com/api/losses/corporationID/{}/pastSeconds/{}/",
corp_id, past_seconds
);
println!("REQ ZKILL {url}");
let response = self.client.get(&url).send()?.text()?;
let kills: Vec<ZkbKill> = serde_json::from_str(&response)?;
let result = kills
.into_iter()
.map(|k| KillmailRequest {
id: k.killmail_id,
hash: k.zkb.hash,
value: k.zkb.totalValue,
})
.collect();
Ok(result)
}
pub fn fetch_killmail_details(&self, req: &KillmailRequest) -> Result<Killmail, ZkillError> {
let url = format!(
"https://esi.evetech.net/latest/killmails/{}/{}/",
req.id, req.hash
);
println!("REQ ZKILL {url}");
let esi_killmail: EsiKillmail = self.client.get(&url).send()?.json()?;
let attackers = esi_killmail
.attackers
.into_iter()
.map(|a| KillmailAttacker {
character_id: a.character_id.unwrap_or(0),
corporation_id: a.corporation_id.unwrap_or(0),
alliance_id: a.alliance_id.unwrap_or(0),
ship_id: a.ship_type_id.unwrap_or(0),
damage_done: a.damage_done.unwrap_or(0),
final_blow: a.final_blow.unwrap_or(false),
})
.collect();
let victim = KillmailVictim {
character_id: esi_killmail.victim.character_id.unwrap_or(0),
corporation_id: esi_killmail.victim.corporation_id.unwrap_or(0),
alliance_id: esi_killmail.victim.alliance_id.unwrap_or(0),
ship_id: esi_killmail.victim.ship_type_id.unwrap_or(0),
};
Ok(Killmail {
system_id: esi_killmail.solar_system_id,
victim,
attacker: attackers,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_corporation_kills() {
let zkill = ZkillClient::new();
let kills = zkill.get_corporation_kills(98330748, 86400).unwrap();
println!("Found {} kills", kills.len());
for k in kills.iter().take(5) {
println!("Kill ID: {}, Hash: {}, Value: {}", k.id, k.hash, k.value);
assert!(k.value > 0.0);
assert!(!k.hash.is_empty());
}
}
#[test]
fn test_fetch_killmail_details() {
let zkill = ZkillClient::new();
// Replace with a valid killmail ID & hash for testing
let req = KillmailRequest {
id: 129572369,
hash: "5c95b74245f3fa762dcad7f2dee73581ba885195".to_string(),
value: 52895174.48,
};
let result = zkill.fetch_killmail_details(&req);
match result {
Ok(kill) => {
println!("Kill system ID: {}", kill.system_id);
assert!(kill.attacker.len() > 0 || kill.victim.ship_id > 0);
}
Err(e) => println!("Error fetching killmail: {}", e),
}
}
}