WIP: hellfire #1
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Cargo.lock
|
||||||
|
/target
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
@ -6,16 +6,15 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
openssl = "0.10.45"
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
async-openai = "0.6.1"
|
|
||||||
tokio = { version = "1.23.0", features = ["full"] }
|
|
||||||
rand = "0.8.4"
|
|
||||||
regex = "1.7.1"
|
|
||||||
toml = "0.7.2"
|
|
||||||
serde = "1.0.152"
|
|
||||||
tokio-openssl = "0.6.3"
|
|
||||||
colored = "2"
|
|
||||||
tokio-socks = "0.5.1"
|
tokio-socks = "0.5.1"
|
||||||
socks = "0.3.4"
|
tokio-rustls = "0.25.0"
|
||||||
random_word = "0.3.0"
|
tokio-native-tls = "0.3.1"
|
||||||
#leetspeak = "0.2.0"
|
native-tls = "0.2.11"
|
||||||
|
rand = "0.8.5"
|
||||||
|
toml = "0.8.10"
|
||||||
|
base64 = "0.22.0"
|
||||||
|
regex = "1.10.3"
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
colored = "2.1.0"
|
||||||
|
futures = "0.3.30"
|
||||||
|
38
config.toml
38
config.toml
@ -1,18 +1,26 @@
|
|||||||
nick = "g1r"
|
#[server]
|
||||||
server = "ircd.chat"
|
server = "198.98.52.138" #"irc.supernets.org"
|
||||||
port = 6697
|
port = 6697
|
||||||
password = ""
|
use_ssl = true
|
||||||
channels = [ "#tcpdirect", "#macros", "#b0tsh0p" ] #add key access
|
|
||||||
admin_users = ["s4d", "s4d[m]", "g"] # add host identification
|
|
||||||
ignore_users = ["maple", "aibird", "professorOak", "van"]
|
|
||||||
openai = "sk-"
|
|
||||||
model = "text-davinci-003"
|
|
||||||
accents = "funny, completely insane, and hyperactive"
|
|
||||||
personalities = "GIR from Invader Zim"
|
|
||||||
|
|
||||||
proxy_server = "127.0.0.1"
|
#[user]
|
||||||
proxy_port = 9050
|
nickname = "g1r"
|
||||||
|
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 = 1080
|
||||||
|
proxy_username = ""
|
||||||
|
proxy_password = ""
|
||||||
|
|
||||||
|
#[features]
|
||||||
|
kickrejoin = true
|
||||||
|
ascii_art = "./ircart/ircart"
|
||||||
|
pump_delay = 0 # in milliseconds
|
||||||
|
|
||||||
# INVADER SETTINGS
|
|
||||||
invaders = ["d1b", "z1m", "t4k", "m3mbr4n3", "g4z", "4l4n", "m1yuk1", "purpl3", "r3d", "5p0rk", "t3nn", "l4rb", "ch1nn", "d00ky", "3l", "fl0rb3", "g00ch", "gr4p4", "gr00t", "k00t", "k1m", "krunk", "l4dn4r", "n3n", "p3st0", "p00t"]
|
|
||||||
proxy_list = "./proxies.txt"
|
|
||||||
|
78
proxies.txt
78
proxies.txt
@ -1,78 +0,0 @@
|
|||||||
184.185.2.12:4145
|
|
||||||
174.138.33.62:59166
|
|
||||||
206.220.175.2:4145
|
|
||||||
96.126.113.216:59166
|
|
||||||
205.240.77.164:4145
|
|
||||||
174.75.211.222:4145
|
|
||||||
72.210.252.137:4145
|
|
||||||
192.111.129.145:16894
|
|
||||||
68.71.254.6:4145
|
|
||||||
159.203.13.82:59166
|
|
||||||
107.170.81.141:59166
|
|
||||||
159.65.220.89:59166
|
|
||||||
192.241.143.216:59166
|
|
||||||
149.56.247.67:59166
|
|
||||||
157.245.223.201:59166
|
|
||||||
203.57.114.228:59166
|
|
||||||
192.111.135.18:18301
|
|
||||||
192.252.216.81:4145
|
|
||||||
68.183.90.210:59166
|
|
||||||
123.171.245.162:1080
|
|
||||||
72.195.34.58:4145
|
|
||||||
208.111.40.144:59166
|
|
||||||
98.162.25.29:31679
|
|
||||||
192.252.214.20:15864
|
|
||||||
95.213.228.10:59166
|
|
||||||
142.93.77.151:59166
|
|
||||||
72.195.114.169:4145
|
|
||||||
192.111.135.17:18302
|
|
||||||
75.119.157.170:59166
|
|
||||||
75.127.13.195:59166
|
|
||||||
167.71.10.234:59166
|
|
||||||
51.255.219.244:59166
|
|
||||||
51.178.82.49:59166
|
|
||||||
184.178.172.14:4145
|
|
||||||
68.71.247.130:4145
|
|
||||||
192.111.137.37:18762
|
|
||||||
176.58.125.165:59166
|
|
||||||
205.186.162.190:59166
|
|
||||||
47.243.95.228:10080
|
|
||||||
142.54.228.193:4145
|
|
||||||
168.196.160.61:59166
|
|
||||||
138.68.124.120:59166
|
|
||||||
174.138.63.144:59166
|
|
||||||
74.119.147.209:4145
|
|
||||||
68.183.20.254:59166
|
|
||||||
67.205.181.126:59166
|
|
||||||
139.144.31.132:59166
|
|
||||||
159.89.49.172:59166
|
|
||||||
43.226.26.120:59166
|
|
||||||
23.253.253.26:59166
|
|
||||||
206.189.157.253:59166
|
|
||||||
184.170.248.5:4145
|
|
||||||
159.203.78.174:59166
|
|
||||||
46.101.37.189:59166
|
|
||||||
184.170.249.65:4145
|
|
||||||
192.111.137.34:18765
|
|
||||||
167.71.190.131:59166
|
|
||||||
174.64.199.79:4145
|
|
||||||
184.178.172.25:15291
|
|
||||||
45.79.46.53:59166
|
|
||||||
40.87.45.45:59166
|
|
||||||
199.58.184.97:4145
|
|
||||||
198.8.94.170:4145
|
|
||||||
192.252.208.67:14287
|
|
||||||
184.170.245.148:4145
|
|
||||||
69.164.224.53:59166
|
|
||||||
68.71.249.153:48606
|
|
||||||
143.198.229.170:59166
|
|
||||||
165.227.153.96:59166
|
|
||||||
192.252.220.89:4145
|
|
||||||
138.197.185.192:59166
|
|
||||||
151.80.45.47:59166
|
|
||||||
69.194.181.6:7497
|
|
||||||
170.210.156.33:59166
|
|
||||||
209.94.62.123:59166
|
|
||||||
198.8.94.174:39078
|
|
||||||
184.178.172.23:4145
|
|
||||||
72.221.232.155:4145
|
|
379
src/main.rs
379
src/main.rs
@ -1,151 +1,270 @@
|
|||||||
use std::io::prelude::*;
|
use tokio::io::{split, AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt, BufReader, AsyncBufReadExt};
|
||||||
use std::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use std::io::Write;
|
use tokio_native_tls::native_tls::TlsConnector as NTlsConnector;
|
||||||
use openssl::ssl::{SslMethod, SslConnector};
|
use tokio_native_tls::TlsConnector;
|
||||||
use toml::Value;
|
use tokio::sync::mpsc;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
use tokio_socks::tcp::Socks5Stream;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
mod modules {
|
|
||||||
pub trait Command {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String>;
|
|
||||||
}
|
|
||||||
pub mod ping;
|
|
||||||
pub mod kill;
|
|
||||||
pub mod ai;
|
|
||||||
pub mod invade;
|
|
||||||
pub mod test;
|
|
||||||
//pub mod ai_invade;
|
|
||||||
}
|
|
||||||
|
|
||||||
use modules::ai::Ai; // FIX THIS BS
|
|
||||||
use modules::ping::PingCommand;
|
|
||||||
use modules::invade::InvadeCommand;
|
|
||||||
//use modules::ai_invade::AiInvadeCommand;
|
|
||||||
use modules::kill::KillCommand; // ...
|
|
||||||
use crate::modules::Command;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Config {
|
struct Config {
|
||||||
server: String,
|
server: String,
|
||||||
port: u16,
|
port: u16,
|
||||||
nick: String,
|
use_ssl: bool,
|
||||||
password: String,
|
nickname: String,
|
||||||
|
realname: Option<String>,
|
||||||
channels: Vec<String>,
|
channels: Vec<String>,
|
||||||
admin_users: Vec<String>,
|
sasl_username: Option<String>,
|
||||||
ignore_users: Vec<String>,
|
sasl_password: Option<String>,
|
||||||
|
capabilities: Option<Vec<String>>,
|
||||||
|
|
||||||
|
reconnect_delay: u64,
|
||||||
|
reconnect_attempts: u64,
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
use_proxy: bool,
|
||||||
|
// proxy_type: Option<String>,
|
||||||
|
proxy_addr: Option<String>,
|
||||||
|
proxy_port: Option<u16>,
|
||||||
|
proxy_username: Option<String>,
|
||||||
|
proxy_password: Option<String>,
|
||||||
|
|
||||||
|
ascii_art: Option<String>,
|
||||||
|
pump_delay: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
mod mods {
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
pub mod proxy;
|
||||||
.enable_all()
|
pub mod tls;
|
||||||
.build()
|
pub mod handler;
|
||||||
.unwrap();
|
pub mod sasl;
|
||||||
|
pub mod sed;
|
||||||
|
pub mod ascii;
|
||||||
|
pub mod vomit;
|
||||||
|
// pub mod invade;
|
||||||
|
}
|
||||||
|
use mods::proxy::proxy_exec;
|
||||||
|
use mods::tls::tls_exec;
|
||||||
|
use mods::handler::handler;
|
||||||
|
use mods::sasl::{start_sasl_auth, handle_sasl_messages};
|
||||||
|
use mods::sed::{SedCommand, MessageBuffer};
|
||||||
|
use mods::ascii::handle_ascii_command;
|
||||||
|
use mods::vomit::handle_vomit_command;
|
||||||
|
//use mods::invade::{handle_invade_command};
|
||||||
|
|
||||||
// LOAD CONFIG
|
#[tokio::main(flavor = "multi_thread", worker_threads = 12)]
|
||||||
let config_str = std::fs::read_to_string("config.toml").unwrap();
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let config_value = config_str.parse::<Value>().unwrap();
|
println!("Loading Config...");
|
||||||
let config: Config = config_value.try_into().unwrap();
|
let config = loaded_config().expect("Error parsing config.toml");
|
||||||
// GIVE THE SERVER A SLOPPPY SPAM OF RETARDEDNESS
|
println!("Config loaded!");
|
||||||
let stream = TcpStream::connect(format!("{}:{}", config.server, config.port)).unwrap();
|
let mut reconnect_attempts = 0;
|
||||||
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
|
|
||||||
// DONT DO DRUGS YOU WILL END UP LIKE ME
|
|
||||||
let mut ssl_stream = connector.connect(&config.server, stream).unwrap();
|
|
||||||
let nick_command = format!("NICK {}_\r\n", config.nick); //setup passwords
|
|
||||||
let user_command = format!("USER {} 0 * :{}\r\n", config.nick, config.nick);
|
|
||||||
ssl_stream.write_all(nick_command.as_bytes()).unwrap();
|
|
||||||
ssl_stream.write_all(user_command.as_bytes()).unwrap();
|
|
||||||
let identify_command = format!("PRIVMSG NickServ :IDENTIFY {} {}\r\n", config.nick, config.password);
|
|
||||||
ssl_stream.write(identify_command.as_bytes()).unwrap();
|
|
||||||
let channels = config.channels.join(",");
|
|
||||||
let join_command = format!("JOIN {}\r\n", channels);
|
|
||||||
|
|
||||||
let admin_users = config.admin_users; // ADMINS
|
while reconnect_attempts < config.reconnect_attempts {
|
||||||
let ignored_users = config.ignore_users; // IGNORED
|
let configc = config.clone();
|
||||||
// ...
|
|
||||||
ssl_stream.write_all(join_command.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
let mut buf = [0; 512];
|
let server = format!("{}:{}", configc.server, configc.port);
|
||||||
loop {
|
let connection_result = tokio::spawn(async move {
|
||||||
match ssl_stream.read(&mut buf) {
|
let config = configc.clone();
|
||||||
Ok(0) => break,
|
let tcp_stream = if config.use_proxy {
|
||||||
Ok(n) => {
|
match proxy_exec(&config).await {
|
||||||
let received = String::from_utf8_lossy(&buf[0..n]);
|
Ok(stream) => stream,
|
||||||
let message = received.trim();
|
Err(e) => {
|
||||||
|
println!("Error connecting to proxy: {}", e);
|
||||||
//debug chat
|
return Ok::<(), Box<dyn std::error::Error + Send>>(());
|
||||||
println!("{} {}","[%] DEBUG:".bold().green(), received.purple());
|
|
||||||
|
|
||||||
|
|
||||||
// RESPOND TO PINGS
|
|
||||||
if message.starts_with("PING") {
|
|
||||||
println!("{} {}","[%] PONG:".bold().green(), config.nick.blue()); // DEBUG
|
|
||||||
ssl_stream.write_all("PONG ircd.chat\r\n".as_bytes()).unwrap();
|
|
||||||
continue; // skip processing the PING message further
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match TcpStream::connect(server).await {
|
||||||
|
Ok(stream) => stream,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error connecting to server: {}", e);
|
||||||
|
return Ok::<(), Box<dyn std::error::Error + Send>>(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// MODULES
|
if config.use_ssl {
|
||||||
let ping_command = PingCommand;
|
println!("Connecting to SSL server...");
|
||||||
let kill_command = KillCommand;
|
match tls_exec(&config, tcp_stream).await {
|
||||||
let invade_command = InvadeCommand;
|
Ok(tls_stream) => handler(tls_stream, config).await.unwrap(),
|
||||||
//let ai_invade_command = AiInvadeCommand;
|
Err(e) => {
|
||||||
|
println!("Error establishing TLS connection: {}", e);
|
||||||
//let test_command = TestCommand;
|
return Ok::<(), Box<dyn std::error::Error + Send>>(());
|
||||||
let ai = Ai;
|
}
|
||||||
|
}
|
||||||
// ADMIN MODULES
|
} else {
|
||||||
if message.starts_with(":") && message.contains(" :%") {
|
println!("Connecting to Non-SSL server...");
|
||||||
let parts: Vec<&str> = message.splitn(2, ' ').collect(); // Check if user is admin_user
|
handler(tcp_stream, config).await.unwrap();
|
||||||
let username = parts[0].trim_start_matches(':').split("!").next().unwrap();
|
}
|
||||||
if !admin_users.contains(&username.to_string()) {
|
Ok::<(), Box<dyn std::error::Error + Send>>(())
|
||||||
println!("{} {}","[!] UNAUTHORIZED:".bold().clear().on_red(), username.red().bold());
|
}).await.unwrap();
|
||||||
continue; // ...
|
|
||||||
}
|
|
||||||
if message.contains(":%ping") {
|
|
||||||
for response in ping_command.handle(message) {
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
} else if message.contains(":%kill") {
|
|
||||||
for response in kill_command.handle(message) {
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
} else if message.contains(":%invade") {
|
|
||||||
for response in invade_command.handle(message) {
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
} //else if message.contains(":%aiinvade") {
|
|
||||||
// for response in ai_invade_command.handle(message) {
|
|
||||||
// ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the message is user and respond via ai
|
|
||||||
else if message.starts_with(":") && message.contains("PRIVMSG ") && message.contains(&config.nick) { //modify for on mention
|
|
||||||
let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap();
|
|
||||||
if !channels.contains(&channel) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// extract the username from the first part and check if ignored
|
|
||||||
let parts: Vec<&str> = message.splitn(2, ' ').collect(); // split the message into two parts at the first space
|
|
||||||
let username = parts[0].trim_start_matches(':').split("!").next().unwrap();
|
|
||||||
if ignored_users.contains(&username.to_string()) {
|
|
||||||
println!("[!] IGNORED: {}", username.red());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for response in ai.handle(message, ) {
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
match connection_result {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Connection established successfully!");
|
||||||
|
reconnect_attempts = 0;
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("[!] ERROR: {}", e);
|
println!("Error handling connection: {}", e);
|
||||||
break;
|
reconnect_attempts += 1;
|
||||||
},
|
tokio::time::sleep(tokio::time::Duration::from_secs(config.reconnect_delay)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Reconnect attempts exceeded. Exiting...");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the config file
|
||||||
|
fn loaded_config() -> Result<Config, Box<dyn std::error::Error>> {
|
||||||
|
let config_contents = fs::read_to_string("config.toml")?;
|
||||||
|
let config: Config = toml::from_str(&config_contents)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read messages from the server
|
||||||
|
async fn readmsg<S>(mut reader: tokio::io::ReadHalf<S>, tx: tokio::sync::mpsc::Sender<String>) where S: AsyncRead + Unpin {
|
||||||
|
let mut buf = vec![0; 4096];
|
||||||
|
while let Ok (n) = reader.read(&mut buf).await {
|
||||||
|
if n == 0 { break; }
|
||||||
|
let msg_list = String::from_utf8_lossy(&buf[..n]).to_string();
|
||||||
|
for lines in msg_list.lines() {
|
||||||
|
let msg = lines.to_string();
|
||||||
|
println!("{}{}{} {}{} {}", "[".green().bold(), ">".yellow().bold(), "]".green().bold(), "DEBUG:".bold().yellow(), ":".bold().green(), msg.trim().purple());
|
||||||
|
tx.send(msg).await.unwrap();
|
||||||
|
if buf.len() == n {
|
||||||
|
buf.resize(buf.len() * 2, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SASL_AUTH: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
/// Write messages to the server
|
||||||
|
async fn writemsg<S>(mut writer: tokio::io::WriteHalf<S>, mut rx: tokio::sync::mpsc::Receiver<String>, config: &Config, mut message_buffer: MessageBuffer) where S: AsyncWrite + Unpin {
|
||||||
|
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, &realname, capabilities).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
SASL_AUTH.store(true, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
nickme(&mut writer, &nickname, &realname).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(msg) = rx.recv().await {
|
||||||
|
let msg = msg.trim();
|
||||||
|
if msg.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let parts = msg.split(' ').collect::<Vec<&str>>();
|
||||||
|
let serv = parts.first().unwrap_or(&"");
|
||||||
|
let cmd = parts.get(1).unwrap_or(&"");
|
||||||
|
|
||||||
|
println!("{} {} {} {} {}", "DEBUG:".bold().yellow(), "serv:".bold().green(), serv.purple(), "cmd:".bold().green(), cmd.purple());
|
||||||
|
if *serv == "PING" {
|
||||||
|
let response = msg.replace("PING", "PONG") + "\r\n";
|
||||||
|
println!("{} {} {}","[%] PONG:".bold().green(), nickname.blue(), response.purple());
|
||||||
|
writer.write_all(response.as_bytes()).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (*cmd == "CAP" || msg.starts_with("AUTHENTICATE +") || *cmd == "903") && SASL_AUTH.load(Ordering::Relaxed) {
|
||||||
|
println!("Handling SASL messages...");
|
||||||
|
handle_sasl_messages(&mut writer, msg.trim(), &username, &password, &nickname).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
}
|
||||||
|
if *cmd == "001" {
|
||||||
|
println!("Setting mode");
|
||||||
|
writer.write_all(format!("MODE {} +B\r\n", nickname).as_bytes()).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if *cmd == "376" {
|
||||||
|
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.get(2).unwrap_or(&"");
|
||||||
|
let userme = parts.get(3).unwrap_or(&"");
|
||||||
|
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.get(2).to_owned().unwrap_or(&"");
|
||||||
|
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 = if parts.len() > 3 {
|
||||||
|
let remainder = &parts[3..].join(" ");
|
||||||
|
if let Some(pos) = remainder.find(':') {
|
||||||
|
let (first_part, last_part) = remainder.split_at(pos);
|
||||||
|
format!("{}{}", first_part, &last_part[1..])
|
||||||
|
} else {
|
||||||
|
remainder.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
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/") {
|
||||||
|
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, response).as_bytes()).await.unwrap();
|
||||||
|
writer.flush().await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message_buffer.add_message(msg_content.clone().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ansi art
|
||||||
|
if msg_content.starts_with("%ascii") {
|
||||||
|
let _ = handle_ascii_command(&mut writer, config, &msg_content, channel).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vomit
|
||||||
|
if msg_content.starts_with("%vomit") {
|
||||||
|
let _ = handle_vomit_command(&mut writer, config, &msg_content, channel).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invade
|
||||||
|
// if msg_content.starts_with("%invade") {
|
||||||
|
// let _ = handle_vomit_command(&mut writer, config, &msg_content, channel).await;
|
||||||
|
// }
|
||||||
|
// other commands here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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, realname).as_bytes()).await?;
|
||||||
|
writer.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
160
src/mods/ascii.rs
Normal file
160
src/mods/ascii.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
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,
|
||||||
|
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(2).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,
|
||||||
|
parts: 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) = parts {
|
||||||
|
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>> {
|
||||||
|
println!("{:?}", parts);
|
||||||
|
let file_name = if parts.len() > 2 {
|
||||||
|
parts[1..].join(" ").replace(' ', "/")
|
||||||
|
} else {
|
||||||
|
parts.get(1).unwrap_or(&"").to_string()
|
||||||
|
};
|
||||||
|
println!("{:?}", file_name);
|
||||||
|
|
||||||
|
let file_path = format!("{}/{}.txt", config.ascii_art.clone().unwrap_or_else(|| "ascii_art".to_string()), file_name);
|
||||||
|
println!("{:?}", file_path);
|
||||||
|
|
||||||
|
send_ansi_art(writer, &file_path, config.pump_delay, channel).await
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
src/mods/handler.rs
Normal file
24
src/mods/handler.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// mods/handler.rs
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, split};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use crate::{Config, readmsg, writemsg, MessageBuffer};
|
||||||
|
|
||||||
|
/// Handle the connection to the server
|
||||||
|
pub async fn handler<S>(stream: S, config: Config) -> Result<(), Box<dyn std::error::Error>> where S: AsyncRead + AsyncWrite + Unpin + Send + 'static {
|
||||||
|
let (reader, writer) = split(stream);
|
||||||
|
let (tx, rx) = mpsc::channel(1000);
|
||||||
|
|
||||||
|
let read_task = tokio::spawn(async move {
|
||||||
|
readmsg(reader, tx).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let message_buffer = MessageBuffer::new(1000);
|
||||||
|
|
||||||
|
let write_task = tokio::spawn(async move {
|
||||||
|
writemsg(writer, rx, &config, message_buffer).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
//let _ = tokio::try_join!(read_task, write_task);
|
||||||
|
tokio::try_join!(read_task, write_task).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
5
src/mods/invade.rs
Normal file
5
src/mods/invade.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// mods/invade.rs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
28
src/mods/proxy.rs
Normal file
28
src/mods/proxy.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// mods/proxy.rs
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_socks::tcp::Socks5Stream;
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
/// Establish a connection to the proxy
|
||||||
|
pub async fn proxy_exec(config: &Config) -> Result<TcpStream, Box<dyn std::error::Error + Send>> {
|
||||||
|
let proxy_addr = match config.proxy_addr.as_ref() {
|
||||||
|
Some(addr) => addr,
|
||||||
|
None => "127.0.0.1",
|
||||||
|
};
|
||||||
|
let proxy_port = config.proxy_port.unwrap_or(9050);
|
||||||
|
let proxy = format!("{}:{}", proxy_addr, proxy_port);
|
||||||
|
let server = format!("{}:{}", config.server, config.port);
|
||||||
|
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();
|
||||||
|
tcp_stream
|
||||||
|
} else {
|
||||||
|
let tcp_stream = Socks5Stream::connect_with_socket(proxy_stream, server).await.unwrap();
|
||||||
|
tcp_stream
|
||||||
|
};
|
||||||
|
let tcp_stream = tcp_stream.into_inner();
|
||||||
|
|
||||||
|
Ok(tcp_stream)
|
||||||
|
}
|
45
src/mods/sasl.rs
Normal file
45
src/mods/sasl.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// mods/sasl.rs
|
||||||
|
use crate::nickme;
|
||||||
|
use base64::Engine;
|
||||||
|
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, realname).await?;
|
||||||
|
|
||||||
|
if let Some(caps) = capabilities {
|
||||||
|
if !caps.is_empty() {
|
||||||
|
let cap_req_cmd = format!("CAP REQ :{}\r\n", caps.join(" "));
|
||||||
|
writer.write_all(cap_req_cmd.as_bytes()).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writer.write_all(b"CAP REQ :sasl\r\n").await?;
|
||||||
|
}
|
||||||
|
//println!("Handling SASL messages...");
|
||||||
|
writer.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_sasl_messages<W: tokio::io::AsyncWriteExt + Unpin>(
|
||||||
|
writer: &mut W,
|
||||||
|
message: &str,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
nickname: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if message.contains(format!("CAP {} ACK :sasl", nickname).as_str()) {
|
||||||
|
writer.write_all(b"AUTHENTICATE PLAIN\r\n").await?;
|
||||||
|
} else if message.starts_with("AUTHENTICATE +") {
|
||||||
|
let auth_string = format!("\0{}\0{}", username, password);
|
||||||
|
let encoded = base64::engine::general_purpose::STANDARD.encode(auth_string);
|
||||||
|
writer.write_all(format!("AUTHENTICATE {}\r\n", encoded).as_bytes()).await?;
|
||||||
|
} else if message.contains("903 * :SASL authentication successful") {
|
||||||
|
writer.write_all(b"CAP END\r\n").await?;
|
||||||
|
}
|
||||||
|
writer.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
70
src/mods/sed.rs
Normal file
70
src/mods/sed.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
pub struct SedCommand {
|
||||||
|
pattern: Regex,
|
||||||
|
replacement: String,
|
||||||
|
global: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SedCommand {
|
||||||
|
pub fn parse(command: &str) -> Option<Self> {
|
||||||
|
let parts: Vec<&str> = command.split('/').collect();
|
||||||
|
if parts.len() >= 4 {
|
||||||
|
let pattern = match Regex::new(parts[1]) {
|
||||||
|
Ok(regex) => regex,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
let replacement = parts[2].to_string();
|
||||||
|
let global = parts.get(3).map_or(false, |&flag| flag.contains('g'));
|
||||||
|
|
||||||
|
Some(SedCommand {
|
||||||
|
pattern,
|
||||||
|
replacement,
|
||||||
|
global,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_to(&self, message: &str) -> String {
|
||||||
|
if self.global {
|
||||||
|
self.pattern.replace_all(message, self.replacement.as_str()).to_string()
|
||||||
|
} else {
|
||||||
|
self.pattern.replace(message, self.replacement.as_str()).to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MessageBuffer {
|
||||||
|
buffer: VecDeque<String>,
|
||||||
|
capacity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageBuffer {
|
||||||
|
pub fn new(capacity: usize) -> Self {
|
||||||
|
MessageBuffer {
|
||||||
|
buffer: VecDeque::with_capacity(capacity),
|
||||||
|
capacity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_message(&mut self, message: String) {
|
||||||
|
if self.buffer.len() == self.capacity {
|
||||||
|
self.buffer.pop_front();
|
||||||
|
}
|
||||||
|
self.buffer.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_sed_command(&mut self, command: &SedCommand) -> Option<String> {
|
||||||
|
for message in self.buffer.iter_mut() {
|
||||||
|
if command.pattern.is_match(message) {
|
||||||
|
*message = command.apply_to(message);
|
||||||
|
return Some(message.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
12
src/mods/tls.rs
Normal file
12
src/mods/tls.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// mods/tls.rs
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
use tokio_native_tls::{TlsConnector, native_tls::TlsConnector as NTlsConnector};
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
// Establish a TLS connection to the server
|
||||||
|
pub async fn tls_exec(config: &Config, tcp_stream: TcpStream) -> Result<tokio_native_tls::TlsStream<TcpStream>, Box<dyn std::error::Error + Send>> {
|
||||||
|
let tls_builder = NTlsConnector::builder().danger_accept_invalid_certs(true).build().unwrap();
|
||||||
|
let tls_connector = TlsConnector::from(tls_builder);
|
||||||
|
Ok(tls_connector.connect(&config.server, tcp_stream).await.unwrap())
|
||||||
|
|
||||||
|
}
|
77
src/mods/vomit.rs
Normal file
77
src/mods/vomit.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
use rand::prelude::*;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::time;
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
async fn generate_random_unicode() -> char {
|
||||||
|
let codepoint: u32 = thread_rng().gen_range(0..=0x10FFFF);
|
||||||
|
std::char::from_u32(codepoint).unwrap_or(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate_random_color() -> u8 {
|
||||||
|
thread_rng().gen_range(0..=15)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate_random_format() -> char {
|
||||||
|
match thread_rng().gen_range(0..=2) {
|
||||||
|
0 => '\x02', // Bold
|
||||||
|
1 => '\x1D', // Italic
|
||||||
|
_ => '\x1F', // Underline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn generate_vomit(length: usize) -> String {
|
||||||
|
let mut irc_text = String::new();
|
||||||
|
for _ in 0..length {
|
||||||
|
let char = generate_random_unicode().await;
|
||||||
|
let color_code = generate_random_color().await;
|
||||||
|
let format_code = generate_random_format().await;
|
||||||
|
irc_text.push_str(&format!("\x03{:02}{}", color_code, format_code));
|
||||||
|
irc_text.push(char);
|
||||||
|
}
|
||||||
|
irc_text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_into_chunks(s: &str, max_chunk_size: usize) -> Vec<String> {
|
||||||
|
let mut chunks = Vec::new();
|
||||||
|
let mut current_chunk = String::new();
|
||||||
|
|
||||||
|
for char in s.chars() {
|
||||||
|
if current_chunk.len() + char.len_utf8() > max_chunk_size {
|
||||||
|
chunks.push(current_chunk.clone());
|
||||||
|
current_chunk.clear();
|
||||||
|
}
|
||||||
|
current_chunk.push(char);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_chunk.is_empty() {
|
||||||
|
chunks.push(current_chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 400;
|
||||||
|
// Function to handle the vomit command
|
||||||
|
pub async fn handle_vomit_command<W: AsyncWriteExt + Unpin>(
|
||||||
|
writer: &mut W,
|
||||||
|
config: &Config,
|
||||||
|
command: &str,
|
||||||
|
channel: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||||
|
let length = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(1000);
|
||||||
|
|
||||||
|
let vomit = generate_vomit(length).await; // Correctly awaited
|
||||||
|
let chunks = split_into_chunks(&vomit, CHUNK_SIZE); // Adjust if split_into_chunks is async
|
||||||
|
|
||||||
|
for chunk in chunks {
|
||||||
|
writer.write_all(format!("PRIVMSG {} :{}\r\n", channel, chunk).as_bytes()).await?;
|
||||||
|
writer.flush().await?;
|
||||||
|
time::sleep(tokio::time::Duration::from_secs(config.pump_delay)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
// Check if the message is user and respond via ai
|
|
||||||
use async_openai::{Client, types::{CreateCompletionRequestArgs}};
|
|
||||||
use regex::Regex;
|
|
||||||
use crate::modules::Command;
|
|
||||||
use toml::Value;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use colored::*;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
nick: String,
|
|
||||||
openai: String,
|
|
||||||
model: String,
|
|
||||||
accents: String,
|
|
||||||
personalities: String,
|
|
||||||
}
|
|
||||||
pub struct Ai;
|
|
||||||
// setup a prompt and respnse log for training other bots
|
|
||||||
impl Command for Ai {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String> {
|
|
||||||
let mut responses = Vec::new();
|
|
||||||
let config_str = std::fs::read_to_string("config.toml").unwrap();
|
|
||||||
let config_value = config_str.parse::<Value>().unwrap();
|
|
||||||
let config: Config = config_value.try_into().unwrap(); // respond to name with and without leet VVV
|
|
||||||
if message.starts_with(":") && message.contains("PRIVMSG ") && message.contains(&config.nick) {
|
|
||||||
let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap(); // set the response to varible
|
|
||||||
let user_message = format!("The following is a chat log:\n{}\nRespond {} as you are chatting as {}: \n\n",
|
|
||||||
message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(),
|
|
||||||
config.accents,
|
|
||||||
config.personalities
|
|
||||||
);
|
|
||||||
let parts: Vec<&str> = message.splitn(2, ' ').collect();
|
|
||||||
let username = parts[0].trim_start_matches(':').split("!").next().unwrap();
|
|
||||||
|
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
||||||
let result = rt.block_on(ai(&user_message, &username, &channel));
|
|
||||||
responses.extend(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
responses
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async fn ai(user_message: &str, username: &str, channel: &str) -> Vec<String> {
|
|
||||||
let config_str = std::fs::read_to_string("config.toml").unwrap();
|
|
||||||
let config_value = config_str.parse::<Value>().unwrap();
|
|
||||||
let config: Config = config_value.try_into().unwrap();
|
|
||||||
let api_key = config.openai; // set this from config
|
|
||||||
|
|
||||||
let client = Client::new().with_api_key(api_key);
|
|
||||||
println!("{} {} {}: {}", "[?]".on_green().bold(), "PROMPT:".green().bold(), username, user_message);
|
|
||||||
let chat_request = CreateCompletionRequestArgs::default()
|
|
||||||
.prompt(user_message)
|
|
||||||
.max_tokens(40_u16)
|
|
||||||
.model(config.model)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let chat_response = client
|
|
||||||
.completions()
|
|
||||||
.create(chat_request)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
println!("{} {} {}","[+]".on_green().bold(), "RESPONSE:".green().bold(), chat_response.choices.first().unwrap().text);
|
|
||||||
//modify regex for varible username ie G1R g1r GIR gir but as handle nick for bots
|
|
||||||
let response_text = &chat_response.choices.first().unwrap().text;
|
|
||||||
let regex = Regex::new(r#""|[gG][1iI][rR]:\s"#).unwrap(); // THIS IS FUCKING UP EVERYTHING
|
|
||||||
//let nick = &config.nick;
|
|
||||||
//let regex_str = format!(
|
|
||||||
// r#""|[{}{}{}]|\b[gG][1iI][rR]:\s*|\b[mM][eE]:?\s"#,
|
|
||||||
// nick.to_lowercase(),
|
|
||||||
// nick.to_uppercase(),
|
|
||||||
// nick.chars().map(|c| match c { /// regex magic nick removal in progress
|
|
||||||
// 'a' => '4',
|
|
||||||
// 'e' => '3',
|
|
||||||
// 'i' => '1',
|
|
||||||
// 'o' => '0',
|
|
||||||
// 's' => '5',
|
|
||||||
// _ => c,
|
|
||||||
// }).collect::<String>(),
|
|
||||||
//);
|
|
||||||
//let regex = Regex::new(®ex_str).unwrap();
|
|
||||||
let response_text = regex.replace_all(response_text, "").trim().to_string();
|
|
||||||
let response_lines = response_text.split("\n").filter(|line| !line.trim().is_empty());
|
|
||||||
let mut responses = Vec::new();
|
|
||||||
for line in response_lines {
|
|
||||||
responses.push(format!("PRIVMSG {} :\x0313{}\x0f: {}\r\n", channel, username, line));
|
|
||||||
}
|
|
||||||
|
|
||||||
responses
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
use crate::modules::Command;
|
|
||||||
|
|
||||||
use std::io::{BufRead, BufReader, Write};
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use openssl::ssl::{SslConnector, SslMethod};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use toml::{Value, to_string};
|
|
||||||
use colored::*;
|
|
||||||
|
|
||||||
// add better error handling
|
|
||||||
// add ai invasion
|
|
||||||
// add proxy support
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
//invaders: Vec<String>,
|
|
||||||
server: String,
|
|
||||||
port: u16,
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct InvadeCommand;
|
|
||||||
|
|
||||||
impl Command for InvadeCommand {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String> {
|
|
||||||
let mut response = vec![];
|
|
||||||
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%invade") {
|
|
||||||
let parts: Vec<&str> = message.split_whitespace().collect();
|
|
||||||
let num_invaders = parts[4].parse::<u32>().unwrap_or(1) as usize;
|
|
||||||
let channel = parts[2];
|
|
||||||
let invadechannel = parts[5];
|
|
||||||
let scream = if parts.len() > 6 { parts[6] } else { "" }; // read entire message
|
|
||||||
//message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(),
|
|
||||||
//let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap();
|
|
||||||
let config_str = std::fs::read_to_string("config.toml").unwrap();
|
|
||||||
let config_value = config_str.parse::<Value>().unwrap();
|
|
||||||
let config: Config = config_value.try_into().unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
for _invader in 1..=num_invaders {//&config.invaders[1..num_invaders] { //1..=20 {
|
|
||||||
let thread_channel = invadechannel.to_string();
|
|
||||||
let config_clone = config.clone();
|
|
||||||
let screaming = scream.to_string();
|
|
||||||
let command_channel = channel.to_string();
|
|
||||||
let thread_invader = random_word::gen(); // change to leetspeak on nick collision
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let stream = TcpStream::connect((config_clone.server.as_str(), config_clone.port)).unwrap();
|
|
||||||
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
|
|
||||||
let mut ssl_stream = connector.connect(config_clone.server.as_str(), stream).unwrap();
|
|
||||||
let nick_command = format!("NICK {}\r\n", thread_invader);
|
|
||||||
let user_command = format!("USER {} 0 * :{}\r\n", thread_invader, thread_invader);
|
|
||||||
ssl_stream.write_all(nick_command.as_bytes()).unwrap();
|
|
||||||
ssl_stream.write_all(user_command.as_bytes()).unwrap();
|
|
||||||
let join_command = format!("JOIN {} \r\n", thread_channel);
|
|
||||||
let commander = format!("JOIN {} \r\n", command_channel);
|
|
||||||
ssl_stream.write_all(commander.as_bytes()).unwrap();
|
|
||||||
ssl_stream.write_all(join_command.as_bytes()).unwrap();
|
|
||||||
let msg = format!("PRIVMSG {} :{}\r\n", thread_channel, screaming);
|
|
||||||
ssl_stream.write_all(msg.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let mut buf = [0; 512];
|
|
||||||
match ssl_stream.ssl_read(&mut buf) {
|
|
||||||
Ok(0) => break,
|
|
||||||
Ok(n) => {
|
|
||||||
let received = String::from_utf8_lossy(&buf[0..n]);
|
|
||||||
let message = received.trim();
|
|
||||||
|
|
||||||
//debug chat
|
|
||||||
println!("{} {} {}","[%] DEBUG:".bold().green(), thread_invader.green(), received.blue());
|
|
||||||
|
|
||||||
if message.starts_with("PING") {
|
|
||||||
let response = message.replace("PING", "PONG");
|
|
||||||
println!("{} {}","[%] PONG:".bold().green(), thread_invader.blue());
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
// turn to mods
|
|
||||||
// setup so these will only run from the server admin to avoid handle/host conflicts
|
|
||||||
let commandi = format!("PRIVMSG {} :%%",command_channel); // & check for admin and verify with server
|
|
||||||
|
|
||||||
if message.contains(&commandi) && message.contains(":%%") {
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%join") { // fix so commands get picked up faster
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%join ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
let response = format!("JOIN {} \r\n", invade_channel);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%leave") {
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%leave ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
let response = format!("PART {} \r\n", invade_channel);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%scream") {
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%scream ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
if parts.len() == 2 {
|
|
||||||
let scream = parts[1];
|
|
||||||
let response = format!("PRIVMSG {} :{}\r\n", invade_channel, scream);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ...1
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{} {}","[!] ERROR FROM SERVER:".on_red(), e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
response.push(format!("PRIVMSG {} :\x0304,01[!] INVADING {} WITH {} INVADERS...\x0f\r\n", channel, invadechannel, num_invaders));
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
use crate::modules::Command;
|
|
||||||
|
|
||||||
use std::io::{BufRead, BufReader, Write};
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use openssl::ssl::{SslConnector, SslMethod};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use toml::{Value, to_string};
|
|
||||||
use colored::*;
|
|
||||||
use socks::*;
|
|
||||||
// add better error handling
|
|
||||||
// add ai invasion
|
|
||||||
// add proxy support
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
|
||||||
struct Config {
|
|
||||||
//invaders: Vec<String>,
|
|
||||||
server: String,
|
|
||||||
port: u16,
|
|
||||||
|
|
||||||
proxy_server: String,
|
|
||||||
proxy_port: u16,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct InvadeCommand;
|
|
||||||
|
|
||||||
impl Command for InvadeCommand {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String> {
|
|
||||||
let mut response = vec![];
|
|
||||||
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%invade") {
|
|
||||||
let parts: Vec<&str> = message.split_whitespace().collect();
|
|
||||||
let num_invaders = parts[4].parse::<u32>().unwrap_or(1) as usize;
|
|
||||||
let channel = parts[2];
|
|
||||||
let invadechannel = parts[5];
|
|
||||||
let scream = if parts.len() > 6 { parts[6] } else { "" }; // read entire message
|
|
||||||
//message.split(&format!("PRIVMSG {} :", channel.to_string())).nth(1).unwrap(),
|
|
||||||
//let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap();
|
|
||||||
let config_str = std::fs::read_to_string("config.toml").unwrap();
|
|
||||||
let config_value = config_str.parse::<Value>().unwrap();
|
|
||||||
let config: Config = config_value.try_into().unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
for _invader in 1..=num_invaders {//&config.invaders[1..num_invaders] { //1..=20 {
|
|
||||||
let thread_channel = invadechannel.to_string();
|
|
||||||
let config_clone = config.clone();
|
|
||||||
let screaming = scream.to_string();
|
|
||||||
let command_channel = channel.to_string();
|
|
||||||
let thread_invader = random_word::gen(); // change to leetspeak on nick collision
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
|
|
||||||
let stream = TcpStream::connect((config_clone.server.as_str(), config_clone.port)).unwrap();
|
|
||||||
let connector = SslConnector::builder(SslMethod::tls()).unwrap().build();
|
|
||||||
let mut ssl_stream = connector.connect(config_clone.server.as_str(), stream).unwrap();
|
|
||||||
|
|
||||||
let nick_command = format!("NICK {}\r\n", thread_invader);
|
|
||||||
let user_command = format!("USER {} 0 * :{}\r\n", thread_invader, thread_invader);
|
|
||||||
ssl_stream.write_all(nick_command.as_bytes()).unwrap();
|
|
||||||
ssl_stream.write_all(user_command.as_bytes()).unwrap();
|
|
||||||
let join_command = format!("JOIN {} \r\n", thread_channel);
|
|
||||||
let commander = format!("JOIN {} \r\n", command_channel);
|
|
||||||
ssl_stream.write_all(commander.as_bytes()).unwrap();
|
|
||||||
ssl_stream.write_all(join_command.as_bytes()).unwrap();
|
|
||||||
let msg = format!("PRIVMSG {} :{}\r\n", thread_channel, screaming);
|
|
||||||
ssl_stream.write_all(msg.as_bytes()).unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let mut buf = [0; 512];
|
|
||||||
match ssl_stream.ssl_read(&mut buf) {
|
|
||||||
Ok(0) => break,
|
|
||||||
Ok(n) => {
|
|
||||||
let received = String::from_utf8_lossy(&buf[0..n]);
|
|
||||||
let message = received.trim();
|
|
||||||
|
|
||||||
//debug chat
|
|
||||||
println!("{} {} {}","[%] DEBUG:".bold().green(), thread_invader.green(), received.blue());
|
|
||||||
|
|
||||||
if message.starts_with("PING") {
|
|
||||||
let response = message.replace("PING", "PONG");
|
|
||||||
println!("{} {}","[%] PONG:".bold().green(), thread_invader.blue());
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
// turn to mods
|
|
||||||
// setup so these will only run from the server admin to avoid handle/host conflicts
|
|
||||||
let commandi = format!("PRIVMSG {} :%%",command_channel); // & check for admin and verify with server
|
|
||||||
|
|
||||||
if message.contains(&commandi) && message.contains(":%%") {
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%join") { // fix so commands get picked up faster
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%join ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
let response = format!("JOIN {} \r\n", invade_channel);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%leave") {
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%leave ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
let response = format!("PART {} \r\n", invade_channel);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%%scream") {
|
|
||||||
let parts: Vec<&str> = message.splitn(3, ":%%scream ").collect();
|
|
||||||
let invade_channel = parts[1];
|
|
||||||
if parts.len() == 2 {
|
|
||||||
let scream = parts[1];
|
|
||||||
let response = format!("PRIVMSG {} :{}\r\n", invade_channel, scream);
|
|
||||||
ssl_stream.write_all(response.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ...1
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("{} {}","[!] ERROR FROM SERVER:".on_red(), e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
response.push(format!("PRIVMSG {} :\x0304,01[!] INVADING {} WITH {} INVADERS...\x0f\r\n", channel, invadechannel, num_invaders));
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
use crate::modules::Command;
|
|
||||||
|
|
||||||
pub struct KillCommand;
|
|
||||||
impl Command for KillCommand {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String> {
|
|
||||||
let mut response = vec![];
|
|
||||||
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%kill") {
|
|
||||||
let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap();
|
|
||||||
response.push(format!("PRIVMSG {} :SELF DESTRUCTING...\r\n", channel));
|
|
||||||
println!("[!] KILLING!");
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
use std::time::{Instant};
|
|
||||||
use crate::modules::Command;
|
|
||||||
pub struct PingCommand;
|
|
||||||
impl Command for PingCommand {
|
|
||||||
fn handle(&self, message: &str) -> Vec<String> {
|
|
||||||
let mut response = vec![];
|
|
||||||
|
|
||||||
if message.contains("PRIVMSG") && message.contains(":%ping") {
|
|
||||||
let channel = message.split("PRIVMSG ").nth(1).and_then(|s| s.splitn(2, ' ').next()).unwrap();
|
|
||||||
let start = Instant::now();
|
|
||||||
let elapsed = start.elapsed();
|
|
||||||
response.push(format!("PRIVMSG {} :PONG: {:?}\r\n", channel, elapsed));
|
|
||||||
}
|
|
||||||
response
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user