commit 28441f89139fc9779a05d91952d2fb2a06659a69
parent 318fdcfbf22367520483db7a94f10e0debe4583e
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Thu, 6 Apr 2023 22:35:31 -0700
fix bhc client
Diffstat:
4 files changed, 260 insertions(+), 220 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -119,6 +119,7 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
name = "bhcli"
version = "0.1.0"
dependencies = [
+ "anyhow",
"base64",
"bresenham",
"chrono",
diff --git a/Cargo.toml b/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+anyhow = "1.0.70"
base64 = "0.21.0"
bresenham = "0.1.1"
chrono = "0.4.19"
diff --git a/src/lechatphp/mod.rs b/src/lechatphp/mod.rs
@@ -1 +1,202 @@
-pub mod captcha;
-\ No newline at end of file
+use std::{error, fs, io, thread};
+use std::fmt::{Display, Formatter};
+use std::io::Write;
+use std::time::Duration;
+use base64::Engine;
+use base64::engine::general_purpose;
+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};
+
+pub mod captcha;
+
+#[derive(Debug)]
+pub enum LoginErr {
+ ServerDownErr,
+ ServerDown500Err,
+ CaptchaFailedSolveErr,
+ CaptchaUsedErr,
+ CaptchaWgErr,
+ RegErr,
+ NicknameErr,
+ KickedErr,
+ UnknownErr,
+ Reqwest(reqwest::Error),
+}
+
+impl From<reqwest::Error> for LoginErr {
+ fn from(value: reqwest::Error) -> Self {
+ LoginErr::Reqwest(value)
+ }
+}
+
+impl Display for LoginErr {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let s = match self {
+ LoginErr::ServerDownErr => SERVER_DOWN_ERR.to_owned(),
+ LoginErr::ServerDown500Err => SERVER_DOWN_500_ERR.to_owned(),
+ LoginErr::CaptchaFailedSolveErr => CAPTCHA_FAILED_SOLVE_ERR.to_owned(),
+ LoginErr::CaptchaUsedErr => CAPTCHA_USED_ERR.to_owned(),
+ LoginErr::CaptchaWgErr => CAPTCHA_WG_ERR.to_owned(),
+ LoginErr::RegErr => REG_ERR.to_owned(),
+ LoginErr::NicknameErr => NICKNAME_ERR.to_owned(),
+ LoginErr::KickedErr => KICKED_ERR.to_owned(),
+ LoginErr::UnknownErr => UNKNOWN_ERR.to_owned(),
+ LoginErr::Reqwest(e) => e.to_string(),
+ };
+ write!(f, "{}", s)
+ }
+}
+
+impl error::Error for LoginErr {}
+
+pub fn login(
+ client: &Client,
+ base_url: &str,
+ page_php: &str,
+ username: &str,
+ password: &str,
+ color: &str,
+ manual_captcha: bool) -> std::result::Result<String, LoginErr>
+{
+ // Get login page
+ let login_url = format!("{}/{}", &base_url, &page_php);
+ let resp = client.get(&login_url).send()?;
+ if resp.status() == StatusCode::BAD_GATEWAY {
+ return Err(LoginErr::ServerDownErr);
+ }
+ let resp = resp.text()?;
+ let doc = Document::from(resp.as_str());
+
+ // Post login form
+ let mut params = vec![
+ ("action", "login".to_owned()),
+ ("lang", LANG.to_owned()),
+ ("nick", username.to_owned()),
+ ("pass", password.to_owned()),
+ ("colour", color.to_owned()),
+ ];
+
+ 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();
+ let img = image::load_from_memory(&img_decoded).unwrap();
+ let img_buf = image::imageops::resize(
+ &img,
+ img.width() * 4,
+ img.height() * 4,
+ 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: ");
+ io::stdout().flush().unwrap();
+ io::stdin().read_line(&mut captcha_input).unwrap();
+ trim_newline(&mut captcha_input);
+ } else {
+ match captcha::solve_b64(captcha_img) {
+ Some(answer) => captcha_input = answer,
+ None => return Err(LoginErr::CaptchaFailedSolveErr),
+ }
+ }
+
+ params.extend(vec![
+ ("challenge", captcha_value.to_owned()),
+ ("captcha", captcha_input.clone()),
+ ]);
+ }
+
+ let mut resp = client.post(&login_url).form(¶ms).send()?;
+ match resp.status() {
+ StatusCode::BAD_GATEWAY => return Err(LoginErr::ServerDownErr),
+ StatusCode::INTERNAL_SERVER_ERROR => return Err(LoginErr::ServerDown500Err),
+ _ => {}
+ }
+
+ 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());
+ 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("");
+ }
+
+ let mut resp = resp.text()?;
+ if resp.contains(CAPTCHA_USED_ERR) {
+ return Err(LoginErr::CaptchaUsedErr);
+ } else if resp.contains(CAPTCHA_WG_ERR) {
+ return Err(LoginErr::CaptchaWgErr);
+ } else if resp.contains(REG_ERR) {
+ return Err(LoginErr::RegErr);
+ } else if resp.contains(NICKNAME_ERR) {
+ return Err(LoginErr::NicknameErr);
+ } else if resp.contains(KICKED_ERR) {
+ return Err(LoginErr::KickedErr);
+ }
+
+ let mut doc = Document::from(resp.as_str());
+ if let Some(body) = doc.find(Name("body")).next() {
+ if let Some(body_class) = body.attr("class") {
+ if body_class == "error" {
+ if let Some(h2) = doc.find(Name("h2")).next() {
+ log::error!("{}", h2.text());
+ }
+ return Err(LoginErr::UnknownErr);
+ } else if body_class == "failednotice" {
+ log::error!("failed logins: {}", body.text());
+ let nc = doc.find(Attr("name", "nc")).next().unwrap();
+ let nc_value = nc.attr("value").unwrap().to_owned();
+ let params: Vec<(&str, String)> = vec![
+ ("lang", LANG.to_owned()),
+ ("nc", nc_value.to_owned()),
+ ("action", "login".to_owned()),
+ ];
+ resp = client.post(&login_url).form(¶ms).send()?.text()?;
+ doc = Document::from(resp.as_str());
+ }
+ }
+ }
+
+ let iframe = match doc.find(Attr("name", "view")).next() {
+ Some(view) => view,
+ None => {
+ fs::write("./dump_login_err.html", resp.as_str()).unwrap();
+ panic!("failed to get view iframe");
+ }
+ };
+ let iframe_src = iframe.attr("src").unwrap();
+
+ let session_captures = SESSION_RGX.captures(iframe_src).unwrap();
+ let session = session_captures.get(1).unwrap().as_str();
+ Ok(session.to_owned())
+}
+
+pub fn logout(
+ client: &Client,
+ base_url: &str,
+ page_php: &str,
+ session: &str) -> anyhow::Result<()>
+{
+ let full_url = format!("{}/{}", &base_url, &page_php);
+ let params = [
+ ("action", "logout"),
+ ("session", &session),
+ ("lang", LANG),
+ ];
+ client.post(&full_url).form(¶ms).send()?;
+ Ok(())
+}
+\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
@@ -2,12 +2,12 @@ mod util;
mod lechatphp;
mod bhc;
+use anyhow::anyhow;
use log;
use log::LevelFilter;
use log4rs::append::file::FileAppender;
use log4rs::encode::pattern::PatternEncoder;
use log4rs;
-use base64::{engine::general_purpose, Engine as _};
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
use clap::Parser;
use clipboard::ClipboardContext;
@@ -22,8 +22,6 @@ use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
-use http::StatusCode;
-use image;
use lazy_static::lazy_static;
use linkify::LinkFinder;
use rand::distributions::Alphanumeric;
@@ -33,12 +31,9 @@ use reqwest::blocking::multipart;
use reqwest::blocking::Client;
use rodio::{source::Source, Decoder, OutputStream};
use select::document::Document;
-use select::predicate::{And, Attr, Name};
+use select::predicate::{Attr, Name};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
-use std::error;
-use std::fmt::{Display, Formatter};
-use std::fs;
use std::io::Cursor;
use std::io::{self, Write};
use std::sync::Arc;
@@ -46,8 +41,9 @@ use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::Instant;
+use http::StatusCode;
use reqwest;
-use termage;
+use reqwest::redirect::Policy;
use textwrap;
use tui::layout::Rect;
use tui::style::Color as tuiColor;
@@ -61,6 +57,7 @@ use tui::{
};
use unicode_width::UnicodeWidthStr;
use util::StatefulList;
+use crate::lechatphp::LoginErr;
const LANG: &str = "en";
const SEND_TO_ALL: &str = "s *";
@@ -86,8 +83,6 @@ const STUXNET: &str = "STUXNET";
const FAGGOT: &str = "faggot";
const DNMX_URL: &str = "http://hxuzjtocnzvv5g2rtg2bhwkcbupmk7rclb6lly3fo4tvqkk5oyrv3nid.onion";
-type Result<T> = std::result::Result<T, Box<dyn error::Error>>;
-
lazy_static! {
static ref SESSION_RGX: Regex = Regex::new(r#"session=([^&]+)"#).unwrap();
static ref COLOR_RGX: Regex = Regex::new(r#"color:\s*([#\w]+)\s*;"#).unwrap();
@@ -103,18 +98,6 @@ lazy_static! {
static ref NEW_COLOR_RGX: Regex = Regex::new(r#"^/color\s(.*)$"#).unwrap();
}
-#[derive(Debug, Serialize, Deserialize)]
-enum Typ {
- BHC,
- Custom,
-}
-
-impl Typ {
- fn bhc() -> Self {
- Typ::BHC
- }
-}
-
fn default_empty_str() -> String {
"".to_string()
}
@@ -123,8 +106,6 @@ fn default_empty_str() -> String {
struct Profile {
username: String,
password: String,
- #[serde(default = "Typ::bhc")]
- typ: Typ,
#[serde(default = "default_empty_str")]
url: String,
#[serde(default = "default_empty_str")]
@@ -203,7 +184,7 @@ impl LeChatPHPConfig {
Self {
url: "http://blkhatjxlrvc5aevqzz5t6kxldayog6jlx5h7glnu44euzongl4fh5ad.onion".to_owned(),
datetime_fmt: "%m-%d %H:%M:%S".to_owned(),
- page_php: "index.php".to_owned(),
+ page_php: "chat.php".to_owned(),
keepalive_send_to: Some("0".to_owned()),
members_tag: "[M] ".to_owned(),
staffs_tag: "[Staff] ".to_owned(),
@@ -222,52 +203,13 @@ impl LeChatPHPConfig {
}
}
-#[derive(Debug)]
-enum LoginErr {
- ServerDownErr,
- ServerDown500Err,
- CaptchaFailedSolveErr,
- CaptchaUsedErr,
- CaptchaWgErr,
- RegErr,
- NicknameErr,
- KickedErr,
- UnknownErr,
- Reqwest(reqwest::Error),
-}
-
-impl From<reqwest::Error> for LoginErr {
- fn from(value: reqwest::Error) -> Self {
- LoginErr::Reqwest(value)
- }
-}
-
-impl Display for LoginErr {
- fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
- let s = match self {
- LoginErr::ServerDownErr => SERVER_DOWN_ERR.to_owned(),
- LoginErr::ServerDown500Err => SERVER_DOWN_500_ERR.to_owned(),
- LoginErr::CaptchaFailedSolveErr => CAPTCHA_FAILED_SOLVE_ERR.to_owned(),
- LoginErr::CaptchaUsedErr => CAPTCHA_USED_ERR.to_owned(),
- LoginErr::CaptchaWgErr => CAPTCHA_WG_ERR.to_owned(),
- LoginErr::RegErr => REG_ERR.to_owned(),
- LoginErr::NicknameErr => NICKNAME_ERR.to_owned(),
- LoginErr::KickedErr => KICKED_ERR.to_owned(),
- LoginErr::UnknownErr => UNKNOWN_ERR.to_owned(),
- LoginErr::Reqwest(e) => e.to_string(),
- };
- write!(f, "{}", s)
- }
-}
-
-impl error::Error for LoginErr {}
-
struct BaseClient {
username: String,
password: String,
}
struct LeChatPHPClient {
+ chat_type: ClientType,
base_client: BaseClient,
guest_color: String,
client: Client,
@@ -295,19 +237,19 @@ impl LeChatPHPClient {
loop {
if let Err(e) = self.login() {
match e {
- LoginErr::KickedErr | LoginErr::RegErr | LoginErr::NicknameErr | LoginErr::UnknownErr => {
+ lechatphp::LoginErr::KickedErr | lechatphp::LoginErr::RegErr | lechatphp::LoginErr::NicknameErr | lechatphp::LoginErr::UnknownErr => {
log::error!("{}", e);
break;
},
- LoginErr::CaptchaFailedSolveErr => {
+ lechatphp::LoginErr::CaptchaFailedSolveErr => {
log::error!("{}", e);
continue;
},
- LoginErr::CaptchaWgErr | LoginErr::CaptchaUsedErr => {},
- LoginErr::ServerDownErr | LoginErr::ServerDown500Err => {
+ lechatphp::LoginErr::CaptchaWgErr | lechatphp::LoginErr::CaptchaUsedErr => {},
+ lechatphp::LoginErr::ServerDownErr | lechatphp::LoginErr::ServerDown500Err => {
log::error!("{}", e);
},
- LoginErr::Reqwest(err) => {
+ lechatphp::LoginErr::Reqwest(err) => {
if err.is_connect() {
log::error!("{}\nIs tor proxy enabled ?", err);
break;
@@ -712,7 +654,7 @@ impl LeChatPHPClient {
h
}
- fn get_msgs(&mut self) -> Result<ExitSignal> {
+ fn get_msgs(&mut self) -> anyhow::Result<ExitSignal> {
let terminate_signal: ExitSignal;
let messages: Arc<Mutex<Vec<Message>>> = Arc::new(Mutex::new(Vec::new()));
@@ -795,150 +737,43 @@ impl LeChatPHPClient {
Ok(terminate_signal)
}
- fn post_msg(&self, post_type: PostType) -> Result<()> {
+ fn post_msg(&self, post_type: PostType) -> anyhow::Result<()> {
self.tx.send(post_type)?;
Ok(())
}
- fn login(&mut self) -> std::result::Result<(), LoginErr> {
+ fn login(&mut self) -> Result<(), lechatphp::LoginErr> {
// If we provided a session, skip login process
if self.session != "" {
return Ok(());
}
-
- // Get login page
- let login_url = format!("{}/{}", &self.config.url, &self.config.page_php);
- let resp = self.client.get(&login_url).send()?;
- if resp.status() == StatusCode::BAD_GATEWAY {
- return Err(LoginErr::ServerDownErr);
- }
- let resp = resp.text()?;
- let doc = Document::from(resp.as_str());
-
- // Post login form
- let mut params = vec![
- ("action", "login".to_owned()),
- ("lang", LANG.to_owned()),
- ("nick", self.base_client.username.clone()),
- ("pass", self.base_client.password.clone()),
- ("colour", self.guest_color.clone()),
- ];
-
- 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();
-
+ if self.chat_type == ClientType::BHC {
+ let captcha_bytes = self.client.get(format!("{}/captcha.php", &self.config.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();
- if self.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();
- let img = image::load_from_memory(&img_decoded).unwrap();
- let img_buf = image::imageops::resize(
- &img,
- img.width() * 4,
- img.height() * 4,
- 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: ");
- io::stdout().flush().unwrap();
- io::stdin().read_line(&mut captcha_input).unwrap();
- trim_newline(&mut captcha_input);
- } else {
- match lechatphp::captcha::solve_b64(captcha_img) {
- Some(answer) => captcha_input = answer,
- None => return Err(LoginErr::CaptchaFailedSolveErr),
- }
+ 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!("{}/form.php", &self.config.url)).form(¶ms).send().unwrap();
+ if resp.status() != StatusCode::SEE_OTHER {
+ return Err(LoginErr::CaptchaWgErr);
}
-
- params.extend(vec![
- ("challenge", captcha_value.to_owned()),
- ("captcha", captcha_input.clone()),
- ]);
- }
-
- let mut resp = self.client.post(&login_url).form(¶ms).send()?;
- match resp.status() {
- StatusCode::BAD_GATEWAY => return Err(LoginErr::ServerDownErr),
- StatusCode::INTERNAL_SERVER_ERROR => return Err(LoginErr::ServerDown500Err),
- _ => {}
- }
-
- 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!("{}{}", &self.config.url, rgx.captures(&refresh_header).unwrap().get(1).unwrap().as_str());
- println!("waitroom enabled, wait 10sec");
- thread::sleep(Duration::from_secs(10));
- resp = self.client.get(refresh_url.clone()).send()?;
- refresh_header = resp.headers().get("refresh").map(|v| v.to_str().unwrap()).unwrap_or("");
- }
-
- let mut resp = resp.text()?;
- if resp.contains(CAPTCHA_USED_ERR) {
- return Err(LoginErr::CaptchaUsedErr);
- } else if resp.contains(CAPTCHA_WG_ERR) {
- return Err(LoginErr::CaptchaWgErr);
- } else if resp.contains(REG_ERR) {
- return Err(LoginErr::RegErr);
- } else if resp.contains(NICKNAME_ERR) {
- return Err(LoginErr::NicknameErr);
- } else if resp.contains(KICKED_ERR) {
- return Err(LoginErr::KickedErr);
}
-
- let mut doc = Document::from(resp.as_str());
- if let Some(body) = doc.find(Name("body")).next() {
- if let Some(body_class) = body.attr("class") {
- if body_class == "error" {
- if let Some(h2) = doc.find(Name("h2")).next() {
- log::error!("{}", h2.text());
- }
- return Err(LoginErr::UnknownErr);
- } else if body_class == "failednotice" {
- log::error!("failed logins: {}", body.text());
- let nc = doc.find(Attr("name", "nc")).next().unwrap();
- let nc_value = nc.attr("value").unwrap().to_owned();
- let params: Vec<(&str, String)> = vec![
- ("lang", LANG.to_owned()),
- ("nc", nc_value.to_owned()),
- ("action", "login".to_owned()),
- ];
- resp = self.client.post(&login_url).form(¶ms).send()?.text()?;
- doc = Document::from(resp.as_str());
- }
- }
- }
-
- let iframe = match doc.find(Attr("name", "view")).next() {
- Some(view) => view,
- None => {
- fs::write("./dump_login_err.html", resp.as_str()).unwrap();
- panic!("failed to get view iframe");
- }
- };
- let iframe_src = iframe.attr("src").unwrap();
-
- let session_captures = SESSION_RGX.captures(iframe_src).unwrap();
- let session = session_captures.get(1).unwrap().as_str();
-
- self.session = session.to_owned();
+ self.session = lechatphp::login(
+ &self.client, &self.config.url, &self.config.page_php, &self.base_client.username,
+ &self.base_client.password, &self.guest_color, self.manual_captcha)?;
Ok(())
}
- fn logout(&mut self) -> Result<()> {
- let full_url = format!("{}/{}", &self.config.url, &self.config.page_php);
- let params = [
- ("action", "logout"),
- ("session", &self.session),
- ("lang", LANG),
- ];
- self.client.post(&full_url).form(¶ms).send()?;
+ fn logout(&mut self) -> anyhow::Result<()> {
+ lechatphp::logout(&self.client, &self.config.url, &self.config.page_php, &self.session)?;
self.session = "".to_owned();
Ok(())
}
@@ -978,7 +813,7 @@ impl LeChatPHPClient {
});
}
- fn handle_input(&mut self, events: &Events, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>) -> std::result::Result<(), ExitSignal> {
+ fn handle_input(&mut self, events: &Events, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>) -> Result<(), ExitSignal> {
match events.next() {
Ok(Event::NeedLogin) => return Err(ExitSignal::NeedLogin),
Ok(Event::Terminate) => return Err(ExitSignal::Terminate),
@@ -987,7 +822,7 @@ impl LeChatPHPClient {
}
}
- fn handle_event(&mut self, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>, event: event::Event) -> std::result::Result<(), ExitSignal> {
+ fn handle_event(&mut self, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>, event: event::Event) -> Result<(), ExitSignal> {
match event {
event::Event::Resize(_cols, _rows) => Ok(()),
event::Event::FocusGained => Ok(()),
@@ -998,7 +833,7 @@ impl LeChatPHPClient {
}
}
- fn handle_key_event(&mut self, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>, key_event: KeyEvent) -> std::result::Result<(), ExitSignal> {
+ fn handle_key_event(&mut self, app: &mut App, messages: &Arc<Mutex<Vec<Message>>>, users: &Arc<Mutex<Users>>, key_event: KeyEvent) -> Result<(), ExitSignal> {
match app.input_mode {
InputMode::LongMessage => self.handle_long_message_mode_key_event(app, key_event, messages),
InputMode::Normal => self.handle_normal_mode_key_event(app, key_event, messages),
@@ -1007,7 +842,7 @@ impl LeChatPHPClient {
}
}
- fn handle_long_message_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, messages: &Arc<Mutex<Vec<Message>>>) -> std::result::Result<(), ExitSignal> {
+ fn handle_long_message_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, messages: &Arc<Mutex<Vec<Message>>>) -> Result<(), ExitSignal> {
match key_event {
KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, .. } |
KeyEvent { code: KeyCode::Esc, modifiers: KeyModifiers::NONE, .. } => self.handle_long_message_mode_key_event_esc(app),
@@ -1017,7 +852,7 @@ impl LeChatPHPClient {
Ok(())
}
- fn handle_normal_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, messages: &Arc<Mutex<Vec<Message>>>) -> std::result::Result<(), ExitSignal> {
+ fn handle_normal_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, messages: &Arc<Mutex<Vec<Message>>>) -> Result<(), ExitSignal> {
match key_event {
KeyEvent { code: KeyCode::Char('/'), modifiers: KeyModifiers::NONE, .. } => self.handle_normal_mode_key_event_slash(app),
KeyEvent { code: KeyCode::Char('j'), modifiers: KeyModifiers::NONE, .. } |
@@ -1052,7 +887,7 @@ impl LeChatPHPClient {
Ok(())
}
- fn handle_editing_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, users: &Arc<Mutex<Users>>) -> std::result::Result<(), ExitSignal> {
+ fn handle_editing_mode_key_event(&mut self, app: &mut App, key_event: KeyEvent, users: &Arc<Mutex<Users>>) -> Result<(), ExitSignal> {
app.input_mode = InputMode::Editing;
match key_event {
KeyEvent { code: KeyCode::Enter, modifiers: KeyModifiers::NONE, .. } => self.handle_editing_mode_key_event_enter(app)?,
@@ -1230,12 +1065,12 @@ impl LeChatPHPClient {
app.items.unselect();
}
- fn handle_normal_mode_key_event_logout(&mut self) -> std::result::Result<(), ExitSignal> {
+ fn handle_normal_mode_key_event_logout(&mut self) -> Result<(), ExitSignal> {
self.logout().unwrap();
return Err(ExitSignal::Terminate);
}
- fn handle_normal_mode_key_event_exit(&mut self) -> std::result::Result<(), ExitSignal> {
+ fn handle_normal_mode_key_event_exit(&mut self) -> Result<(), ExitSignal> {
return Err(ExitSignal::Terminate);
}
@@ -1316,7 +1151,7 @@ impl LeChatPHPClient {
app.items.state.select(Some(0));
}
- fn handle_editing_mode_key_event_enter(&mut self, app: &mut App) -> std::result::Result<(), ExitSignal> {
+ fn handle_editing_mode_key_event_enter(&mut self, app: &mut App) -> Result<(), ExitSignal> {
if FIND_RGX.is_match(&app.input) {
return Ok(());
}
@@ -1562,7 +1397,7 @@ impl LeChatPHPClient {
&mut self,
app: &mut App,
mouse_event: MouseEvent,
- ) -> std::result::Result<(), ExitSignal> {
+ ) -> Result<(), ExitSignal> {
match mouse_event.kind {
MouseEventKind::ScrollDown => app.items.next(),
MouseEventKind::ScrollUp => app.items.previous(),
@@ -1620,7 +1455,7 @@ fn set_profile_base_info(
client: &Client,
full_url: &str,
params: &mut Vec<(&str, String)>,
-) -> Result<()> {
+) -> anyhow::Result<()> {
params.extend(vec![("action", "profile".to_owned())]);
let profile_resp = client.post(full_url).form(¶ms).send()?;
let profile_resp_txt = profile_resp.text().unwrap();
@@ -1654,7 +1489,7 @@ fn delete_message(
params: &mut Vec<(&str, String)>,
date: String,
text: String,
-) -> Result<()> {
+) -> anyhow::Result<()> {
params.extend(vec![
("action", "admin".to_owned()),
("do", "clean".to_owned()),
@@ -1769,6 +1604,7 @@ 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,
@@ -1890,6 +1726,7 @@ fn get_guest_color(wanted: Option<String>) -> String {
fn get_tor_client(socks_proxy_url: &str, no_proxy: bool) -> Client {
let ua = "Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0";
let mut builder = reqwest::blocking::ClientBuilder::new()
+ .redirect(Policy::none())
.cookie_store(true)
.user_agent(ua);
if !no_proxy {
@@ -1916,7 +1753,7 @@ fn ask_password(password: Option<String>) -> String {
})
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
enum ClientType {
BHC,
Dan,
@@ -2005,7 +1842,7 @@ fn start_dnmx_mail_notifier(client: &Client, username: &str, password: &str) {
});
}
-fn main() -> Result<()> {
+fn main() -> anyhow::Result<()> {
let mut opts: Opts = Opts::parse();
// Configs file
@@ -2375,11 +2212,11 @@ fn remove_prefix<'a>(s: &'a str, prefix: &str) -> &'a str {
}
}
-fn extract_messages(doc: &Document) -> Result<Vec<Message>> {
+fn extract_messages(doc: &Document) -> anyhow::Result<Vec<Message>> {
let msgs = doc
.find(Attr("id", "messages"))
.next()
- .ok_or("failed to get messages div")?
+ .ok_or(anyhow!("failed to get messages div"))?
.find(Attr("class", "msg"))
.filter_map(|tag| {
let mut id: Option<usize> = None;
@@ -2866,7 +2703,7 @@ impl Events {
)
}
- fn next(&self) -> std::result::Result<Event<CEvent>, crossbeam_channel::RecvError> {
+ fn next(&self) -> Result<Event<CEvent>, crossbeam_channel::RecvError> {
select! {
recv(&self.rx) -> evt => evt,
recv(&self.messages_updated_rx) -> _ => Ok(Event::Tick),