Last active
June 1, 2021 07:51
-
-
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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
}); | |
}; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It still work?