WIP: hellfire #1
18
config.toml
18
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_port = 1080
|
||||
proxy_username = ""
|
||||
proxy_password = ""
|
||||
|
||||
#[features]
|
||||
kickrejoin = true
|
||||
ascii_art = "./ircart/ircart"
|
||||
pump_delay = 0 # in milliseconds
|
||||
|
||||
|
67
src/main.rs
67
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,7 +16,8 @@ struct Config {
|
||||
port: u16,
|
||||
use_ssl: bool,
|
||||
nickname: String,
|
||||
channel: String,
|
||||
realname: Option<String>,
|
||||
channels: Vec<String>,
|
||||
sasl_username: Option<String>,
|
||||
sasl_password: Option<String>,
|
||||
capabilities: Option<Vec<String>>,
|
||||
@ -27,15 +29,19 @@ struct Config {
|
||||
proxy_port: Option<u16>,
|
||||
proxy_username: Option<String>,
|
||||
proxy_password: Option<String>,
|
||||
|
||||
ascii_art: Option<String>,
|
||||
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<dyn std::error::Error>> {
|
||||
@ -106,10 +112,9 @@ async fn proxy_exec(config: &Config) -> Result<TcpStream, Box<dyn std::error::Er
|
||||
let proxy_stream = TcpStream::connect(proxy).await.unwrap();
|
||||
let username = config.proxy_username.clone().unwrap();
|
||||
let password = config.proxy_password.clone().unwrap();
|
||||
let tcp_stream = if !&username.is_empty() && !password.is_empty() {
|
||||
let tcp_stream = Socks5Stream::connect_with_password_and_socket(proxy_stream, server, &username, &password).await.unwrap();
|
||||
let mut tcp_stream = if !&username.is_empty() && !password.is_empty() {
|
||||
let tcp_stream =Socks5Stream::connect_with_password_and_socket(proxy_stream, server, &username, &password).await.unwrap();
|
||||
tcp_stream
|
||||
|
||||
} else {
|
||||
let tcp_stream = Socks5Stream::connect_with_socket(proxy_stream, server).await.unwrap();
|
||||
tcp_stream
|
||||
@ -156,8 +161,6 @@ async fn readmsg(mut reader: tokio::io::ReadHalf<tokio_native_tls::TlsStream<Tcp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static SASL_AUTH: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Write messages to the server
|
||||
@ -166,14 +169,15 @@ async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<T
|
||||
let username = config.sasl_username.clone().unwrap();
|
||||
let password = config.sasl_password.clone().unwrap();
|
||||
let nickname = config.nickname.clone();
|
||||
let realname = config.realname.clone().unwrap_or(nickname.clone());
|
||||
if !password.is_empty() && !SASL_AUTH.load(Ordering::Relaxed) {
|
||||
let capabilities = config.capabilities.clone();
|
||||
println!("Starting SASL auth...");
|
||||
start_sasl_auth(&mut writer, "PLAIN", &nickname, capabilities).await.unwrap();
|
||||
start_sasl_auth(&mut writer, "PLAIN", &nickname, &realname, capabilities).await.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
SASL_AUTH.store(true, Ordering::Relaxed);
|
||||
} else {
|
||||
nickme(&mut writer, &nickname).await.unwrap();
|
||||
nickme(&mut writer, &nickname, &realname).await.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
}
|
||||
|
||||
@ -206,36 +210,59 @@ async fn writemsg(mut writer: tokio::io::WriteHalf<tokio_native_tls::TlsStream<T
|
||||
}
|
||||
|
||||
if *cmd == "376" {
|
||||
println!("Joining channel");
|
||||
writer.write_all(format!("JOIN {}\r\n", config.channel).as_bytes()).await.unwrap();
|
||||
println!("Joining channels");
|
||||
for channel in &config.channels {
|
||||
writer.write_all(format!("JOIN {}\r\n", channel).as_bytes()).await.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
}
|
||||
}
|
||||
if *cmd == "KICK" {
|
||||
let channel = parts[2];
|
||||
let userme = parts[3];
|
||||
if userme == nickname {
|
||||
writer.write_all(format!("JOIN {}\r\n", channel).as_bytes()).await.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
}
|
||||
}
|
||||
if *cmd == "PRIVMSG" {
|
||||
let channel = parts[2];
|
||||
let user = parts[0].strip_prefix(':').unwrap().split_at(parts[0].find('!').unwrap()).0.strip_suffix('!').unwrap();
|
||||
let host = parts[0].split_at(parts[0].find('!').unwrap()).1;
|
||||
let user = parts[0].strip_prefix(':')
|
||||
.and_then(|user_with_host| user_with_host.split('!').next())
|
||||
.unwrap_or("unknown_user");
|
||||
let host = parts[0].split('@').nth(1).unwrap_or("unknown_host");
|
||||
let msg_content = parts[3..].join(" ").replace(':', "");
|
||||
println!("{} {} {} {} {} {} {}", "DEBUG:".bold().yellow(), "channel:".bold().green(), channel.purple(), "user:".bold().green(), host.purple(), "msg:".bold().green(), msg_content.purple());
|
||||
println!("{} {} {} {} {} {} {} {} {}", "DEBUG:".bold().yellow(), "channel:".bold().green(), channel.purple(), "user:".bold().green(), user.purple(), "host:".bold().green(), host.purple(), "msg:".bold().green(), msg_content.yellow());
|
||||
|
||||
// sed
|
||||
if msg_content.starts_with("s/") {
|
||||
println!("Sed command detected");
|
||||
if let Some(sed_command) = SedCommand::parse(&msg_content) {
|
||||
if let Some(sed_command) = SedCommand::parse(&msg_content.clone()) {
|
||||
if let Some(response) = message_buffer.apply_sed_command(&sed_command) {
|
||||
writer.write_all(format!("PRIVMSG {} :{}: {}\r\n", channel, user, response).as_bytes()).await.unwrap();
|
||||
writer.flush().await.unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message_buffer.add_message(msg_content);
|
||||
message_buffer.add_message(msg_content.clone());
|
||||
}
|
||||
|
||||
// ansi art
|
||||
//
|
||||
if msg_content.starts_with("%ascii") {
|
||||
handle_ascii_command(&mut writer, &config, &msg_content, channel).await;
|
||||
}
|
||||
|
||||
|
||||
// other commands here
|
||||
}
|
||||
}
|
||||
}
|
||||
async fn nickme<W: tokio::io::AsyncWriteExt + Unpin>(writer: &mut W, nickname: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
|
||||
|
||||
async fn nickme<W: tokio::io::AsyncWriteExt + Unpin>(writer: &mut W, nickname: &str, realname: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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(())
|
||||
}
|
||||
|
157
src/mods/ascii.rs
Normal file
157
src/mods/ascii.rs
Normal file
@ -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<W: AsyncWriteExt + Unpin>(writer: &mut W, file_path: &str, pump_delay: u64, channel: &str) -> Result<(), Box<dyn Error>> {
|
||||
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<String> {
|
||||
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::<Vec<String>>();
|
||||
|
||||
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<W: AsyncWriteExt + Unpin>(
|
||||
writer: &mut W,
|
||||
config: &Config, // Adjust the path as necessary to match your project structure
|
||||
command: &str,
|
||||
channel: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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<W: AsyncWriteExt + Unpin>(
|
||||
writer: &mut W,
|
||||
config: &Config,
|
||||
channel: &str,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
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<W: AsyncWriteExt + Unpin>(
|
||||
writer: &mut W,
|
||||
config: &Config,
|
||||
channel: &str,
|
||||
subdirectory: Option<&str>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
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::<Vec<String>>()
|
||||
.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<W: AsyncWriteExt + Unpin>(
|
||||
writer: &mut W,
|
||||
config: &Config,
|
||||
channel: &str,
|
||||
parts: &[&str],
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,11 @@ pub async fn start_sasl_auth<W: tokio::io::AsyncWriteExt + Unpin>(
|
||||
writer: &mut W,
|
||||
mechanism: &str,
|
||||
nickname: &str,
|
||||
realname: &str,
|
||||
capabilities: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user