Update login, add nasu

This commit is contained in:
Pascal Engélibert 2026-10-23 14:53:40 +02:00
commit 18bc76e2ec
3 changed files with 1068 additions and 616 deletions

1485
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,13 +4,15 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.5.20", features = ["derive"] } argp = "0.4.1"
passterm = "2.0.3" futures-util = "0.3.34"
rand = "0.8.5" passterm = "2.1.0"
reqwest = { version = "0.12.8", features = ["blocking", "cookies", "json"] } rand = "0.10.3"
reqwest = { version = "0.13.5", default-features = false, features = ["blocking", "charset", "cookies", "form", "json", "native-tls", "system-proxy"] }
reqwest-websocket = { version = "0.6.0", features = ["json"] }
#serde = { version = "1", features = ["derive"] } #serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
#sled = "0.34.7" #sled = "0.34.7"
#smallvec = "1.13.2" #smallvec = "1.13.2"
tokio = { version = "1", features = [] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
#typed-sled = "0.2.3" #typed-sled = "0.2.3"

View file

@ -1,37 +1,68 @@
use clap::Parser; use futures_util::{SinkExt, TryStreamExt};
use rand::Rng; use rand::RngExt;
use reqwest::{ use reqwest::{
header::{HeaderMap, HeaderValue}, header::{HeaderMap, HeaderValue},
Method, Method,
}; };
use reqwest_websocket::{Message, Upgrade};
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
const WS: &'static str = "wss://leekwars.com/ws";
const API: &'static str = "https://leekwars.com/api/"; const API: &'static str = "https://leekwars.com/api/";
#[derive(Parser)] const WS_GARDEN_BOSS_CREATE_SQUAD: u32 = 66;
#[command(name = "leekwarsbot")] const WS_GARDEN_BOSS_JOIN_SQUAD: u32 = 67;
#[command(bin_name = "leekwarsbot")] const WS_GARDEN_BOSS_SQUAD: u32 = 76;
struct Cli { const WS_GARDEN_BOSS_SQUAD_JOINED: u32 = 74;
#[clap(short, long)] const WS_GARDEN_BOSS_ATTACK: u32 = 71;
username: String, const WS_GARDEN_BOSS_LISTEN: u32 = 72;
/// Number of seconds to wait between each fight
#[clap(short, long, default_value_t = 10)] enum FightType {
delay: u64, Farmer,
/// Number of fights to start Nasu,
#[clap(short, long, default_value_t = 1)]
fights: usize,
} }
fn main() { impl argp::FromArgValue for FightType {
let cli = Cli::parse(); fn from_arg_value(value: &std::ffi::OsStr) -> Result<Self, String> {
match value.to_str().unwrap() {
"farmer" => Ok(FightType::Farmer),
"nasu" => Ok(FightType::Nasu),
_ => Err("fight type must be one of farmer|nasu".into()),
}
}
}
/// LeekWars bot
#[derive(argp::FromArgs)]
struct Cli {
/// Username
#[argp(positional)]
username: String,
/// Number of seconds to wait between each fight
#[argp(option, short = 'd', default = "10")]
delay: u64,
/// Number of fights to start
#[argp(option, short = 'n', default = "1")]
fights: usize,
/// Fight type
#[argp(option, short = 't', default = "FightType::Farmer")]
fight_type: FightType,
/// Leek id
#[argp(option, short = 'l')]
leek: Option<u32>,
}
#[tokio::main]
async fn main() {
let cli: Cli = argp::parse_args_or_exit(argp::DEFAULT);
let delay = std::time::Duration::from_secs(cli.delay); let delay = std::time::Duration::from_secs(cli.delay);
let mut rng = rand::thread_rng(); let mut rng = rand::rng();
let password = passterm::prompt_password_tty(Some("Password: ")).unwrap(); let password = passterm::prompt_password_tty(Some("Password: ")).unwrap();
let client = reqwest::blocking::Client::builder() let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30)) .timeout(Duration::from_secs(30))
.user_agent("RustyLeekwarsBot") .user_agent("RustyLeekwarsBot")
.build() .build()
@ -45,20 +76,21 @@ fn main() {
.post(format!("{}{}", API, "farmer/login-token")) .post(format!("{}{}", API, "farmer/login-token"))
.form(&form) .form(&form)
.send() .send()
.await
.unwrap(); .unwrap();
//dbg!(resp.status()); //dbg!(resp.status());
//dbg!(resp.headers()); //dbg!(resp.headers());
let phpsessid = resp let hostsess = resp
.cookies() .cookies()
.find_map(|cookie| { .find_map(|cookie| {
if cookie.name() == "PHPSESSID" && cookie.value() != "deleted" { if cookie.name() == "__Host-sess" && cookie.value() != "deleted" {
Some(cookie.value().to_owned()) Some(cookie.value().to_owned())
} else { } else {
None None
} }
}) })
.expect("No PHPSESSID from the API!"); .expect("No __Host-sess from the API!");
let token = resp let token = resp
.cookies() .cookies()
.find_map(|cookie| { .find_map(|cookie| {
@ -73,13 +105,15 @@ fn main() {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(
reqwest::header::AUTHORIZATION, reqwest::header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), HeaderValue::from_str("Bearer $").unwrap(),
); );
headers.insert( headers.insert(
reqwest::header::COOKIE, reqwest::header::COOKIE,
HeaderValue::from_str(&format!("token={}; PHPSESSID={}", token, phpsessid)).unwrap(), HeaderValue::from_str(&format!("token={}; __Host-sess={}", token, hostsess)).unwrap(),
); );
match cli.fight_type {
FightType::Farmer => {
for _ in 0..cli.fights { for _ in 0..cli.fights {
let resp: serde_json::Value = client let resp: serde_json::Value = client
.request( .request(
@ -88,12 +122,14 @@ fn main() {
) )
.headers(headers.clone()) .headers(headers.clone())
.send() .send()
.await
.unwrap() .unwrap()
.json() .json()
.await
.unwrap(); .unwrap();
//dbg!(&resp); //dbg!(&resp);
let opponent = &resp["opponents"].as_array().unwrap()[rng.gen_range(0..5)]; let opponent = &resp["opponents"].as_array().unwrap()[rng.random_range(0..5)];
let opponent_name = opponent["name"].as_str().unwrap(); let opponent_name = opponent["name"].as_str().unwrap();
let opponent_id = opponent["id"].as_u64().unwrap(); let opponent_id = opponent["id"].as_u64().unwrap();
@ -110,10 +146,123 @@ fn main() {
.headers(headers.clone()) .headers(headers.clone())
.json(&map) .json(&map)
.send() .send()
.await
.unwrap() .unwrap()
.json() .json()
.await
.unwrap(); .unwrap();
//dbg!(resp); //dbg!(resp);
std::thread::sleep(delay); std::thread::sleep(delay);
} }
}
FightType::Nasu => {
headers.insert(
reqwest::header::SEC_WEBSOCKET_PROTOCOL,
HeaderValue::from_str("leek-wars, $").unwrap(),
);
headers.insert(
reqwest::header::SEC_WEBSOCKET_EXTENSIONS,
HeaderValue::from_str("permessage-deflate").unwrap(),
);
headers.insert(
reqwest::header::SEC_WEBSOCKET_KEY,
HeaderValue::from_str("mKwpFRJwZHKyJDTUdyhRIA==").unwrap(),
);
headers.insert(
reqwest::header::SEC_WEBSOCKET_VERSION,
HeaderValue::from_str("13").unwrap(),
);
headers.insert(
reqwest::header::HeaderName::from_static("sec-fetch-dest"),
HeaderValue::from_str("empty").unwrap(),
);
headers.insert(
reqwest::header::HeaderName::from_static("sec-fetch-site"),
HeaderValue::from_str("same-origin").unwrap(),
);
headers.insert(
reqwest::header::HeaderName::from_static("sec-fetch-mode"),
HeaderValue::from_str("websocket").unwrap(),
);
headers.insert(
reqwest::header::CACHE_CONTROL,
HeaderValue::from_str("no-cache").unwrap(),
);
headers.insert(
reqwest::header::ORIGIN,
HeaderValue::from_str("https://leekwars.com").unwrap(),
);
let ws_resp = client
.get(WS)
.headers(headers.clone())
.upgrade()
.protocols(["leek-wars"])
.send()
.await
.unwrap();
let mut ws = ws_resp.into_websocket().await.unwrap();
let enter_msg = Message::text_from_json(&[
serde_json::Value::Number(serde_json::Number::from(WS_GARDEN_BOSS_CREATE_SQUAD)),
serde_json::Value::Number(serde_json::Number::from(1)),
serde_json::Value::Bool(true),
serde_json::Value::Array(vec![serde_json::Value::Number(
serde_json::Number::from(cli.leek.unwrap()),
)]),
])
.unwrap();
let attack_msg = Message::text_from_json(&[serde_json::Value::Number(
serde_json::Number::from(WS_GARDEN_BOSS_ATTACK),
)])
.unwrap();
for i in 0..cli.fights {
if i > 0 {
std::thread::sleep(delay);
}
ws.flush().await.unwrap();
ws.send(enter_msg.clone()).await.unwrap();
let mut boss_id = None;
//let mut master_id = None;
while let Some(msg) = ws.try_next().await.unwrap() {
match dbg!(&msg) {
Message::Ping(b) => {
ws.send(Message::Pong(b.clone())).await.unwrap();
}
Message::Text(_) | Message::Binary(_) => {
let msg: serde_json::Value = msg.json().unwrap();
if !msg.is_array() {
continue;
}
let msg = msg.as_array().unwrap();
if msg[0]
== serde_json::Value::Number(serde_json::Number::from(
WS_GARDEN_BOSS_SQUAD,
)) {
boss_id = Some(msg[1].as_object().unwrap()["id"].clone());
//master_id = Some(msg[1].as_object().unwrap()["master"].clone());
} else if msg[0]
== serde_json::Value::Number(serde_json::Number::from(
WS_GARDEN_BOSS_SQUAD_JOINED,
))
&& boss_id.is_some()
{
break;
}
}
_ => {}
}
}
let join_msg = Message::text_from_json(&[
serde_json::Value::Number(serde_json::Number::from(WS_GARDEN_BOSS_JOIN_SQUAD)),
boss_id.unwrap(),
serde_json::Value::Array(vec![serde_json::Value::Number(
serde_json::Number::from(cli.leek.unwrap()),
)]),
])
.unwrap();
ws.send(join_msg).await.unwrap();
ws.send(attack_msg.clone()).await.unwrap();
}
}
}
} }