bhcli

"Strange's fork of n0tr1v's bhcli (onion)"
git clone https://git.dasho.dev/Strange/bhcli.git
Log | Files | Refs | README

commit 82497e8b61673f3cebe6e4e1d7443ad86c04b859
parent 67b4d54f6b232d2ec0b0b6fc52677dff3fbe2eca
Author: Strange <StrangeGuy6228@protonmail.com>
Date:   Mon, 24 Jun 2024 16:21:07 +0530

fixed -m for both png/gif capcha MIMEtype + --sxiv to display captcha in sxiv window

Diffstat:
Msrc/lechatphp/mod.rs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/main.rs | 10+++++++++-
2 files changed, 94 insertions(+), 30 deletions(-)

diff --git a/src/lechatphp/mod.rs b/src/lechatphp/mod.rs @@ -1,15 +1,19 @@ -use std::{error, fs, io, thread}; -use std::fmt::{Display, Formatter}; -use std::io::Write; -use std::time::Duration; -use base64::Engine; +use crate::{ + trim_newline, CAPTCHA_FAILED_SOLVE_ERR, CAPTCHA_USED_ERR, CAPTCHA_WG_ERR, KICKED_ERR, LANG, + NICKNAME_ERR, REG_ERR, SERVER_DOWN_500_ERR, SERVER_DOWN_ERR, SESSION_RGX, UNKNOWN_ERR, +}; use base64::engine::general_purpose; +use base64::Engine; use http::StatusCode; use regex::Regex; use reqwest::blocking::Client; use select::document::Document; use select::predicate::{And, Attr, Name}; -use crate::{CAPTCHA_FAILED_SOLVE_ERR, CAPTCHA_USED_ERR, CAPTCHA_WG_ERR, KICKED_ERR, LANG, NICKNAME_ERR, REG_ERR, SERVER_DOWN_500_ERR, SERVER_DOWN_ERR, SESSION_RGX, trim_newline, UNKNOWN_ERR}; +use std::fmt::{Display, Formatter}; +use std::io::Write; +use std::process::{Command, Stdio}; +use std::time::Duration; +use std::{error, fs, io, thread}; pub mod captcha; @@ -60,8 +64,9 @@ pub fn login( username: &str, password: &str, color: &str, - manual_captcha: bool) -> Result<String, LoginErr> -{ + manual_captcha: bool, + sxiv: bool, +) -> Result<String, LoginErr> { // Get login page let login_url = format!("{}/{}", &base_url, &page_php); let resp = client.get(&login_url).send()?; @@ -80,14 +85,33 @@ pub fn login( ("colour", color.to_owned()), ]; - if let Some(captcha_node) = doc.find(And(Name("input"), Attr("name", "challenge"))).next() { + if let Some(captcha_node) = doc + .find(And(Name("input"), Attr("name", "challenge"))) + .next() + { let captcha_value = captcha_node.attr("value").unwrap(); let captcha_img = doc.find(Name("img")).next().unwrap().attr("src").unwrap(); let mut captcha_input = String::new(); if manual_captcha { // Otherwise, save the captcha on disk and prompt user for answer - let img_decoded = general_purpose::STANDARD.decode(captcha_img.strip_prefix("data:image/gif;base64,").unwrap()).unwrap(); + // println!("Captcha image source: {}", captcha_img); + // let img_decoded = general_purpose::STANDARD.decode(captcha_img.strip_prefix("data:image/gif;base64,").unwrap()).unwrap(); + // + // Attempt to strip the appropriate prefix based on the MIME type + let base64_str = + if let Some(base64) = captcha_img.strip_prefix("data:image/png;base64,") { + base64 + } else if let Some(base64) = captcha_img.strip_prefix("data:image/gif;base64,") { + base64 + } else { + panic!("Unexpected captcha image format. Expected PNG or GIF."); + }; + + // Decode the base64 string into binary image data + let img_decoded = general_purpose::STANDARD.decode(base64_str).unwrap(); + + // let img = image::load_from_memory(&img_decoded).unwrap(); let img_buf = image::imageops::resize( &img, @@ -98,15 +122,36 @@ pub fn login( // Save captcha as file on disk img_buf.save("captcha.gif").unwrap(); - termage::display_image("captcha.gif", img.width(), img.height()); + if sxiv { + let mut sxiv_process = Command::new("sxiv") + .arg("captcha.gif") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("Failed to open image with sxiv"); + + // Prompt the user to enter the CAPTCHA + print!("Please enter the CAPTCHA: "); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut captcha_input).unwrap(); + trim_newline(&mut captcha_input); + + // Close the sxiv window + sxiv_process.kill().expect("Failed to close sxiv"); - // Enter captcha - print!("captcha: "); - io::stdout().flush().unwrap(); - io::stdin().read_line(&mut captcha_input).unwrap(); - trim_newline(&mut captcha_input); + println!("Captcha input: {}", captcha_input); + } else { + termage::display_image("captcha.gif", img.width(), img.height()); + + // Enter captcha + print!("captcha: "); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut captcha_input).unwrap(); + trim_newline(&mut captcha_input); + } } else { - captcha_input = captcha::solve_b64(captcha_img).ok_or(LoginErr::CaptchaFailedSolveErr)?; + captcha_input = + captcha::solve_b64(captcha_img).ok_or(LoginErr::CaptchaFailedSolveErr)?; } params.extend(vec![ @@ -122,14 +167,30 @@ pub fn login( _ => {} } - let mut refresh_header = resp.headers().get("refresh").map(|v| v.to_str().unwrap()).unwrap_or(""); + let mut refresh_header = resp + .headers() + .get("refresh") + .map(|v| v.to_str().unwrap()) + .unwrap_or(""); while refresh_header != "" { let rgx = Regex::new(r#"URL=(.+)"#).unwrap(); - let refresh_url = format!("{}{}", base_url, rgx.captures(&refresh_header).unwrap().get(1).unwrap().as_str()); + let refresh_url = format!( + "{}{}", + base_url, + rgx.captures(&refresh_header) + .unwrap() + .get(1) + .unwrap() + .as_str() + ); println!("waitroom enabled, wait 10sec"); thread::sleep(Duration::from_secs(10)); resp = client.get(refresh_url.clone()).send()?; - refresh_header = resp.headers().get("refresh").map(|v| v.to_str().unwrap()).unwrap_or(""); + refresh_header = resp + .headers() + .get("refresh") + .map(|v| v.to_str().unwrap()) + .unwrap_or(""); } let mut resp = resp.text()?; @@ -186,14 +247,10 @@ pub fn logout( client: &Client, base_url: &str, page_php: &str, - session: &str) -> anyhow::Result<()> -{ + session: &str, +) -> anyhow::Result<()> { let full_url = format!("{}/{}", &base_url, &page_php); - let params = [ - ("action", "logout"), - ("session", &session), - ("lang", LANG), - ]; + let params = [("action", "logout"), ("session", &session), ("lang", LANG)]; client.post(&full_url).form(&params).send()?; Ok(()) -} -\ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs @@ -167,6 +167,9 @@ struct Opts { #[arg(long)] session: Option<String>, + + #[arg(long)] + sxiv: bool, } struct LeChatPHPConfig { @@ -176,7 +179,6 @@ struct LeChatPHPConfig { keepalive_send_to: String, members_tag: String, staffs_tag: String, - // session: String } impl LeChatPHPConfig { @@ -205,6 +207,7 @@ struct LeChatPHPClient { config: LeChatPHPConfig, last_key_event: Option<KeyCode>, manual_captcha: bool, + sxiv: bool, refresh_rate: u64, max_login_retry: isize, @@ -496,6 +499,7 @@ impl LeChatPHPClient { return Ok(()); } // println!("self.session is not Some"); + // println!("self.sxiv = {:?}", self.sxiv); self.session = Some(lechatphp::login( &self.client, &self.config.url, @@ -504,6 +508,7 @@ impl LeChatPHPClient { &self.base_client.password, &self.guest_color, self.manual_captcha, + self.sxiv, )?); Ok(()) } @@ -2061,6 +2066,7 @@ fn new_default_le_chat_php_client(params: Params) -> LeChatPHPClient { last_key_event: None, client: params.client, manual_captcha: params.manual_captcha, + sxiv: params.sxiv, refresh_rate: params.refresh_rate, config: LeChatPHPConfig::new_black_hat_chat_config(), is_muted: Arc::new(Mutex::new(false)), @@ -2090,6 +2096,7 @@ struct Params { guest_color: String, client: Client, manual_captcha: bool, + sxiv: bool, refresh_rate: u64, max_login_retry: isize, keepalive_send_to: Option<String>, @@ -2372,6 +2379,7 @@ fn main() -> anyhow::Result<()> { guest_color, client: client.clone(), manual_captcha: opts.manual_captcha, + sxiv: opts.sxiv, refresh_rate: opts.refresh_rate, max_login_retry: opts.max_login_retry, keepalive_send_to: opts.keepalive_send_to,