├── .gitignore ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hashpool_monitor" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | minidom = "0.11.1" 7 | reqwest = "0.9.22" 8 | serde = "1.0.102" 9 | serde_json = "1.0" 10 | regex = "1.3.1" 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate reqwest; 2 | extern crate minidom; 3 | extern crate serde; 4 | extern crate serde_json; 5 | extern crate regex; 6 | 7 | use reqwest::Error; 8 | // use reqwest::header::{Authorization, Basic, Error}; 9 | use serde::{Serialize, Deserialize}; 10 | use serde_json::{Value}; 11 | use regex::Regex; 12 | 13 | #[derive(Default, PartialEq, Debug)] 14 | struct SharePayload { 15 | shares_15m: f64, 16 | shares_1d: f64 17 | } 18 | 19 | #[derive(Serialize, Deserialize)] 20 | struct HuobiPoolResp { 21 | code: u32, 22 | data: HuobiPoolData, 23 | message: String, 24 | success: bool 25 | } 26 | 27 | #[derive(Serialize, Deserialize)] 28 | struct HuobiPoolData { 29 | speed_f: String, 30 | speed_s: String, 31 | speed_t: String 32 | } 33 | 34 | #[derive(Serialize, Deserialize)] 35 | struct BtcPoolResp { 36 | err_no: u32, 37 | data: BtcPoolData 38 | } 39 | 40 | #[derive(Serialize, Deserialize)] 41 | struct BtcPoolData { 42 | shares_15m: Value, 43 | shares_15m_unit: String, 44 | shares_1d: Value, 45 | shares_1d_unit: String 46 | } 47 | 48 | fn monitor_btcpool(url: &str) -> Result { 49 | // https://pool.btc.com/dashboard?access_key=r_qQPTVtQcZDylU&puid=435429 50 | let body: String = reqwest::get(url)?.text()?; 51 | let obj: BtcPoolResp = serde_json::from_str(&body).unwrap(); 52 | 53 | println!("=== btcpool ==="); 54 | // println!("shares_15m:{}{}, shares_1d:{}{}", 55 | // obj.data.shares_15m, obj.data.shares_15m_unit, 56 | // obj.data.shares_1d, obj.data.shares_1d_unit); 57 | 58 | let mut shares_15m; 59 | if obj.data.shares_15m.is_string() { 60 | shares_15m = obj.data.shares_15m.as_str().unwrap().parse::().unwrap(); 61 | }else{ 62 | shares_15m = obj.data.shares_15m.as_f64().unwrap(); 63 | } 64 | 65 | let mut shares_1d; 66 | if obj.data.shares_1d.is_string() { 67 | shares_1d = obj.data.shares_1d.as_str().unwrap().parse::().unwrap(); 68 | }else{ 69 | shares_1d = obj.data.shares_1d.as_f64().unwrap(); 70 | } 71 | 72 | match obj.data.shares_15m_unit.as_ref() { 73 | "T" => {shares_15m /= 1024.0}, 74 | _ => {}, 75 | } 76 | 77 | match obj.data.shares_1d_unit.as_ref() { 78 | "T" => {shares_1d /= 1024.0}, 79 | _ => {}, 80 | } 81 | 82 | let payload = SharePayload { 83 | shares_15m: shares_15m, 84 | shares_1d: shares_1d 85 | }; 86 | 87 | Ok(payload) 88 | } 89 | 90 | #[derive(Serialize, Deserialize)] 91 | struct SpiderPoolResp { 92 | code: String, 93 | data: SpiderPoolWorker 94 | } 95 | 96 | #[allow(non_snake_case)] 97 | #[derive(Serialize, Deserialize)] 98 | struct SpiderPoolWorker { 99 | hashrate15Fmt: Option, 100 | hashrate1440Fmt: Option 101 | } 102 | 103 | #[derive(Serialize, Deserialize)] 104 | struct SpiderPoolHashData { 105 | value: f64, 106 | unit: String 107 | } 108 | 109 | fn monitor_spiderpool(url: &str) -> Result { 110 | // https://www.spiderpool.com/coin/show/btc/bmytest1/detail.html 111 | let re = Regex::new(r"(?m)https://www.spiderpool.com/coin/show/btc/(?P[a-zA-Z0-9]+?)/detail.html").unwrap(); 112 | let caps = re.captures(url).unwrap(); 113 | let account = &caps["account"].to_lowercase(); 114 | 115 | let api_url = format!("https://btc.api.spiderpool.com:19101/v1/subaccount/status?coin=btc&subaccount={}", account); 116 | let client = reqwest::Client::new(); 117 | let body: String = client.get(&api_url).send()?.text()?; 118 | // println!("{}", body); 119 | 120 | // let body: String = reqwest::get(api_url)?.text()?; 121 | let obj: SpiderPoolResp = serde_json::from_str(&body).expect("have no data."); 122 | println!("=== spiderpool ==="); 123 | 124 | let mut shares_15m = 0.0; 125 | if obj.data.hashrate15Fmt.is_some(){ 126 | let hashrate15Fmt = obj.data.hashrate15Fmt.unwrap(); 127 | shares_15m = hashrate15Fmt.value; 128 | match hashrate15Fmt.unit.as_ref() { 129 | "TH" => {shares_15m /= 1024.0}, 130 | _ => {}, 131 | } 132 | } 133 | 134 | let mut shares_1d = 0.0; 135 | if obj.data.hashrate1440Fmt.is_some(){ 136 | let hashrate1440Fmt = obj.data.hashrate1440Fmt.unwrap(); 137 | shares_1d = hashrate1440Fmt.value; 138 | match hashrate1440Fmt.unit.as_ref() { 139 | "TH" => {shares_1d /= 1024.0}, 140 | _ => {}, 141 | } 142 | } 143 | 144 | let payload = SharePayload { 145 | shares_15m: shares_15m, 146 | shares_1d: shares_1d 147 | }; 148 | 149 | Ok(payload) 150 | } 151 | 152 | #[derive(Serialize, Deserialize)] 153 | struct PoolinResp { 154 | err_no: u32, 155 | data: PoolinHashData 156 | } 157 | 158 | #[derive(Serialize, Deserialize)] 159 | struct PoolinHashData { 160 | workers_active: u32, 161 | workers_inactive: u32, 162 | workers_dead: u32, 163 | workers_total: u32, 164 | shares_15m: f64, 165 | shares_24h: f64, 166 | shares_unit: String, 167 | } 168 | 169 | fn monitor_poolin(url: &str) -> Result { 170 | //https://www.poolin.com/my/9052047/btc/miners?read_token=wowkUwojrU5GAwJXmEn30tqpRvmBwdPdAKR6paQn2AMZbZr2I3yrYa6usqvrLOHa&status=ACTIVE 171 | let re = Regex::new(r"(?m)https://[[:ascii:]]+?/my/(?P\d+?)/btc/miners\?read_token=(?P[a-zA-Z0-9_]+)&status=ACTIVE").unwrap(); 172 | let caps = re.captures(url).unwrap(); 173 | 174 | // println!("{:#?}", caps); 175 | // let reader_token = r"wowkUwojrU5GAwJXmEn30tqpRvmBwdPdAKR6paQn2AMZbZr2I3yrYa6usqvrLOHa"; 176 | // println!("{} {}", puid, reader_token); 177 | let puid = &caps["puid"]; 178 | let reader_token = &caps["reader_token"]; 179 | 180 | // https://api-prod.poolin.com/api/public/v2/worker/stats?puid=9052047&coin_type=btc 181 | let api_url = format!("https://api-prod.poolin.com/api/public/v2/worker/stats?puid={}&coin_type=btc", puid); 182 | let client = reqwest::Client::new(); 183 | let body: String = client.get(&api_url).bearer_auth(reader_token).send()?.text()?; 184 | let obj: PoolinResp = serde_json::from_str(&body).unwrap(); 185 | 186 | println!("=== poolin ==="); 187 | // println!("workers_active {}", obj.data.workers_active); 188 | // println!("shares_15m {}", obj.data.shares_15m); 189 | // println!("shares_24h {}", obj.data.shares_24h); 190 | 191 | let mut shares_15m = obj.data.shares_15m; 192 | match obj.data.shares_unit.as_ref() { 193 | "T" => {shares_15m /= 1024.0}, 194 | _ => {}, 195 | } 196 | 197 | let mut shares_1d = obj.data.shares_24h; 198 | match obj.data.shares_unit.as_ref() { 199 | "T" => {shares_1d /= 1024.0}, 200 | _ => {}, 201 | } 202 | 203 | let payload = SharePayload { 204 | shares_15m: shares_15m, 205 | shares_1d: shares_1d 206 | }; 207 | 208 | Ok(payload) 209 | } 210 | 211 | fn monitor_huobipool(url: &str) -> Result { 212 | // https://www.huobipool.com/pow/miners?signature=n4lnoQ2-mra7NoJxoRbEdA 213 | // "data":{"speed_f":"0.0000000000","speed_s":"0.0000000000","speed_t":"0.0000000000"}, 214 | let body: String = reqwest::get(url)?.text()?; 215 | let obj: HuobiPoolResp = serde_json::from_str(&body).unwrap(); 216 | 217 | let payload = SharePayload { 218 | shares_15m: obj.data.speed_f.parse::().unwrap(), 219 | shares_1d: obj.data.speed_t.parse::().unwrap() 220 | }; 221 | // println!("speed_f {}", format_hashrate(obj.data.speed_f.parse().unwrap())); 222 | // println!("speed_s {}", format_hashrate(obj.data.speed_s.parse().unwrap())); 223 | // println!("speed_t {}", format_hashrate(obj.data.speed_t.parse().unwrap())); 224 | 225 | Ok(payload) 226 | } 227 | 228 | #[allow(non_snake_case)] 229 | #[derive(Serialize, Deserialize)] 230 | struct AntPoolResp { 231 | userGroupList: String, 232 | userWorkerList: AntPoolUserWorker 233 | } 234 | 235 | #[allow(non_snake_case)] 236 | #[derive(Serialize, Deserialize)] 237 | struct AntPoolUserWorker { 238 | useWorkerId: String, 239 | hsLash1d: String, 240 | hsLash1h: String, 241 | hsLast5m: String, 242 | rejectRate: f64, 243 | } 244 | 245 | #[allow(dead_code)] 246 | fn monitor_antpool(url: &str) -> Result { 247 | // https://www.antpool.com/observer.htm?accessKey=3iaNEfO5tm4y0iLLGaiO&coinType=BTC 248 | println!("=== antpool ==="); 249 | 250 | let body: String = reqwest::get(url)?.text()?; 251 | let _obj: AntPoolResp = serde_json::from_str(&body).unwrap(); 252 | 253 | let payload = SharePayload { 254 | shares_15m: 0.0, 255 | shares_1d: 0.0 256 | }; 257 | // println!("speed_f {}", format_hashrate(obj.data.speed_f.parse().unwrap())); 258 | // println!("speed_s {}", format_hashrate(obj.data.speed_s.parse().unwrap())); 259 | // println!("speed_t {}", format_hashrate(obj.data.speed_t.parse().unwrap())); 260 | 261 | Ok(payload) 262 | } 263 | 264 | fn main() -> Result<(), Error> { 265 | let mut total_share = SharePayload::default(); 266 | 267 | println!("=== wangp001 280T ==="); 268 | // _ret = monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_R6O5Znbu62Rbc&puid=438908"); 269 | match monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_R6O5Znbu62Rbc&puid=438908") { 270 | Ok(payload) => { 271 | println!("payload: {:?}", payload); 272 | total_share.shares_15m += payload.shares_15m; 273 | total_share.shares_1d += payload.shares_1d; 274 | }, 275 | Err(err) => eprintln!("error: {}", err), 276 | }; 277 | 278 | println!("=== qm001 1400T ==="); 279 | // _ret = monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_yPX9uJAUr1O3y&puid=438907"); 280 | match monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_yPX9uJAUr1O3y&puid=438907") { 281 | Ok(payload) => { 282 | println!("payload: {:?}", payload); 283 | total_share.shares_15m += payload.shares_15m; 284 | total_share.shares_1d += payload.shares_1d; 285 | }, 286 | Err(err) => eprintln!("error: {}", err), 287 | }; 288 | 289 | println!("=== YY321 3570T ==="); 290 | // _ret = monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_zeMvLldrHZBNG&puid=437981"); 291 | match monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_zeMvLldrHZBNG&puid=437981") { 292 | Ok(payload) => { 293 | println!("payload: {:?}", payload); 294 | total_share.shares_15m += payload.shares_15m; 295 | total_share.shares_1d += payload.shares_1d; 296 | }, 297 | Err(err) => eprintln!("error: {}", err), 298 | }; 299 | 300 | println!("=== ZW321 350T ==="); 301 | // https://pool.btc.com/dashboard?access_key=r_a0xiXY5TCNsY7&puid=443548 302 | match monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_a0xiXY5TCNsY7&puid=443548") { 303 | Ok(payload) => { 304 | println!("payload: {:?}", payload); 305 | total_share.shares_15m += payload.shares_15m; 306 | total_share.shares_1d += payload.shares_1d; 307 | }, 308 | Err(err) => eprintln!("error: {}", err), 309 | }; 310 | 311 | // println!("=== Yan09 0T ==="); 312 | // // _ret = monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_Dg8RMLPjB7V7h&puid=438684"); 313 | // match monitor_btcpool("https://pool.btc.com/v1/realtime/hashrate?access_key=r_Dg8RMLPjB7V7h&puid=438684") { 314 | // Ok(payload) => { 315 | // println!("payload: {:?}", payload); 316 | // total_share.shares_15m += payload.shares_15m; 317 | // total_share.shares_1d += payload.shares_1d; 318 | // }, 319 | // Err(err) => eprintln!("error: {}", err), 320 | // }; 321 | 322 | println!("=== bmytest1 2030T ==="); 323 | match monitor_spiderpool("https://www.spiderpool.com/coin/show/btc/bmytest1/detail.html") { 324 | Ok(payload) => { 325 | println!("payload: {:?}", payload); 326 | total_share.shares_15m += payload.shares_15m; 327 | total_share.shares_1d += payload.shares_1d; 328 | }, 329 | Err(err) => eprintln!("error: {}", err), 330 | }; 331 | 332 | println!("total_share: {:?}", total_share); 333 | 334 | // ######################################### 335 | 336 | total_share = SharePayload::default(); 337 | 338 | println!("=== gaopengpeng0809 1P ==="); 339 | match monitor_spiderpool("https://www.spiderpool.com/coin/show/btc/gaopengpeng0809/detail.html") { 340 | Ok(payload) => { 341 | println!("payload: {:?}", payload); 342 | total_share.shares_15m += payload.shares_15m; 343 | total_share.shares_1d += payload.shares_1d; 344 | }, 345 | Err(err) => eprintln!("error: {}", err), 346 | }; 347 | 348 | println!("=== Funny 3.5P ==="); 349 | match monitor_spiderpool("https://www.spiderpool.com/coin/show/btc/Funny/detail.html") { 350 | Ok(payload) => { 351 | println!("payload: {:?}", payload); 352 | total_share.shares_15m += payload.shares_15m; 353 | total_share.shares_1d += payload.shares_1d; 354 | }, 355 | Err(err) => eprintln!("error: {}", err), 356 | }; 357 | 358 | println!("=== yibobtc06 0.5P ==="); 359 | match monitor_spiderpool("https://www.spiderpool.com/coin/show/btc/yibobtc06/detail.html") { 360 | Ok(payload) => { 361 | println!("payload: {:?}", payload); 362 | total_share.shares_15m += payload.shares_15m; 363 | total_share.shares_1d += payload.shares_1d; 364 | }, 365 | Err(err) => eprintln!("error: {}", err), 366 | }; 367 | 368 | println!("total_share: {:?}", total_share); 369 | // ######################################### 370 | 371 | total_share = SharePayload::default(); 372 | 373 | println!("=== huobipool 10T ==="); 374 | match monitor_huobipool("https://www.huobipool.com/p4/pow/sub_user_speed?visitor_path=n4lnoQ2-mra7NoJxoRbEdA¤cy=btc") { 375 | Ok(payload) => { 376 | println!("payload: {:?}", payload); 377 | total_share.shares_15m += payload.shares_15m; 378 | total_share.shares_1d += payload.shares_1d; 379 | }, 380 | Err(err) => eprintln!("error: {}", err), 381 | }; 382 | 383 | println!("=== poolin 20T ==="); 384 | match monitor_poolin("https://www.poolin.com/my/9052047/btc/miners?read_token=wowkUwojrU5GAwJXmEn30tqpRvmBwdPdAKR6paQn2AMZbZr2I3yrYa6usqvrLOHa&status=ACTIVE") { 385 | Ok(payload) => { 386 | println!("payload: {:?}", payload); 387 | total_share.shares_15m += payload.shares_15m; 388 | total_share.shares_1d += payload.shares_1d; 389 | }, 390 | Err(err) => eprintln!("error: {}", err), 391 | }; 392 | 393 | // println!("=== antpool 20T ==="); 394 | // match monitor_antpool("https://www.antpool.com/observer.htm?m=minerManage&accessKey=3iaNEfO5tm4y0iLLGaiO") { 395 | // Ok(payload) => { 396 | // println!("payload: {:?}", payload); 397 | // total_share.shares_15m += payload.shares_15m; 398 | // total_share.shares_1d += payload.shares_1d; 399 | // }, 400 | // Err(err) => eprintln!("error: {}", err), 401 | // }; 402 | // 403 | // println!("total_share: {:?}", total_share); 404 | 405 | Ok(()) 406 | } 407 | --------------------------------------------------------------------------------