commit 85f4758532eafc5e3c02fafa664e88537adb11ba
parent 851275d4064bddb3391ec6cd7b451165585d4108
Author: Strange <StrangeGuy6228@protonmail.com>
Date: Sun, 17 Mar 2024 14:49:09 +0530
added Members Mode+readme update
Diffstat:
3 files changed, 131 insertions(+), 84 deletions(-)
diff --git a/README.md b/README.md
@@ -1,16 +1,16 @@
# 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.