1use clawser_browser::Browser;
5use std::time::Instant;
6
7const PER_ROUND: usize = 3;
8const ROUNDS: usize = 10;
9const BROWSER_LIVE_SECS: u64 = 30;
10const ROTATE_COOLDOWN_SECS: u64 = 5;
11
12const PROXY_KEY: &str = "h9f015c1zpu65df90452b6349a63a9ac2d7bf15be1727e453ab6013";
13
14const USERS: [&str; 30] = [
15 "chokevy", "phanminhtu", "lehoangnam", "nguyenvana",
16 "tranthimai", "vothanhdat", "buiquochuy", "dangtuanhung",
17 "lythihuong", "hoanganhduc", "ngothikieu", "phamduclong",
18 "truongmylinh", "doanthanhson", "luuquanghai", "nguyenthilan",
19 "vutrongnhan", "lequangvinh", "tranductho", "phamthingoc",
20 "hothienphuc", "nguyenkhoa", "vuthanhtrung", "buithihoa",
21 "dangquocbao", "lethimynhi", "phanducminh", "truongankhang",
22 "nguyenhaiyen", "vothianhthy",
23];
24
25fn api_url() -> String {
26 std::env::var("GAME_API_URL").unwrap_or_else(|_| "https://api.abb1211.com/endpoint/play".into())
27}
28fn api_token() -> String {
29 std::env::var("GAME_API_TOKEN").expect("Set GAME_API_TOKEN in .env")
30}
31
32fn load_dotenv() {
33 for path in ["clawser-browser/.env", ".env"] {
34 if let Ok(content) = std::fs::read_to_string(path) {
35 for line in content.lines() {
36 let line = line.trim();
37 if line.is_empty() || line.starts_with('#') { continue; }
38 if let Some((k, v)) = line.split_once('=') {
39 std::env::set_var(k.trim(), v.trim());
40 }
41 }
42 break;
43 }
44 }
45}
46
47async fn rotate_and_get_proxy() -> Option<String> {
48 let client = reqwest::Client::new();
49
50 let url = format!("https://api.zingproxy.com/open/change-ip/{}", PROXY_KEY);
52 match client.get(&url).send().await {
53 Ok(resp) => {
54 let body: serde_json::Value = resp.json().await.unwrap_or_default();
55 let msg = body["message"].as_str().unwrap_or("?");
56 println!(" Rotate: {msg}");
57 }
58 Err(e) => { println!(" Rotate FAIL: {e}"); return None; }
59 }
60
61 tokio::time::sleep(std::time::Duration::from_secs(ROTATE_COOLDOWN_SECS)).await;
62
63 let url = format!("https://api.zingproxy.com/open/get-proxy/{}", PROXY_KEY);
65 match client.get(&url).send().await {
66 Ok(resp) => {
67 let body: serde_json::Value = resp.json().await.unwrap_or_default();
68 if let Some(http) = body["proxy"]["httpProxy"].as_str() {
70 let parts: Vec<&str> = http.splitn(4, ':').collect();
71 if parts.len() >= 2 {
72 let proxy = format!("http://{}:{}", parts[0], parts[1]);
73 println!(" Proxy: {proxy}");
74 return Some(proxy);
75 }
76 }
77 println!(" Get proxy FAIL: {body}");
78 None
79 }
80 Err(e) => { println!(" Get proxy FAIL: {e}"); None }
81 }
82}
83
84async fn verify_proxy(proxy: &str) -> bool {
86 let proxy_obj = match reqwest::Proxy::all(proxy) {
87 Ok(p) => p,
88 Err(e) => { println!(" Verify FAIL (bad proxy url): {e}"); return false; }
89 };
90 let client = match reqwest::Client::builder()
91 .proxy(proxy_obj)
92 .timeout(std::time::Duration::from_secs(10))
93 .build()
94 {
95 Ok(c) => c,
96 Err(e) => { println!(" Verify FAIL (client): {e}"); return false; }
97 };
98 match client.get("http://httpbin.org/ip").send().await {
100 Ok(resp) => {
101 let body = resp.text().await.unwrap_or_default();
102 println!(" Verify OK: {}", body.trim());
103 true
104 }
105 Err(e) => { println!(" Verify FAIL: {e}"); false }
106 }
107}
108
109#[tokio::main]
110async fn main() {
111 load_dotenv();
112 let total = PER_ROUND * ROUNDS;
113 println!("=== Proxy batch: {}×{} = {} | live {}s | rotate {}s cooldown ===\n",
114 PER_ROUND, ROUNDS, total, BROWSER_LIVE_SECS, ROTATE_COOLDOWN_SECS);
115
116 let start = Instant::now();
117 let mut ok = 0u32;
118 let mut fail = 0u32;
119
120 for round in 0..ROUNDS {
121 println!("--- Round {}/{} ---", round + 1, ROUNDS);
122
123 let proxy = match rotate_and_get_proxy().await {
125 Some(p) => p,
126 None => {
127 println!(" Skip round — no proxy");
128 fail += PER_ROUND as u32;
129 continue;
130 }
131 };
132
133 if !verify_proxy(&proxy).await {
135 println!(" Skip round — proxy dead");
136 fail += PER_ROUND as u32;
137 continue;
138 }
139
140 let offset = round * PER_ROUND;
142 let mut handles = Vec::new();
143
144 for i in 0..PER_ROUND {
145 let idx = offset + i;
146 let user_id = USERS[idx % USERS.len()].to_string();
147 let proxy = proxy.clone();
148 let api = api_url();
149 let token = api_token();
150
151 handles.push(tokio::spawn(async move {
152 let label = format!("[{:02}/{}]", idx + 1, total);
153 let t = Instant::now();
154
155 let game_url = match reqwest::Client::new()
157 .post(&api)
158 .header("Authorization", format!("Bearer {}", token))
159 .header("Content-Type", "application/json")
160 .body(format!(r#"{{"user_id": "{}"}}"#, user_id))
161 .send().await
162 {
163 Ok(resp) => {
164 let body: serde_json::Value = resp.json().await.unwrap_or_default();
165 match body["url"].as_str() {
166 Some(u) if !u.is_empty() => u.to_string(),
167 _ => return format!("{label} FAIL api: {body}"),
168 }
169 }
170 Err(e) => return format!("{label} FAIL api: {e}"),
171 };
172 let api_ms = t.elapsed().as_millis();
173
174 let browser = match Browser::builder()
176 .headful()
177 .random()
178 .proxy(&proxy)
179 .build().await
180 {
181 Ok(b) => b,
182 Err(e) => return format!("{label} FAIL launch: {e}"),
183 };
184 let launch_ms = t.elapsed().as_millis() - api_ms;
185
186 let page = match browser.new_page("about:blank").await {
188 Ok(p) => p,
189 Err(e) => return format!("{label} FAIL page: {e}"),
190 };
191
192 let scroll_page = page.cdp().clone();
194 let scroll_handle = tokio::spawn(async move {
195 use chromiumoxide::cdp::browser_protocol::input::{
196 DispatchMouseEventParams, DispatchMouseEventType,
197 };
198 let mut y = 300.0f64;
199 loop {
200 let mut ev = DispatchMouseEventParams::new(
202 DispatchMouseEventType::MouseWheel, 500.0, y,
203 );
204 ev.delta_x = Some(0.0);
205 ev.delta_y = Some(300.0);
206 let _ = scroll_page.execute(ev).await;
207 y = ((y + 50.0) % 800.0) + 100.0;
208 tokio::time::sleep(std::time::Duration::from_millis(800)).await;
209 }
210 });
211
212 let t_nav = Instant::now();
213 if let Err(e) = page.goto(&game_url).await {
214 scroll_handle.abort();
215 return format!("{label} FAIL nav: {e}");
216 }
217 let nav_ms = t_nav.elapsed().as_millis();
218
219 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
221
222 let cookies = page.cdp().get_cookies().await.unwrap_or_default();
223 let has_evo = cookies.iter().any(|c| c.name == "EVOSESSIONID");
224 let evo = cookies.iter().find(|c| c.name == "EVOSESSIONID")
225 .map(|c| c.value.clone())
226 .unwrap_or_else(|| "N/A".into());
227 let cookie_names: Vec<String> = cookies.iter().map(|c| c.name.clone()).collect();
228
229 let elapsed = t.elapsed().as_secs();
231 if elapsed < BROWSER_LIVE_SECS {
232 tokio::time::sleep(std::time::Duration::from_secs(BROWSER_LIVE_SECS - elapsed)).await;
233 }
234
235 scroll_handle.abort();
237 let _ = browser.close().await;
238 let total_ms = t.elapsed().as_millis();
239
240 let status = if has_evo { "OK" } else { "MISS" };
241 format!("{label} {status} | {user_id:<18} | total:{total_ms}ms api:{api_ms}ms launch:{launch_ms}ms nav:{nav_ms}ms | cookies:{} | EVO:{evo}\n {:?}",
242 cookies.len(), cookie_names)
243 }));
244 }
245
246 for h in handles {
247 match h.await {
248 Ok(msg) => {
249 if msg.contains("] OK ") { ok += 1; } else { fail += 1; }
250 println!("{msg}");
251 }
252 Err(e) => { println!(" task panic: {e}"); fail += 1; }
253 }
254 }
255
256 println!();
257 }
258
259 let elapsed = start.elapsed().as_secs_f64();
260 println!("=== OK:{ok} FAIL:{fail} | {elapsed:.0}s total | {:.1}s/profile ===",
261 elapsed / total as f64);
262}