bhcli

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

commit 85f4758532eafc5e3c02fafa664e88537adb11ba
parent 851275d4064bddb3391ec6cd7b451165585d4108
Author: Strange <StrangeGuy6228@protonmail.com>
Date:   Sun, 17 Mar 2024 14:49:09 +0530

added Members Mode+readme update

Diffstat:
MREADME.md | 22++++++++++++++++++----
Msrc/main.rs | 193++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Astrange_bhcli.jpg | 0
3 files changed, 131 insertions(+), 84 deletions(-)

diff --git a/README.md b/README.md @@ -1,16 +1,16 @@ # BHCLI -![screenshot](screenshot.png "Screenshot") +<!-- ![screenshot](screenshot.png "Screenshot") --> +![screenshot](strange_bhcli.jpg "strange_bhcli") ## Description This is a CLI client for any of [le-chat-php](https://github.com/DanWin/le-chat-php) -currently supported chats are [Black Hat Chat](http://blkhatjxlrvc5aevqzz5t6kxldayog6jlx5h7glnu44euzongl4fh5ad.onion) and -[Daniel's chat](http://danschat356lctri3zavzh6fbxg2a7lo6z3etgkctzzpspewu7zdsaqd.onion) +Officially supported chats are [Black Hat Chat](http://blkhatjxlrvc5aevqzz5t6kxldayog6jlx5h7glnu44euzongl4fh5ad.onion) ## Pre-built binaries -Pre-buit binaries can be found on the [official website](http://dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/bhcli) +Pre-buit binaries can be found on the [official website](http://git.dkforestseeaaq2dqz2uflmlsybvnq2irzn4ygyvu53oazyorednviid.onion/Strange/bhcli/releases) ## Features @@ -24,6 +24,7 @@ Pre-buit binaries can be found on the [official website](http://dkforestseeaaq2d - Unignore someone `/unignore username` - Toggle notifications sound `m` - Toggle a "guest" view, by filtering out PMs and "Members chat" `shift+G` +- Toggle a "members" view, by filtering out PMs and "Guest chat" `shift+M` - Filter messages `/f terms` - Copy a selected message to clipboard `ctrl+C` | `y` - Copy the first link in a message to clipboard `shift+Y` @@ -33,6 +34,14 @@ Pre-buit binaries can be found on the [official website](http://dkforestseeaaq2d - captcha is displayed directly in terminal 10 times the real size - Upload file `/u C:\path\to\file.png @username message` (@username is optional) `@members` for members group - `<tab>` to autocomplete usernames while typing +- `ctrl + w` or !warn username to send a pre-kick warning message to a user + [ Only for members+ users ] + > This is your warning @username, will be kicked next !rules +- Can hide messages with `backspace`, hidden messages can be viewed by toggling + `ctrl+H`. + > - Hidden messages are just hidden from the view, they are not deleted + > - Deleted messages once hidden can't be viewed again + ### Editing mode - `ctrl+A` Move cursor to start of line @@ -66,6 +75,11 @@ Pre-buit binaries can be found on the [official website](http://dkforestseeaaq2d - Install Rust - Install dependencies `apt-get install -y pkg-config libasound2-dev libssl-dev cmake libfreetype6-dev libexpat1-dev libxcb-composite0-dev libx11-dev` - Compile with `cargo build --release` +- Run with `./target/release/bhcli` +- You can move the binary to `/opt/` to make it available system wide, given + that `/opt/` is in your `$PATH` +- The bhcli.log file will be created in the same directory as the pwd you run + the binary from ## Cross compile diff --git a/src/main.rs b/src/main.rs @@ -4,11 +4,9 @@ mod bhc; use anyhow::{anyhow, Context}; use std::process; -use log; use log::LevelFilter; use log4rs::append::file::FileAppender; use log4rs::encode::pattern::PatternEncoder; -use log4rs; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; use clap::Parser; use clipboard::ClipboardContext; @@ -43,9 +41,7 @@ use std::thread; use std::time::Duration; use std::time::Instant; use http::StatusCode; -use reqwest; use reqwest::redirect::Policy; -use textwrap; use tui::layout::Rect; use tui::style::Color as tuiColor; use tui::{ @@ -225,6 +221,7 @@ struct LeChatPHPClient { is_muted: Arc<Mutex<bool>>, show_sys: bool, display_guest_view: bool, + display_member_view: bool, display_hidden_msgs: bool, tx: crossbeam_channel::Sender<PostType>, rx: Arc<Mutex<crossbeam_channel::Receiver<PostType>>>, @@ -349,11 +346,11 @@ impl LeChatPHPClient { messages_updated_tx: crossbeam_channel::Sender<()>, ) -> thread::JoinHandle<()> { let client = self.client.clone(); - let messages = Arc::clone(&messages); - let users = Arc::clone(&users); + let messages = Arc::clone(messages); + let users = Arc::clone(users); let session = self.session.clone().unwrap(); let username = self.base_client.username.clone(); - let refresh_rate = self.refresh_rate.clone(); + let refresh_rate = self.refresh_rate; let base_url = self.config.url.clone(); let page_php = self.config.page_php.clone(); let datetime_fmt = self.config.datetime_fmt.clone(); @@ -426,6 +423,7 @@ impl LeChatPHPClient { app.is_muted = *self.is_muted.lock().unwrap(); app.show_sys = self.show_sys; app.display_guest_view = self.display_guest_view; + app.display_member_view = self.display_member_view; app.display_hidden_msgs = self.display_hidden_msgs; app.members_tag = self.config.members_tag.clone(); app.staffs_tag = self.config.staffs_tag.clone(); @@ -534,7 +532,7 @@ impl LeChatPHPClient { fn logout(&mut self) -> anyhow::Result<()> { if let Some(session) = &self.session { - lechatphp::logout(&self.client, &self.config.url, &self.config.page_php, &session)?; + lechatphp::logout(&self.client, &self.config.url, &self.config.page_php, session)?; self.session = None; } Ok(()) @@ -546,7 +544,7 @@ impl LeChatPHPClient { let color_rx = Arc::clone(&self.color_rx); thread::spawn(move || { let mut idx = 0; - let colors = vec![ + let colors = [ "#ff3366", "#ff6633", "#FFCC33", "#33FF66", "#33FFCC", "#33CCFF", "#3366FF", "#6633FF", "#CC33FF", "#efefef", ]; @@ -632,7 +630,8 @@ impl LeChatPHPClient { KeyEvent { code: KeyCode::Char('d'), modifiers: KeyModifiers::NONE, .. } => self.handle_normal_mode_key_event_debug(app), KeyEvent { code: KeyCode::Char('D'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_debug2(app), KeyEvent { code: KeyCode::Char('m'), modifiers: KeyModifiers::NONE, .. } => self.handle_normal_mode_key_event_toggle_mute(), - KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_toggle_sys(), + KeyEvent { code: KeyCode::Char('S'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_toggle_sys(), + KeyEvent { code: KeyCode::Char('M'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_toggle_member_view(), KeyEvent { code: KeyCode::Char('G'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_toggle_guest_view(), KeyEvent { code: KeyCode::Char('H'), modifiers: KeyModifiers::SHIFT, .. } => self.handle_normal_mode_key_event_toggle_hidden(), KeyEvent { code: KeyCode::Char('i'), modifiers: KeyModifiers::NONE, .. } => self.handle_normal_mode_key_event_input_mode(app), @@ -717,7 +716,7 @@ impl LeChatPHPClient { if let Some(idx) = app.items.state.selected() { if let Some(item) = app.items.items.get(idx) { // If we have a filter, <enter> will "jump" to the message - if app.filter != "" { + if !app.filter.is_empty(){ let idx = messages .lock().unwrap() .iter() @@ -819,6 +818,9 @@ impl LeChatPHPClient { self.display_guest_view = !self.display_guest_view; } + fn handle_normal_mode_key_event_toggle_member_view(&mut self) { + self.display_member_view = !self.display_member_view; + } fn handle_normal_mode_key_event_g(&mut self, app: &mut App) { // Handle "gg" key combination @@ -1331,7 +1333,7 @@ fn post_msg(client: &Client, post_type_recv: PostType, full_url: &str, session: ]); } PostType::NewNickname(new_nickname) => { - set_profile_base_info(&client, full_url, &mut params)?; + set_profile_base_info(client, full_url, &mut params)?; params.extend(vec![ ("do", "save".to_owned()), ("timestamps", "on".to_owned()), @@ -1339,7 +1341,7 @@ fn post_msg(client: &Client, post_type_recv: PostType, full_url: &str, session: ]); } PostType::NewColor(new_color) => { - set_profile_base_info(&client, full_url, &mut params)?; + set_profile_base_info(client, full_url, &mut params)?; params.extend(vec![ ("do", "save".to_owned()), ("timestamps", "on".to_owned()), @@ -1347,7 +1349,7 @@ fn post_msg(client: &Client, post_type_recv: PostType, full_url: &str, session: ]); } PostType::Ignore(username) => { - set_profile_base_info(&client, full_url, &mut params)?; + set_profile_base_info(client, full_url, &mut params)?; params.extend(vec![ ("do", "save".to_owned()), ("timestamps", "on".to_owned()), @@ -1355,7 +1357,7 @@ fn post_msg(client: &Client, post_type_recv: PostType, full_url: &str, session: ]); } PostType::Unignore(username) => { - set_profile_base_info(&client, full_url, &mut params)?; + set_profile_base_info(client, full_url, &mut params)?; params.extend(vec![ ("do", "save".to_owned()), ("timestamps", "on".to_owned()), @@ -1363,7 +1365,7 @@ fn post_msg(client: &Client, post_type_recv: PostType, full_url: &str, session: ]); } PostType::Profile(new_color, new_nickname) => { - set_profile_base_info(&client, full_url, &mut params)?; + set_profile_base_info(client, full_url, &mut params)?; params.extend(vec![ ("do", "save".to_owned()), ("timestamps", "on".to_owned()), @@ -1482,7 +1484,7 @@ fn get_msgs( } { let mut users = users.lock().unwrap(); - ban_imposters(&tx, &username, &users); + // ban_imposters(&tx, &username, &users); *users = extract_users(&doc); } Ok(()) @@ -1577,13 +1579,13 @@ fn n0tr1v_only_process_msg(from: &str, msg: &str, tx: &crossbeam_channel::Sender 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(); - } - } + // 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( @@ -1615,62 +1617,62 @@ 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(); - } - } - } - } - } -} +// 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 { @@ -1720,6 +1722,7 @@ fn new_default_le_chat_php_client(params: Params) -> LeChatPHPClient { is_muted: Arc::new(Mutex::new(false)), show_sys: false, display_guest_view: false, + display_member_view: false, display_hidden_msgs: false, tx, rx: Arc::new(Mutex::new(rx)), @@ -2503,6 +2506,8 @@ fn render_help_txt(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &mut App, r let style = Style::default().fg(fg).add_modifier(Modifier::BOLD); msg.extend(vec![Span::raw(" | "), Span::styled("not muted", style)]); } + + //Strange if app.display_guest_view { let fg = tuiColor::LightGreen; let style = Style::default().fg(fg).add_modifier(Modifier::BOLD); @@ -2512,6 +2517,18 @@ fn render_help_txt(f: &mut Frame<CrosstermBackend<io::Stdout>>, app: &mut App, r let style = Style::default().fg(fg); msg.extend(vec![Span::raw(" | "), Span::styled("G", style)]); } + + //Strange + if app.display_member_view { + let fg = tuiColor::LightGreen; + let style = Style::default().fg(fg).add_modifier(Modifier::BOLD); + msg.extend(vec![Span::raw(" | "), Span::styled("M", style)]); + } else { + let fg = tuiColor::Gray; + let style = Style::default().fg(fg); + msg.extend(vec![Span::raw(" | "), Span::styled("M", style)]); + } + if app.display_hidden_msgs { let fg = tuiColor::LightGreen; let style = Style::default().fg(fg).add_modifier(Modifier::BOLD); @@ -2590,6 +2607,20 @@ fn render_messages( } } + // Strange + // Display only messages from members and staff + if app.display_member_view { + // In members mode, include only messages from members and staff + let text = m.text.text(); + if !text.starts_with(&app.members_tag) && !text.starts_with(&app.staffs_tag) { + return None; + } + if let Some((_, Some(_), _)) = get_message(&m.text, &app.members_tag) { + return None; + } + } + + if app.filter != "" { if !m.text.text().to_lowercase().contains(&app.filter.to_lowercase()) { return None; @@ -2689,6 +2720,7 @@ struct App { is_muted: bool, show_sys: bool, display_guest_view: bool, + display_member_view: bool, display_hidden_msgs: bool, items: StatefulList<Message>, filter: String, @@ -2706,6 +2738,7 @@ impl Default for App { is_muted: false, show_sys: false, display_guest_view: false, + display_member_view: false, display_hidden_msgs: false, items: StatefulList::new(), filter: "".to_owned(), diff --git a/strange_bhcli.jpg b/strange_bhcli.jpg Binary files differ.