Skip to content

Instantly share code, notes, and snippets.

@boxmein
Last active June 1, 2021 07:51
Show Gist options
  • Save boxmein/6150456 to your computer and use it in GitHub Desktop.
Save boxmein/6150456 to your computer and use it in GitHub Desktop.
An IRC bot that responds to every input on a channel with Markov-generated output. It also learns from every channel message. Put two in a channel for utter chaos!
// Stripped down version of minibot.
// Use as baseline for other bots !
/*jslint node: true */
console.log("-- working directory: " + process.cwd());
var net = require("net"),
fs = require("fs");
var config = {
rawlogging: true,
sendlogging: true,
stdin: true,
nickname: "boxmarkov1",
realname: "boxmein's minibot ~markov",
channels: "##boxmein",
c: "\\", // Command character!
delay: 3591
};
var msgqueue = [];
var markov = {
memory : {},
separator : ' ',
order : 4,
learn : function (txt) {
var mem = this.memory;
this.breakText(txt, learnPart);
function learnPart (key, value) {
if (!mem[key]) {
mem[key] = [];
}
mem[key].push(value);
return mem;
}
},
ask : function (seed) {
if (!seed) {
seed = this.genInitial();
}
return seed.concat(this.step(seed, [])).join(this.separator);
},
step : function (state, ret) {
var nextAvailable = this.memory[state] || [''],
next = nextAvailable.random();
//we don't have anywhere to go
if (!next) {
return ret;
}
ret.push(next);
var nextState = state.slice(1);
nextState.push(next);
return this.step(nextState, ret);
},
breakText : function (txt, cb) {
var parts = txt.split(this.separator),
prev = this.genInitial();
parts.forEach(step);
cb(prev, '');
function step (next) {
cb(prev, next);
prev.shift();
prev.push(next);
}
},
genInitial : function () {
var ret = [];
for (
var i = 0;
i < this.order;
ret.push(''), i++
);
return ret;
}
};
Array.prototype.random = function () {
return this[Math.floor(Math.random() * this.length)];
};
function msgTick() {
if (msgqueue.length === 0)
return;
var msg = msgqueue.shift();
irc.privmsg(msg.chan, msg.msg);
}
var connection = {
client: new net.Socket(),
PORT: 6667,
HOST: "irc.freenode.net",
onConnected: function() {
console.log("** Connection successful");
setTimeout(function() {
irc.raw("NICK " + config.nickname);
irc.raw("USER " + config.nickname + " 8 * :" + config.realname);
irc.raw("JOIN " + config.channels);
console.log("--- --- Finished startup --- ---");
}, 7000);
connection.tick = setInterval(msgTick, config.delay);
// Pre-learn some text
fs.readFile('learn.txt', function (err, chunk) {
console.log('Reading data');
if (err) return console.log("\033]31;1m"+err.stack);
chunk.toString().split("\n").map(function(each) {markov.learn(each);});
});
// /Pre-learning
},
onData: function(data) {
// :boxmein!boxmein@unaffiliated/boxmein PRIVMSG ##powder-bots :this is a test message
if(config.rawlogging)
console.log("<< "+data);
if (data.indexOf("PRIVMSG ") != -1) {
var arr = data.split(' ');
var result = arr.splice(0,3);
result.push(arr.join(' '));
var ircdata = {
hostmask : result[0].substring(1),
channel : result[2],
sender : result[0].substring(1, result[0].indexOf("!")),
message : result[3].substring(1).trim()
};
var cmd;
ircdata.args = ircdata.message.split(" ");
cmd = ircdata.args.shift();
// Collect input from every line
if (config.channels.indexOf(ircdata.channel) !== -1 &&
ircdata.sender.indexOf("boxmarkov") === -1)
markov.learn(ircdata.message);
if (cmd == "\\reinit") {
markov.memory = {};
msgqueue = [];
}
else if (cmd == "\\sep")
markov.separator = markov.separator == ' ' ? '' : ' ';
//else if (cmd == "\\ask")
var response = markov.ask(ircdata.args[Math.floor(Math.random()*ircdata.args.length)]).trim().replace(/^\s*[$;%!\/\-\.>~\()?\`]+/, '');
console.log("\033]32mResponding with "+response+"\033]0m");
//if (markov.separator == ' ')
// response = response.replace(/(.)\s/g, "$1");
respond(ircdata, response, true);
delete response;
if (ircdata.message.indexOf("\x01") === 0)
{
// CTCP!
if (ircdata.message.substring(1).indexOf("VERSION") === 0) {
irc.notice(ircdata.sender, "this bot is awesome ~boxmein");
}
}
}
else if(data.indexOf("PING") != -1) {
irc.raw("PONG " + (data.split(" ")[1] || ":00000000"), true);
}
},
onConnectionClose: function() {
console.log("** Disconnected. ");
process.exit(0);
},
onErr: function(evt) {
console.log(":( Error connecting: " + evt);
}
};
var respond = function(ircdata, message, nonick) {
console.log("==> \033]32;1m" + message + "\033]0m");
msgqueue.push({chan: ircdata.channel, msg: (nonick ? "" : ircdata.sender + ": ") + message});
};
connection.client.setEncoding("ascii");
connection.client.setNoDelay();
connection.client.connect(connection.PORT, connection.HOST, connection.onConnected);
connection.client.on("data", connection.onData);
connection.client.on("close", connection.onConnectionClose);
connection.client.on("error", connection.onErr);
var irc = {
// Sends a CTCP request to the channel or user. Used for ACTION and various CTCP requests.
// @param channel The user or channel to send a request to
// @param data The CTCP data to send. Includes the sub-command.
// Looks like "VERSION" or like "ACTION eats a vegetable"
ctcp: function(channel, data) {
irc.raw("PRIVMSG {0} :\x01{1}\x01".format(channel, data));
},
// Sets a channel mode. Will also work with bans/stuff since mode can contain a name.
// @param channel The channel that will receive the given mode.
// @param mode The mode being set. May either be "+o" or even "+b boxmein!*@*"
mode: function (channel, mode) {
irc.raw("MODE {0} {1}".format(channel, mode));
},
// Sends a NAMES request to the given channel to find out all about the users joined there.
// @param channel The channel to send NAMES to.
names: function (channel) {
irc.raw("NAMES " + channel);
},
// Sends a CTCP ACTION, the equivalent of /me in IRC clients.
// @param channel The channel to send the ACTION to. May also be an username.
// @param action The action to send.
action: function(channel, action) {
irc.ctcp(channel, "ACTION {0}".format(action));
},
// Sends a private message (also a channel message!)
// @param channel The given channel to send to. May also be an username.
// @param message The message that will be sent.
// @param hide Whether or not to hide the sent message. Gets passed on to irc.raw. May be left
// undefined.
privmsg: function(channel, message, hide) {
irc.raw("PRIVMSG {0} :{1}".format(channel, message), hide);
},
// Sends a NOTICE to the channel or user
// @param channel The channel or user to send the given message to.
// @param message The message being sent.
notice: function(channel, message) {
irc.raw("NOTICE {0} :{1}".format(channel, message));
},
// Joins a channel.
// @param channel The channel to join to
join: function(channel) {
irc.raw("JOIN {0}".format(channel));
},
// Removes self from the given channel. No leave messages.
// @param channel The channel to leave from
part: function(channel) {
irc.raw("PART {0}".format(channel));
},
// Quits the IRC. Sends a quit message and waits for the server for 5 seconds then quits
// the program.
quit: function() {
irc.raw("QUIT :Sayonara");
setTimeout(function() {
process.exit(0);
}, 5000);
},
// Sends raw data to the IRC server.
// @param data The data that is to be written, also known as raw IRC. Use wrappers instead :D
// @param hide Whether or not to hide the sending notification into the console.
raw: function(data, hide) {
connection.client.write(data + "\n", "ascii", function() {
if(!hide && config.sendlogging)
console.log(">> " + data);
});
},
// IRC Colours.
col: {
cchar: '\x03', // Control character => 0x03 or ^C.
beep: '\x07', // Bell character
clear: '\x0F', // Clear Formatting
bold: '\x02', // Bold character
italic: '\x15', // Italic formatting
uline: '\x1E', // Underlines
white: 0, // Note: may be interpreted differently
black: 1,
navy: 2,
green: 3,
red: 4,
brown: 5,
purple: 6,
orange: 7,
yellow: 8,
lime: 9,
teal: 10,
cyan: 11,
lblue: 12,
pink: 13,
grey: 14,
lgrey: 15,
c: function(col, bgcol) {
return this.cchar + col + (bgcol? "," + bgcol : '');
}
}
};
if (config.stdin) {
var stdin = process.openStdin();
stdin.on('data', function(data) {
// feel free to add commands here.
data = data.toString().trim();
if (data === '')
return;
var args = data.split(" ");
var cmd = args.shift();
// output.log("stdin.onData", "received input: " + data);
switch (cmd) {
case "say":
case "tell":
case "privmsg":
irc.privmsg(args.shift(), args.join(" "));
break;
case "raw":
case "raw-irc":
args.join(" ");
break;
case "join":
case "join-channel":
irc.join(args.shift());
break;
case "leave-channel":
case "part-channel":
case "part":
irc.part(args.shift());
break;
case "quit-server":
case "quit":
irc.quit();
break;
case "help":
case "list":
console.log(
"[--] Help:\n" +
"[--] privmsg <chan> <msg> - sends message to channel\n" +
"[--] join-channel <chan> - joins a channel\n" +
"[--] raw-irc <RAW> - sends raw IRC\n[--] join <chan> - joins a channel\n" +
"[--] leave-channel <chan> - parts a channel\n[--] quit - quits the server\n" +
"[--] quit-server - quits the server.\n" +
"[--] eval-unsafe <javascript> - runs unsafe Javascript");
break;
case "eval-unsafe":
case "eval":
eval(args.join(" "));
break;
}
});
}
/*
Utility functions courtesy of dearly beloved Stack Overflow
<3
*/
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, "");
};
if (!String.prototype.format) {
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ?
args[number] :
match;
});
};
}
@diveyez
Copy link

diveyez commented Feb 3, 2018

It still work?

@SullySadface
Copy link

Please don't post gore

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment