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"
[dependencies]
clap = { version = "4.5.20", features = ["derive"] }
passterm = "2.0.3"
rand = "0.8.5"
reqwest = { version = "0.12.8", features = ["blocking", "cookies", "json"] }
argp = "0.4.1"
futures-util = "0.3.34"
passterm = "2.1.0"
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_json = "1"
#sled = "0.34.7"
#smallvec = "1.13.2"
tokio = { version = "1", features = [] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
#typed-sled = "0.2.3"

View file

@ -1,37 +1,68 @@
use clap::Parser;
use rand::Rng;
use futures_util::{SinkExt, TryStreamExt};
use rand::RngExt;
use reqwest::{
header::{HeaderMap, HeaderValue},
Method,
};
use reqwest_websocket::{Message, Upgrade};
use std::{collections::HashMap, time::Duration};
const WS: &'static str = "wss://leekwars.com/ws";
const API: &'static str = "https://leekwars.com/api/";
#[derive(Parser)]
#[command(name = "leekwarsbot")]
#[command(bin_name = "leekwarsbot")]
struct Cli {
#[clap(short, long)]
username: String,
/// Number of seconds to wait between each fight
#[clap(short, long, default_value_t = 10)]
delay: u64,
/// Number of fights to start
#[clap(short, long, default_value_t = 1)]
fights: usize,
const WS_GARDEN_BOSS_CREATE_SQUAD: u32 = 66;
const WS_GARDEN_BOSS_JOIN_SQUAD: u32 = 67;
const WS_GARDEN_BOSS_SQUAD: u32 = 76;
const WS_GARDEN_BOSS_SQUAD_JOINED: u32 = 74;
const WS_GARDEN_BOSS_ATTACK: u32 = 71;
const WS_GARDEN_BOSS_LISTEN: u32 = 72;
enum FightType {
Farmer,
Nasu,
}
fn main() {
let cli = Cli::parse();
impl argp::FromArgValue for FightType {
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 mut rng = rand::thread_rng();
let mut rng = rand::rng();
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))
.user_agent("RustyLeekwarsBot")
.build()
@ -45,20 +76,21 @@ fn main() {
.post(format!("{}{}", API, "farmer/login-token"))
.form(&form)
.send()
.await
.unwrap();
//dbg!(resp.status());
//dbg!(resp.headers());
let phpsessid = resp
let hostsess = resp
.cookies()
.find_map(|cookie| {
if cookie.name() == "PHPSESSID" && cookie.value() != "deleted" {
if cookie.name() == "__Host-sess" && cookie.value() != "deleted" {
Some(cookie.value().to_owned())
} else {
None
}
})
.expect("No PHPSESSID from the API!");
.expect("No __Host-sess from the API!");
let token = resp
.cookies()
.find_map(|cookie| {
@ -73,47 +105,164 @@ fn main() {
let mut headers = HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(),
HeaderValue::from_str("Bearer $").unwrap(),
);
headers.insert(
reqwest::header::COOKIE,
HeaderValue::from_str(&format!("token={}; PHPSESSID={}", token, phpsessid)).unwrap(),
HeaderValue::from_str(&format!("token={}; __Host-sess={}", token, hostsess)).unwrap(),
);
for _ in 0..cli.fights {
let resp: serde_json::Value = client
.request(
Method::GET,
format!("{}{}", API, "garden/get-farmer-opponents"),
)
.headers(headers.clone())
.send()
.unwrap()
.json()
match cli.fight_type {
FightType::Farmer => {
for _ in 0..cli.fights {
let resp: serde_json::Value = client
.request(
Method::GET,
format!("{}{}", API, "garden/get-farmer-opponents"),
)
.headers(headers.clone())
.send()
.await
.unwrap()
.json()
.await
.unwrap();
//dbg!(&resp);
let opponent = &resp["opponents"].as_array().unwrap()[rng.random_range(0..5)];
let opponent_name = opponent["name"].as_str().unwrap();
let opponent_id = opponent["id"].as_u64().unwrap();
println!("Start farmer fight against: {opponent_id} `{opponent_name}`");
let mut map = HashMap::<&str, u64>::new();
map.insert("target_id", opponent_id);
let _resp: serde_json::Value = client
.request(
Method::POST,
format!("{}{}", API, "garden/start-farmer-fight"),
)
.headers(headers.clone())
.json(&map)
.send()
.await
.unwrap()
.json()
.await
.unwrap();
//dbg!(resp);
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();
//dbg!(&resp);
let opponent = &resp["opponents"].as_array().unwrap()[rng.gen_range(0..5)];
let opponent_name = opponent["name"].as_str().unwrap();
let opponent_id = opponent["id"].as_u64().unwrap();
println!("Start farmer fight against: {opponent_id} `{opponent_name}`");
let mut map = HashMap::<&str, u64>::new();
map.insert("target_id", opponent_id);
let _resp: serde_json::Value = client
.request(
Method::POST,
format!("{}{}", API, "garden/start-farmer-fight"),
)
.headers(headers.clone())
.json(&map)
.send()
.unwrap()
.json()
let attack_msg = Message::text_from_json(&[serde_json::Value::Number(
serde_json::Number::from(WS_GARDEN_BOSS_ATTACK),
)])
.unwrap();
//dbg!(resp);
std::thread::sleep(delay);
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();
}
}
}
}