bhcli

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

commit e37498bb0e87fceacb59a152493d9bce21564518
parent 818b57435449883a0fe798dfa2056880fab5dea6
Author: Strange <StrangeGuy6228@protonmail.com>
Date:   Sun, 21 Apr 2024 19:01:52 +0530

BHC 1st captcha removed => completely ripped ClientType:BHC&DAN, either provide the url with -url or defuault url=blkhat.onion

Diffstat:
Msrc/main.rs | 290++++++-------------------------------------------------------------------------
1 file changed, 21 insertions(+), 269 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -18,7 +18,6 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use http::StatusCode; use lazy_static::lazy_static; use linkify::LinkFinder; use log::LevelFilter; @@ -37,7 +36,6 @@ use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::Cursor; use std::io::{self, Write}; -use std::process; use std::process::Command; use std::sync::Mutex; use std::sync::{Arc, MutexGuard}; @@ -64,9 +62,6 @@ const SEND_TO_STAFFS: &str = "s %"; const SEND_TO_ADMINS: &str = "s _"; const SOUND1: &[u8] = include_bytes!("sound1.mp3"); const DKF_URL: &str = "http://dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion"; -const BHCLI_BLOG_URL: &str = - "http://dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/bhcli"; -// const BAN_IMPOSTERS: bool = true; const SERVER_DOWN_500_ERR: &str = "500 Internal Server Error, server down"; const SERVER_DOWN_ERR: &str = "502 Bad Gateway, server down"; const KICKED_ERR: &str = "You have been kicked"; @@ -76,9 +71,6 @@ const CAPTCHA_WG_ERR: &str = "Wrong Captcha"; const CAPTCHA_FAILED_SOLVE_ERR: &str = "Failed solve captcha"; const CAPTCHA_USED_ERR: &str = "Captcha already used or timed out"; const UNKNOWN_ERR: &str = "Unknown error"; -const N0TR1V: &str = "n0tr1v"; -const STUXNET: &str = "STUXNET"; -// const FAGGOT: &str = "faggot"; const DNMX_URL: &str = "http://hxuzjtocnzvv5g2rtg2bhwkcbupmk7rclb6lly3fo4tvqkk5oyrv3nid.onion"; lazy_static! { @@ -189,17 +181,6 @@ impl LeChatPHPConfig { staffs_tag: "[Staff] ".to_owned(), } } - - fn new_dans_chat_config() -> Self { - Self { - url: "http://danschat356lctri3zavzh6fbxg2a7lo6z3etgkctzzpspewu7zdsaqd.onion".to_owned(), - datetime_fmt: "%d-%m %H:%M:%S".to_owned(), - page_php: "chat.php".to_owned(), - keepalive_send_to: None, - members_tag: "[Members] ".to_owned(), - staffs_tag: "[Staff] ".to_owned(), - } - } } struct BaseClient { @@ -208,7 +189,6 @@ struct BaseClient { } struct LeChatPHPClient { - chat_type: ClientType, base_client: BaseClient, guest_color: String, client: Client, @@ -243,27 +223,34 @@ impl LeChatPHPClient { | LoginErr::NicknameErr | LoginErr::UnknownErr => { log::error!("{}", e); + println!("Login error: {}", e); // Print error message break; } LoginErr::CaptchaFailedSolveErr => { log::error!("{}", e); + println!("Captcha failed to solve: {}", e); // Print error message continue; } LoginErr::CaptchaWgErr | LoginErr::CaptchaUsedErr => {} LoginErr::ServerDownErr | LoginErr::ServerDown500Err => { log::error!("{}", e); + println!("Server is down: {}", e); // Print error message } LoginErr::Reqwest(err) => { if err.is_connect() { log::error!("{}\nIs tor proxy enabled ?", err); + println!("Connection error: {}\nIs tor proxy enabled ?", err); // Print error message break; } else if err.is_timeout() { log::error!("timeout: {}", err); + println!("Timeout error: {}", err); // Print error message } else { log::error!("{}", err); + println!("Reqwest error: {}", err); // Print error message } } }, + Ok(()) => { attempt = 0; match self.get_msgs() { @@ -365,7 +352,6 @@ impl LeChatPHPClient { let is_muted = Arc::clone(&self.is_muted); let exit_rx = sig.lock().unwrap().clone(); let sig = Arc::clone(sig); - let tx = self.tx.clone(); let members_tag = self.config.members_tag.clone(); thread::spawn(move || loop { let (_stream, stream_handle) = OutputStream::try_default().unwrap(); @@ -378,7 +364,6 @@ impl LeChatPHPClient { &page_php, &session, &username, - &tx, &users, &sig, &messages_updated_tx, @@ -449,6 +434,7 @@ impl LeChatPHPClient { app.members_tag = self.config.members_tag.clone(); app.staffs_tag = self.config.staffs_tag.clone(); + // process() // Draw UI terminal.draw(|f| { draw_terminal_frame(f, &mut app, &messages, &users, &self.base_client.username); @@ -499,98 +485,6 @@ impl LeChatPHPClient { if self.session.is_some() { return Ok(()); } - if self.chat_type == ClientType::BHC { - //let mut resp_txt = self.client.get(&self.config.url).send().unwrap().text().unwrap(); - let mut resp_txt = match self.client.get(&self.config.url).send() { - Ok(response) => match response.text() { - Ok(text) => text, - Err(err) => { - println!("Error: {}\nCheck Your Tor Connection", err); - process::exit(0); - } - }, - Err(err) => { - println!("Error: {}\nCheck Your Tor Connection", err); - process::exit(0); - } - }; - let doc = Document::from(resp_txt.as_str()); - if let Some(meta) = doc.find(Name("meta")).next() { - let meta_content = meta - .attr("content") - .context("meta content not found") - .unwrap() - .to_owned(); - let index_url = META_REFRESH_RGX.captures(&meta_content).unwrap()[1].to_owned(); - resp_txt = self - .client - .get(format!("{}/{}", &self.config.url, index_url)) - .send() - .unwrap() - .text() - .unwrap(); - } - let doc = Document::from(resp_txt.as_str()); - let form_action = doc - .find(Name("form")) - .next() - .unwrap() - .attr("action") - .context("form action not found") - .unwrap() - .to_owned(); - let captcha_url = doc - .find(Name("img")) - .next() - .unwrap() - .attr("src") - .context("img src not found") - .unwrap() - .to_owned(); - - let captcha_bytes = self - .client - .get(format!("{}/{}", &self.config.url, captcha_url)) - .send() - .unwrap() - .bytes() - .unwrap(); - let img = image::load_from_memory(&captcha_bytes).unwrap(); - let img_buf = image::imageops::resize( - &img, - img.width(), - img.height(), - image::imageops::FilterType::Nearest, - ); - // Save captcha as file on disk - img_buf.save("captcha.gif").unwrap(); - termage::display_image("captcha.gif", img.width(), img.height()); - // Enter captcha - print!("captcha: "); - let mut captcha_input = String::new(); - io::stdout().flush().unwrap(); - io::stdin().read_line(&mut captcha_input).unwrap(); - trim_newline(&mut captcha_input); - let params = vec![ - ("input", captcha_input), - ("submit", "Enter+Chat".to_owned()), - ]; - let resp = self - .client - .post(format!("{}/{}", &self.config.url, form_action)) - .form(&params) - .send() - .unwrap(); - if resp.status() != StatusCode::SEE_OTHER { - return Err(LoginErr::CaptchaWgErr); - } - self.config.page_php = resp - .headers() - .get("location") - .map(|v| v.to_str().unwrap()) - .unwrap_or(&self.config.page_php) - .to_string(); - } self.session = Some(lechatphp::login( &self.client, &self.config.url, @@ -1235,22 +1129,6 @@ impl LeChatPHPClient { } } - // fn handle_normal_mode_key_event_debug(&mut self, app: &mut App) { - // if let Some(idx) = app.items.state.selected() { - // if let Some(item) = app.items.items.get(idx) { - // log::error!("{:?}", item.text.text()); - // } - // } - // } - - // fn handle_normal_mode_key_event_debug2(&mut self, app: &mut App) { - // if let Some(idx) = app.items.state.selected() { - // if let Some(item) = app.items.items.get(idx) { - // log::error!("{:?} {:?}", item.text, item.upload_link); - // } - // } - // } - fn handle_normal_mode_key_event_toggle_mute(&mut self) { let mut is_muted = self.is_muted.lock().unwrap(); *is_muted = !*is_muted; @@ -1425,20 +1303,11 @@ impl LeChatPHPClient { let input: String = app.input.drain(..).collect(); app.input_idx = 0; - if app.commands.commands.is_empty() { - log::error!("Commands hashmap is empty!"); - } else { - for (command, action) in &app.commands.commands { - log::error!("command: {}, action: {}", command, action); - } - } - // Iterate over commands and execute associated actions for (command, action) in &app.commands.commands { - log::error!("command :{} action :{}", command, action); + // log::error!("command :{} action :{}", command, action); let expected_input = format!("!{}", command); if input == expected_input { - log::error!("inside if"); // Execute the action by posting a message self.post_msg(PostType::Post(action.clone(), None)).unwrap(); // Return Ok(()) if the action is executed successfully @@ -1549,7 +1418,7 @@ impl LeChatPHPClient { "This is your warning - {}, will be kicked next !rules", msg ); - log::error!("The Strange end_msg is :{}", end_msg); + // log::error!("The Strange end_msg is :{}", end_msg); self.post_msg(PostType::Post(end_msg, None)).unwrap(); } else { if input.starts_with("/") && !input.starts_with("/me ") { @@ -1975,7 +1844,6 @@ fn get_msgs( page_php: &str, session: &str, username: &str, - tx: &crossbeam_channel::Sender<PostType>, users: &Arc<Mutex<Users>>, sig: &Arc<Mutex<Sig>>, messages_updated_tx: &crossbeam_channel::Sender<()>, @@ -2008,8 +1876,6 @@ fn get_msgs( members_tag, username, should_notify, - tx, - sig, ); // Build messages vector. Tag deleted messages. update_messages(new_messages, messages, datetime_fmt); @@ -2020,7 +1886,6 @@ fn get_msgs( } { let mut users = users.lock().unwrap(); - // ban_imposters(&tx, &username, &users); *users = extract_users(&doc); } Ok(()) @@ -2033,21 +1898,17 @@ fn process_new_messages( members_tag: &str, username: &str, should_notify: &mut bool, - tx: &crossbeam_channel::Sender<PostType>, - sig: &Arc<Mutex<Sig>>, ) { - if let Some(last_known_msg) = messages.get(0) { + if let Some(last_known_msg) = messages.first() { let last_known_msg_parsed_dt = parse_date(&last_known_msg.date, datetime_fmt); let filtered = new_messages.iter().filter(|new_msg| { last_known_msg_parsed_dt <= parse_date(&new_msg.date, datetime_fmt) && !(new_msg.date == last_known_msg.date && last_known_msg.text == new_msg.text) }); for new_msg in filtered { - if let Some((from, to_opt, msg)) = get_message(&new_msg.text, &members_tag) { + if let Some((_, to_opt, msg)) = get_message(&new_msg.text, members_tag) { // Process new messages - if username == N0TR1V { - n0tr1v_only_process_msg(&from, &msg, &tx, &sig); - } + // Notify when tagged if msg.contains(format!("@{}", &username).as_str()) { *should_notify = true; @@ -2114,31 +1975,6 @@ fn update_messages( messages.truncate(1000); } -fn n0tr1v_only_process_msg( - from: &str, - msg: &str, - tx: &crossbeam_channel::Sender<PostType>, - sig: &Arc<Mutex<Sig>>, -) { - // !bhcli filters - if msg == "!bhcli" { - let msg = format!("@{} -> {}", from, BHCLI_BLOG_URL).to_owned(); - tx.send(PostType::Post(msg, None)).unwrap(); - } else if msg == "/logout" && from == STUXNET { - log::error!("forced logout by {}", from); - sig.lock().unwrap().signal(&ExitSignal::Terminate); - return; - } - // Auto kick spammers - // if from != N0TR1V && from != FAGGOT { - // if msg.contains(FAGGOT) && (msg.contains("pedo") || msg.contains("child")) { - // let msg = "spam".to_owned(); - // let username_to_kick = from.to_owned(); - // tx.send(PostType::Kick(msg, username_to_kick)).unwrap(); - // } - // } -} - fn delete_message( client: &Client, full_url: &str, @@ -2174,81 +2010,17 @@ fn delete_message( Ok(()) } -// fn ban_imposters(tx: &crossbeam_channel::Sender<PostType>, account_username: &str, users: &Users) { -// if BAN_IMPOSTERS { -// if users.admin.len() == 0 && (users.staff.len() == 0 || account_username == N0TR1V) { -// let n0tr1v_rgx = Regex::new(r#"n[o0]tr[1il][vy]"#).unwrap(); // o 0 | 1 i l | v y -// let molester_rgx = Regex::new(r#"m[o0][1l][e3][s5$]t[e3]r"#).unwrap(); -// let rapist_rgx = Regex::new(r#"r[a4]p[i1l]st"#).unwrap(); -// let hitler_rgx = Regex::new(r#"h[i1l]t[l1]er"#).unwrap(); -// let himmler_rgx = Regex::new(r#"h[i1]m+l[e3]r"#).unwrap(); -// let mengele_rgx = Regex::new(r#"m[e3]ng[e3]l[e3]"#).unwrap(); -// let goebbels_rgx = Regex::new(r#"g[o0][e|3]b+[e3]ls"#).unwrap(); -// let heydrich_rgx = Regex::new(r#"h[e3]ydr[i1]ch"#).unwrap(); -// let globocnik_rgx = Regex::new(r#"gl[o0]b[o0]cn[i1l]k"#).unwrap(); -// let dirlewanger_rgx = Regex::new(r#"d[i1]rl[e3]wang[e3]r"#).unwrap(); -// let jeckeln_rgx = Regex::new(r#"j[e3]ck[e3]ln"#).unwrap(); -// let kramer_rgx = Regex::new(r#"kram[e3]r"#).unwrap(); -// let blobel_rgx = Regex::new(r#"bl[o0]b[e3]l"#).unwrap(); -// let stangl_rgx = Regex::new(r#"stangl"#).unwrap(); -// for (_color, username) in &users.guests { -// let lower_name = username.to_lowercase(); -// // Names that anyone using bhcli will ban -// if n0tr1v_rgx.is_match(&lower_name) || lower_name.contains("pedo") { -// let msg = "forbidden name".to_owned(); -// let username = username.to_owned(); -// tx.send(PostType::Kick(msg, username)).unwrap(); -// } -// // Names that only "n0tr1v" will ban -// if account_username == N0TR1V { -// if lower_name.contains("fuck") -// || lower_name.contains("nigger") -// || lower_name.contains("nigga") -// || lower_name.contains("chink") -// || lower_name.contains("atomwaffen") -// || lower_name.contains("altright") -// || hitler_rgx.is_match(&lower_name) -// || goebbels_rgx.is_match(&lower_name) -// || himmler_rgx.is_match(&lower_name) -// || mengele_rgx.is_match(&lower_name) -// || heydrich_rgx.is_match(&lower_name) -// || globocnik_rgx.is_match(&lower_name) -// || dirlewanger_rgx.is_match(&lower_name) -// || jeckeln_rgx.is_match(&lower_name) -// || kramer_rgx.is_match(&lower_name) -// || blobel_rgx.is_match(&lower_name) -// || stangl_rgx.is_match(&lower_name) -// || rapist_rgx.is_match(&lower_name) -// || molester_rgx.is_match(&lower_name) -// { -// let msg = "forbidden name".to_owned(); -// let username = username.to_owned(); -// tx.send(PostType::Kick(msg, username)).unwrap(); -// } -// } -// } -// } -// } -// } - impl ChatClient { fn new(params: Params) -> Self { let mut c = new_default_le_chat_php_client(params.clone()); - match params.chat_type { - ClientType::Custom => { - c.config.url = params.url.unwrap_or("".to_owned()); - c.config.page_php = params.page_php.unwrap_or("chat.php".to_owned()); - c.config.datetime_fmt = params.datetime_fmt.unwrap_or("%m-%d %H:%M:%S".to_owned()); - c.config.members_tag = params.members_tag.unwrap_or("[M] ".to_owned()); - c.config.keepalive_send_to = None; - } - ClientType::Dan => { - c.config = LeChatPHPConfig::new_dans_chat_config(); - } - ClientType::BHC => { - c.config = LeChatPHPConfig::new_black_hat_chat_config(); - } - } + c.config.url = params.url.unwrap_or( + "http://blkhatjxlrvc5aevqzz5t6kxldayog6jlx5h7glnu44euzongl4fh5ad.onion/index.php" + .to_owned(), + ); + c.config.page_php = params.page_php.unwrap_or("chat.php".to_owned()); + c.config.datetime_fmt = params.datetime_fmt.unwrap_or("%m-%d %H:%M:%S".to_owned()); + c.config.members_tag = params.members_tag.unwrap_or("[M] ".to_owned()); + c.config.keepalive_send_to = None; Self { le_chat_php_client: c, } @@ -2263,7 +2035,6 @@ fn new_default_le_chat_php_client(params: Params) -> LeChatPHPClient { let (color_tx, color_rx) = crossbeam_channel::unbounded(); let (tx, rx) = crossbeam_channel::unbounded(); LeChatPHPClient { - chat_type: params.chat_type, base_client: BaseClient { username: params.username, password: params.password, @@ -2294,7 +2065,6 @@ struct ChatClient { #[derive(Debug, Clone)] struct Params { - chat_type: ClientType, url: Option<String>, page_php: Option<String>, datetime_fmt: Option<String>, @@ -2412,13 +2182,6 @@ fn ask_password(password: Option<String>) -> String { password.unwrap_or_else(|| rpassword::prompt_password("Password: ").unwrap()) } -#[derive(Debug, Clone, PartialEq)] -enum ClientType { - BHC, - Dan, - Custom, -} - #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DkfNotifierResp { @@ -2517,12 +2280,10 @@ impl Default for Commands { // Strange // Function to read the configuration file and parse it -// Function to read the configuration file and parse it fn read_commands_file(file_path: &str) -> Result<Commands, Box<dyn std::error::Error>> { // Read the contents of the file let commands_content = std::fs::read_to_string(file_path)?; // log::error!("Read file contents: {}", commands_content); - // Deserialize the contents into a Commands struct let commands: Commands = toml::from_str(&commands_content)?; // log::error!( @@ -2581,16 +2342,7 @@ fn main() -> anyhow::Result<()> { let username = ask_username(opts.username); let password = ask_password(opts.password); - let chat_type = if opts.url.is_some() { - ClientType::Custom - } else if opts.dan { - ClientType::Dan - } else { - ClientType::BHC - }; - let params = Params { - chat_type, url: opts.url, page_php: opts.page_php, datetime_fmt: opts.datetime_fmt,