diff --git a/config.toml b/config.toml index 15883e5..6e1a529 100644 --- a/config.toml +++ b/config.toml @@ -1,14 +1,26 @@ -server = "irc.supernets.org" +#[server] +server = "198.98.52.138" #"irc.supernets.org" port = 6697 use_ssl = true + +#[user] nickname = "g1r" -channel = "#superbowl" +realname = "git.supernets.org/sad/g1r" +channels = ["#dev", "#superbowl", "#5000"] sasl_username = "" sasl_password = "" capabilities = ["sasl"] + +#[proxy] use_proxy = false proxy_type = "socks5" proxy_addr = "127.0.0.1" -proxy_port = 9050 -proxy_username = "" +proxy_port = 1080 +proxy_username = "" proxy_password = "" + +#[features] +kickrejoin = true +ascii_art = "./ircart/ircart" +pump_delay = 0 # in milliseconds + diff --git a/src/main.rs b/src/main.rs index d840741..869c543 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ -use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}; +use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt, BufReader, AsyncBufReadExt}; use tokio::net::TcpStream; use tokio_native_tls::native_tls::TlsConnector as NTlsConnector; use tokio_native_tls::TlsConnector; use tokio::sync::mpsc; use serde::Deserialize; use std::fs; + use std::sync::atomic::{AtomicBool, Ordering}; use colored::*; use tokio_socks::tcp::Socks5Stream; @@ -15,11 +16,12 @@ struct Config { port: u16, use_ssl: bool, nickname: String, - channel: String, + realname: Option, + channels: Vec, sasl_username: Option, sasl_password: Option, capabilities: Option>, - + // Proxy use_proxy: bool, proxy_type: Option, @@ -27,15 +29,19 @@ struct Config { proxy_port: Option, proxy_username: Option, proxy_password: Option, + + ascii_art: Option, + pump_delay: u64, } mod mods { pub mod sasl; pub mod sed; + pub mod ascii; } use mods::sasl::{start_sasl_auth, handle_sasl_messages}; use mods::sed::{SedCommand, MessageBuffer}; - +use mods::ascii::handle_ascii_command; #[tokio::main(flavor = "multi_thread", worker_threads = 12)] async fn main() -> Result<(), Box> { @@ -106,10 +112,9 @@ async fn proxy_exec(config: &Config) -> Result(writer: &mut W, nickname: &str) -> Result<(), Box> { + + + +async fn nickme(writer: &mut W, nickname: &str, realname: &str) -> Result<(), Box> { writer.write_all(format!("NICK {}\r\n", nickname).as_bytes()).await?; writer.flush().await?; - writer.write_all(format!("USER {} 0 * :{}\r\n", nickname, nickname).as_bytes()).await?; + writer.write_all(format!("USER {} 0 * :{}\r\n", nickname, realname).as_bytes()).await?; writer.flush().await?; Ok(()) } diff --git a/src/mods/ascii.rs b/src/mods/ascii.rs new file mode 100644 index 0000000..cb151b8 --- /dev/null +++ b/src/mods/ascii.rs @@ -0,0 +1,157 @@ +use tokio::io::{AsyncWriteExt, BufReader}; +use tokio::fs::File; +use tokio::time::{self, Duration}; +use std::fs; +use rand::Rng; +use tokio::io::AsyncBufReadExt; +use std::error::Error; +use crate::Config; + +const CHUNK_SIZE: usize = 4096; + +async fn send_ansi_art(writer: &mut W, file_path: &str, pump_delay: u64, channel: &str) -> Result<(), Box> { + let file = File::open(file_path).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + + let mut line_count = 0; + let lines_stream = &mut lines; + while let Ok(Some(_)) = lines_stream.next_line().await { + line_count += 1; + } + let mut pump_delay = Duration::from_millis(pump_delay); + if line_count > 500 && pump_delay < Duration::from_millis(100){ + pump_delay = Duration::from_millis(100); + } + let file = File::open(file_path).await?; + let reader = BufReader::new(file); + let mut lines = reader.lines(); + + + while let Some(line) = lines.next_line().await? { + + if line.len() > CHUNK_SIZE { + for chunk in line.as_bytes().chunks(CHUNK_SIZE) { + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, String::from_utf8_lossy(chunk)).as_bytes()).await?; + writer.flush().await?; + time::sleep(pump_delay).await; + } + } else { + + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, line).as_bytes()).await?; + writer.flush().await?; + time::sleep(pump_delay).await; + } + } + Ok(()) +} + +fn select_random_file(dir: &str) -> Option { + let files = fs::read_dir(dir).ok()?.filter_map(|entry| { + let path = entry.ok()?.path(); + if path.is_file() { + path.to_str().map(ToString::to_string) + } else { + None + } + }).collect::>(); + + if files.is_empty() { + None + } else { + let mut rng = rand::thread_rng(); + let index = rng.gen_range(0..files.len()); + files.get(index).cloned() + } +} + +pub async fn handle_ascii_command( + writer: &mut W, + config: &Config, // Adjust the path as necessary to match your project structure + command: &str, + channel: &str, +) -> Result<(), Box> { + let parts: Vec<&str> = command.split_whitespace().collect(); + let command_type = parts.get(1).unwrap_or(&""); + + if *command_type == "random" && parts.len() == 2 { + handle_random(writer, config, channel).await?; + } else if *command_type == "list"{ + handle_list(writer, config, channel, Some(parts.get(3).unwrap_or(&""))).await?; + } else { + handle_specific_file(writer, config, channel, &parts).await?; + } + + Ok(()) +} + +async fn handle_random( + writer: &mut W, + config: &Config, + channel: &str, +) -> Result<(), Box> { + if let Some(dir) = config.ascii_art.as_ref() { + if let Some(random_file) = select_random_file(dir) { + send_ansi_art(writer, &random_file, config.pump_delay, channel).await?; + } else { + writer.write_all(format!("PRIVMSG {} :No files found\r\n", channel).as_bytes()).await?; + } + } + Ok(()) +} + +async fn handle_list( + writer: &mut W, + config: &Config, + channel: &str, + subdirectory: Option<&str>, +) -> Result<(), Box> { + let base_dir = config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()); + let dir = if let Some(subdir) = subdirectory { + format!("{}/{}", base_dir, subdir) + } else { + base_dir + }; + + let entries = fs::read_dir(&dir) + .map_err(|_| "Failed to read directory")? + .filter_map(|entry| entry.ok()) + .map(|entry| { + let path = entry.path(); + let display_name = path.file_name().unwrap_or_default().to_string_lossy().into_owned(); + if path.is_dir() { + format!("{}/", display_name) + } else { + display_name.strip_suffix(".txt").unwrap_or(&display_name).to_string() + } + }) + .collect::>() + .join(", "); + + if entries.is_empty() { + writer.write_all(format!("PRIVMSG {} :No files or directories found\r\n", channel).as_bytes()).await?; + } else { + writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, entries).as_bytes()).await?; + } + + Ok(()) +} + +async fn handle_specific_file( + writer: &mut W, + config: &Config, + channel: &str, + parts: &[&str], +) -> Result<(), Box> { + let file_name = if parts.len() >= 3 { + parts[2..].join(" ") + } else { + parts[2].to_string() + }; + + let file_path = format!("{}/{}.txt", config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()), file_name); + + send_ansi_art(writer, &file_path, config.pump_delay, channel).await +} + + diff --git a/src/mods/sasl.rs b/src/mods/sasl.rs index bdef9d8..5b94b34 100644 --- a/src/mods/sasl.rs +++ b/src/mods/sasl.rs @@ -5,10 +5,11 @@ pub async fn start_sasl_auth( writer: &mut W, mechanism: &str, nickname: &str, + realname: &str, capabilities: Option>) -> Result<(), Box> { writer.write_all(b"CAP LS 302\r\n").await?; - nickme(writer, nickname).await?; + nickme(writer, nickname, realname).await?; if let Some(caps) = capabilities { if !caps.is_empty() {