-
Rob Swindell authoredRob Swindell authored
ircbot.js 12.16 KiB
// $Id: ircbot.js,v 1.38 2020/06/29 18:36:04 echicken Exp $
/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details:
http://www.gnu.org/licenses/gpl.txt
An IRC bot written in JS that interfaces with the local BBS.
Copyright 2010 Randolph E. Sommerfeld <sysop@rrx.ca>
*/
load("sockdefs.js");
load("sbbsdefs.js");
load("irclib.js");
load("funclib.js");
load("http.js");
load("synchronet-json.js");
js.branch_limit=0; /* we're not an infinite loop. */
/* Global Arrays */
var Modules = {};
var Bot_Servers = {};
var Quotes = [];
var Masks = {};
var DCC_Chats = [];
var Squelch_List = [];
/* Global Variables */
var command_prefix = "";
var real_name = "";
var max_paragraph_length = 512;
var max_paragraphs = 1;
var config_write_delay = 300;
var config_last_write = time(); /* Store when the config was last written. */
var config_filename = "ircbot.ini";
for (cmdarg=0;cmdarg<argc;cmdarg++) {
switch(argv[cmdarg].toLowerCase()) {
case "-f":
config_filename = argv[++cmdarg];
break;
default:
break;
}
}
var config = new File(system.ctrl_dir + config_filename);
function init() {
load("ircbot_functions.js");
if (!config.open("r")) {
exit("Couldn't open config file!");
}
command_prefix =
config.iniGetValue(null, "command_prefix", "");
real_name =
config.iniGetValue(null, "real_name", "");
config_write_delay=parseInt(
config.iniGetValue(null, "config_write_delay", 300));
max_paragraph_length = parseInt(
config.iniGetValue(null, "max_paragraph_length", 512));
max_paragraphs = parseInt(
config.iniGetValue(null, "max_paragraphs", 1));
// Squelch_List =
// config.iniGetValue(null, "Squelch_List").split(",");
/* load server & module data */
init_servers(config);
/* load module files */
init_modules();
/* initialize user settings */
init_users();
config.close();
load("ircbot_commands.js");
}
function init_servers(config) {
var server_channels=[];
/* Modules */
var ini_modules = config.iniGetSections("module_");
for (var m in ini_modules) {
var mysec = ini_modules[m];
var module_name = mysec.replace("module_", "");
log ("--- Adding Module: " + module_name);
var lib_list=config.iniGetValue(mysec,"lib");
var lib=new Array();
if(lib_list) {
lib_list=lib_list.split(",");
for(var l in lib_list) {
lib.push(removeSpaces(lib_list[l]));
}
}
var dir=backslash(config.iniGetValue(mysec,"dir"));
var load_list=directory(dir+"*.js");
for(var l in load_list) {
lib.push(load_list[l]);
}
var global=config.iniGetValue(mysec,"global");
var channels=parse_channel_list(config.iniGetValue(mysec, "channels"));
for(var c in channels) {
if(!server_channels[c] && c[0] != "!") {
server_channels[c]=channels[c];
log (" --- Adding Channel: " + c);
}
}
Modules[module_name.toUpperCase()]=new Bot_Module(
module_name,
dir,
global,
channels,
lib
);
}
/* Servers */
var ini_server_secs = config.iniGetSections("server_");
for (s in ini_server_secs) {
var mysec = ini_server_secs[s];
var network = mysec.replace("server_", "");
log ("--- Adding Server: " + network);
var address = config.iniGetValue(mysec, "addresses");
var port = Number(config.iniGetValue(mysec,"port"));
var nick = config.iniGetValue(mysec, "nick");
var pw = config.iniGetValue(mysec, "services_password");
var channels = parse_channel_list(config.iniGetValue(mysec, "channels"));
for(var c in server_channels) {
if(!channels[c]) {
channels[c]=server_channels[c];
}
}
for(var m in Modules) {
if(Modules[m].global == true) {
for(var c in channels) {
if(Modules[m].channels["!" + c] == true) {
channels[c].modules[m]=false;
} else {
channels[c].modules[m]=true;
}
}
} else {
for(var c in Modules[m].channels) {
if(c[0] == "!") {
if(channels[c.substr(1)]) {
channels[c.substr(1)].modules[m]=false;
}
} else {
channels[c].modules[m]=true;
}
}
}
}
Bot_Servers[network.toUpperCase()] = (new Bot_IRC_Server(
0, /* Socket */
address,
nick,
pw,
channels,
port,
network
));
}
}
function init_modules() {
for(var m in Modules) {
for(var l in Modules[m].lib) {
if(Modules[m].lib[l]) load(Modules[m],Modules[m].lib[l]);
}
}
}
function init_users() {
var user_settings_files = directory(system.data_dir + "user/*.ircbot.ini");
for (f in user_settings_files) {
var us_file = new File(user_settings_files[f]);
if (us_file.open("r")) {
var tokenized_path = user_settings_files[f].split("/");
var us_filename = tokenized_path[tokenized_path.length-1];
var uid_str = us_filename.split(".")[0];
while (uid_str[0] == "0") {
uid_str = uid_str.slice(1);
}
log("***Reading: " + us_file.name + "\r\n");
var read_Masks = us_file.iniGetValue(null, "masks");
if (read_Masks)
Masks[parseInt(uid_str)] = read_Masks.split(",");
}
}
}
function main() {
log("*** Entering Main Loop. ***");
while (!js.terminated) {
// Build array of sockets for select()
var socks=[];
for (my_srv in Bot_Servers) {
var srv=Bot_Servers[my_srv];
if (!srv.sock &&(srv.lastcon <time())) { //we're not connected.
var consock = IRC_client_connect(srv.host, srv.nick,
srv.nick, real_name, srv.port);
if (consock) {
srv.sock = consock;
log("--- Connected to " + srv.host);
/* If we just connected, then clear all our joined channels. */
for (c in srv.channel) {
srv.channel[c].is_joined = false;
}
} else {
log("--- Connect to " + srv.host + " failed, "
+ "retry in 60 seconds.");
srv.lastcon = time() + 60;
}
}
else if(srv.sock) {
// Run through some commands.
if (srv.is_registered) {
if(!srv.is_identified) {
/* If we have a password for services, send it now */
if(srv.svspass) srv.writeout("IDENTIFY " + srv.svspass);
// TODO: verify that the raw IDENTIFY command works, and if not, send /MSG NickServ IDENTIFY <Pass>
srv.is_identified = true;
}
for (c in srv.channel) {
if (!srv.channel[c].is_joined &&
(srv.channel[c].lastjoin < time())) {
if(srv.channel[c].key) srv.writeout("JOIN " + srv.channel[c].name + " " + srv.channel[c].key);
else srv.writeout("JOIN " + srv.channel[c].name);
srv.channel[c].lastjoin = time() + 60;
}
// Cycle any available module "main" functions
for(var m in Modules) {
var module=Modules[m];
if(module && module.enabled && module.main) {
try {
module.main(srv,srv.channel[c].name);
} catch(e) {
srv.o(srv.channel[c].name,m.toLowerCase() + " module error: " + e,"NOTICE");
module.enabled=false;
}
}
}
}
}
// Cycle server output buffer (srv.buffer)
while(srv.burst<srv.max_burst) {
srv.burst++;
if(!srv.buffers.length>0) break;
var next_output=srv.buffers.shift();
if(!srv.sock.write(next_output.buffer.shift())) {
srv.sock.close();
srv.sock=0;
continue;
}
if(next_output.buffer.length) srv.buffers.push(next_output);
}
var curtime=system.timer;
if ((curtime-srv.lastout)>srv.delay) {
srv.lastout=system.timer;
srv.burst=0;
}
// Push into the select array
srv.sock.srv=my_srv;
socks.push(srv.sock);
}
}
for (c in DCC_Chats) {
DCC_Chats[c].sock.chat=c;
socks.push(DCC_Chats[c].sock);
}
var ready=socket_select(socks, 0.1);
for(var s in ready) {
if(socks[s].srv != undefined) {
var srv=Bot_Servers[socks[s].srv];
if(srv.sock && (!srv.sock.is_connected)) {
srv.sock.close();
srv.sock=0;
}
if (srv.sock && srv.sock.poll(0.1) &&
(cmdline=srv.sock.recvline(4096,0))) {
var onick;
var ouh;
var outline;
var sorigin = cmdline.split(" ")[0].slice(1);
if ((cmdline[0] == ":") && sorigin.match(/[@]/)) {
onick = sorigin.split("!")[0];
ouh = sorigin.split("!")[1];
outline = "["+onick+"("+ouh+")] " + cmdline;
} else {
onick = "";
ouh = "";
outline = cmdline;
}
log("<-- " + srv.host + ": " + outline);
if(!srv.ignore[onick.toUpperCase()]) {
srv.server_command(srv,cmdline,onick,ouh);
}
}
}
if(socks[s].chat != undefined) {
var c=socks[s].chat;
if (!DCC_Chats[c].sock.is_connected) {
log("Closing session.");
DCC_Chats[c].sock.close();
delete DCC_Chats[c];
continue;
}
if (DCC_Chats[c].waiting_for_password) {
var dcc_pwd;
if (dcc_pwd=DCC_Chats[c].sock.readln()) {
var usr = new User(system.matchuser(DCC_Chats[c].id));
if (!usr ||
(dcc_pwd.toUpperCase() != usr.security.password)) {
DCC_Chats[c].o(null,"Access Denied.");
DCC_Chats[c].sock.close();
delete DCC_Chats[c];
continue;
}
if (dcc_pwd.toUpperCase() == usr.security.password) {
DCC_Chats[c].waiting_for_password = false;
DCC_Chats[c].o(null,"Welcome aboard.");
}
}
continue;
}
var line = DCC_Chats[c].sock.readln();
if (!line || line == "")
continue;
var usr = new User(system.matchuser(DCC_Chats[c].id));
var cmd = line.split(" ");
cmd[0] = cmd[0].toUpperCase();
try {
DCC_Chats[c].bot_command(cmd);
} catch (err) {
DCC_Chats[c].o(null,err);
DCC_Chats[c].o(null,"file: " + err.fileName);
DCC_Chats[c].o(null,"line: " + err.lineNumber);
}
}
}
if ( (time() - config_last_write) > config_write_delay )
save_everything();
}
}
//////////////////// Objects and Functions ////////////////////
function Bot_IRC_Server(sock,host,nick,svspass,channels,port,name) {
// Static variables (never change)
this.sock = sock;
this.host = host;
this.nick = nick;
this.svspass = svspass;
this.port = port;
this.name = name;
// Dynamic variables (used for the bot's state.
this.curnick = nick;
this.lastcon = 0; // When it's OK to reconnect.
this.lastout = 0; // When it's OK to send the next socket ouput
this.burst = 0;
this.max_burst = 5;
this.delay = 3;
this.is_registered = false;
this.is_identified = false;
this.juped = false;
this.users = {}; // Store local nicks & uh info.
this.buffers= [];
this.ignore = [];
this.channel = channels;
// Functions
this.ctcp = Server_CTCP;
this.ctcp_reply = Server_CTCP_reply;
this.o = Server_target_out;
this.writeout = Server_writeout;
this.bot_access = Server_bot_access;
this.bot_command = Server_bot_command;
this.server_command = Server_command;
this.get_buffer = Server_get_buffer;
}
function Bot_IRC_Channel(name,key) {
// Statics.
this.name = name;
this.key = key;
// Dynamics.
this.is_joined = false;
this.lastjoin = 0;
// Modules active in this channel (associative)
this.modules = [];
// Functions.
}
function Bot_Module(name,dir,global,channels,lib) {
this.name=name;
this.dir=dir;
this.global=global;
this.channels=channels;
this.lib=lib;
this.enabled=true;
this.Bot_Commands = {};
this.Server_Commands = {};
}
function Server_User(uh,nick) {
this.uh = uh;
this.nick = nick;
this.servername = undefined;
this.ident = false;
this.last_spoke = false;
this.channels = [];
}
function Server_Buffer(target) {
this.target = target;
this.buffer = [];
}
function Bot_Command(min_security,args_needed,ident_needed) {
this.min_security = min_security;
this.args_needed = args_needed;
this.ident_needed = ident_needed;
this.command = false;
}
function DCC_Chat(sock,id) {
this.sock = sock;
this.id = id;
/* State info */
this.waiting_for_password = true;
/* Functions */
this.o = DCC_Out;
this.bot_command = function(cmd) {
Server_bot_command(this,Bot_Commands,null,this.id,null,cmd);
for(var bot_cmd in Modules) {
Server_bot_command(
this,
Modules[bot_cmd].Bot_Commands,
null,
this.id,
null,
cmd
);
}
}
}
function DCC_Out(target,str) {
this.sock.write(str + "\r\n");
}
init();
main();