diff --git a/.gitignore b/.gitignore index ffd5a46..2d34204 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ config.json blacklist.json blacklist.log +memories.json # ---> Node # Logs diff --git a/README.md b/README.md index 3b6e348..c2eaddd 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ aewens's IRC bot for irc.tilde.team $ cp config.example.json config.json $ cp blacklist.example.json blacklist.json + +$ cp memories.example.json memories.json ``` 3. Fill out the entries in `config.json` 4. Run the bot using `node bot.js` or `npm start` diff --git a/bot.js b/bot.js index 27f3df2..0554dbb 100644 --- a/bot.js +++ b/bot.js @@ -1,15 +1,20 @@ #!/usr/bin/env node const fs = require("fs"); +const path = require("path"); + const irc = require("irc"); const Summon = require("./spells/summon"); const BotList = require("./spells/botlist"); // const Banish = require("./spells/banish"); +// const Pardon = require("./spells/pardon"); +const Hmm = require("./spells/hmm"); const botName = "BabiliBot" const client = new irc.Client("localhost", botName, { channels: [ - "#bots" + "#bots", + "#meta" ], localAddress: "127.0.0.1", port: 6667, @@ -24,11 +29,14 @@ const client = new irc.Client("localhost", botName, { client.config = JSON.parse(fs.readFileSync("config.json", "utf8")); client.config.botName = botName; +client.config.dirname = __dirname; client.spells = [ Summon, BotList, - // Banish + // Banish, + // Pardon, + Hmm ]; client.triggers = { @@ -51,7 +59,9 @@ client.triggers = { }; // NOTE - For my personal copy, jan6 will remain here until #chaos is mine -client.blacklist = JSON.parse(fs.readFileSync("blacklist.json", "utf8")); +client.blacklist = JSON.parse(fs.readFileSync( + path.resolve(__dirname, "blacklist.json"), "utf8" +)); // Format: // { @@ -60,6 +70,33 @@ client.blacklist = JSON.parse(fs.readFileSync("blacklist.json", "utf8")); // } client.memory = []; +client.brain = {}; +// Persistent memories +client.brain.memories = JSON.parse(fs.readFileSync( + path.resolve(__dirname, "memories.json"), "utf8" +)); +// For backup of last state of memories +client.brain.backup = JSON.parse(fs.readFileSync( + path.resolve(__dirname, "memories.json"), "utf8" +)); +// How you commit memories to file +client.brain.remember = () => { + const memories = JSON.stringify(client.brain.memories); + const file = path.resolve(__dirname, "memories.json"); + + const dirtyClone = (x) => JSON.parse(JSON.stringify(x)); + + fs.writeFile(file, memories, (err) => { + if (err) { + client.brain.memories = dirtyClone(client.brain.backup); + console.log(`${Date.now()}: Reverting back to backup memories`); + throw err; + } + client.brain.backup = dirtyClone(client.brain.memories); + console.log(`${Date.now()}: Memories have been saved`); + }); +} + const contains = (text, test) => { return text.indexOf(test) > -1; } @@ -75,7 +112,8 @@ const logBlacklisters = (from, message) => { const addBlacklister = (from, message) => { // Log reason for adding to the blacklist const blacklistReason = `${from} was added for spamming ${message}\n`; - fs.appendFile("blacklisters.log", blacklistReason, (err) => { + const log = path.resolve(__dirname, "blacklist.log"); + fs.appendFile(log, blacklistReason, (err) => { if (err) throw err; console.log("Blacklist", blacklistReason); }); @@ -88,7 +126,8 @@ const addBlacklister = (from, message) => { // Apply to future sessions const blacklist = JSON.stringify(client.blacklist); - fs.writeFile("blacklist.json", blacklist, (err) => { + const file = path.resolve(__dirname, "blacklist.json"); + fs.writeFile(file, blacklist, (err) => { if (err) throw err; console.log("Added to blacklist:", from); }); @@ -204,19 +243,38 @@ const checkBlacklist = (client, from, to, message) => { return true; } -client.addListener("message", (from, to, _message) => { - console.log(`${from} => ${to}: ${_message}`); - - const message = _message.toLowerCase(); +const runLogic = (client, from, to, message) => { const triggerWord = client.config.triggerWord; const address = botName.toLowerCase(); const triggerNames = Object.keys(client.triggers); - + + const tildebridge = new RegExp(/^<~?([^>]+)>\s/); + + // Handle tildebridge + if (from === "tildebridge") { + const parsedMessage = message.split(tildebridge); + from = parsedMessage[1]; + message = parsedMessage[2]; + } + + const hmm = new RegExp(/.*hm+.*/); + + if (message.match(hmm) && !message.startsWith("!hmmscore")) { + client.brain.memories.hmmscores = client.brain.memories.hmmscores || {}; + let hmmscore = client.brain.memories.hmmscores[from]; + if (!hmmscore) { + client.brain.memories.hmmscores[from] = 0; + hmmscore = 0; + } + client.brain.memories.hmmscores[from] = hmmscore + 1; + client.brain.remember(); + } + if (message.startsWith(triggerWord)) { // NOTE: entry for !cast const incantation = message.split(triggerWord)[1].trim(); const check = checkBlacklist(client, from, to, incantation); - + if (check) { castSpell(client, from, to, incantation); } @@ -230,22 +288,18 @@ client.addListener("message", (from, to, _message) => { } else if (cheatCode.startsWith(", ")) { cheatCode = cheatCode.split(", ")[1]; } - + cheatCode = cheatCode.trim(); const check = checkBlacklist(client, from, to, cheatCode); if (check) { useCheatCode(client, from, to, cheatCode); } - } else if (message.startsWith("!summon ")) { - // NOTE: alias for !cast kunvoki - by popular demand - const cheatCode = message.slice(1).trim(); - const check = checkBlacklist(client, from, to, cheatCode); - if (check) { - useCheatCode(client, from, to, cheatCode); - } - } else if (message.startsWith("!botlist")) { - // NOTE: alias for !cast lerta bots - to adhere to chat standards + } else if ( + message.startsWith("!summon ") || + message.startsWith("!hmmscore ") || + message.startsWith("!botlist") + ) { const cheatCode = message.slice(1).trim(); const check = checkBlacklist(client, from, to, cheatCode); if (check) { @@ -254,7 +308,7 @@ client.addListener("message", (from, to, _message) => { } else if (message.startsWith(`!help ${address}`)) { // NOTE: Help information PM'd to requester client.say(to, `Komprenita, sending help info to ${from}`); - client.say(from, `I answer have ${client.triggers.length} commands:`); + client.say(from, `I answer have ${triggerNames.length} commands:`); triggerNames.forEach((name) => { const trigger = client.triggers[name]; let helpText = `${name} ${trigger.vars}`; @@ -263,10 +317,16 @@ client.addListener("message", (from, to, _message) => { }); client.say(from, "I also respond to: `BabiliBot, `"); } +} + +client.addListener("message", (from, to, message) => { + console.log(`${from} => ${to}: ${message}`); + runLogic(client, from, to, message.toLowerCase()); }); client.addListener("pm", (from, message) => { console.log(`${from} => ME: ${message}`); + runLogic(client, from, botName, message.toLowerCase()); }); client.addListener("error", (message) => { @@ -280,4 +340,5 @@ client.addListener("registered", () => { client.connect(); -client.join("#bots"); \ No newline at end of file +client.join("#bots"); +client.join("#meta"); \ No newline at end of file diff --git a/config.example.json b/config.example.json index bbc441b..63f3dec 100644 --- a/config.example.json +++ b/config.example.json @@ -6,6 +6,7 @@ "triggerWord": "", "password": "", "api": { - "pushbullet": "" + "pushbullet": "", + "jsonwhoisapi": "" } } \ No newline at end of file diff --git a/memories.example.json b/memories.example.json new file mode 100644 index 0000000..e69de29 diff --git a/package-lock.json b/package-lock.json index 8a9e0cb..76fe4b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,15 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, + "axios": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", + "requires": { + "follow-redirects": "^1.3.0", + "is-buffer": "^1.1.5" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -127,6 +136,24 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "follow-redirects": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", + "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", + "requires": { + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -198,6 +225,11 @@ "resolved": "https://registry.npmjs.org/irc-colors/-/irc-colors-1.4.2.tgz", "integrity": "sha512-QZ1g4d9XTGKgBAp7lrltCetefqd3zfYs3SFQ4YyRSORORCmy/9EkU/r8LJrlSnaWc3Z+54EgHXBRlOHaCvpyHA==" }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", diff --git a/package.json b/package.json index b18c079..fb78f3e 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "author": "aewens", "license": "BSD-3-Clause", "dependencies": { + "axios": "^0.18.0", "irc": "^0.5.2", "nodemailer": "^4.6.7", "pushbullet": "^2.2.0" diff --git a/spells/banish.js b/spells/banish.js index b027031..eb437f5 100644 --- a/spells/banish.js +++ b/spells/banish.js @@ -1,56 +1,64 @@ const fs = require("fs"); +const path = require("path"); // Used to allow the author to manually banish someone module.exports = class Banish { - constructor(client, from, incantation) { + constructor(client, from, to, incantation) { this.name = "Banish"; this.client = client; this.summoner = from; + this.channel = to; this.incantation = incantation; this.spell = "malpermesi"; this.cheatCode = "banish"; - this.casted = false; this.varsUsed = 2; + this.casted = false; this.locked = true; } test() { if (this.incantation === this.spell) { this.casted = true; - return this.cast(); + return this.cast.apply(this, arguments); } return { say: false }; } - cast(user, reason) { + cast(user, ...reason) { let response = { say: false, debug: {}, content: "" }; - const subject = "You have been summoned!"; - const text = `Summoning request performed by ${this.summoner}`; + reason = reason.join(" "); - this.transporter.sendMail({ - to: this.client.email, - subject: subject, - text: text - }, (err, info) => { - console.log("Summon::email", info); - response.debug.email = info; + // Log reason for adding to the blacklist + const blacklistReason = `${user} was added for ${reason}\n`; + const dirname = this.client.config.dirname; + const log = path.resolve(dirname, "blacklist.log"); + fs.appendFile(log, blacklistReason, (err) => { + if (err) throw err; + console.log("Blacklist", blacklistReason); }); - this.pusher.note(this.client.device, subject, text, (err, info) => { - console.log("Summon::pusher", info); - response.debug.pusher = info; + // Apply to current session + this.client.blacklist[user] = { + reason: `Your actions triggered auto-banishment`, + when: Date.now() + }; + + // Apply to future sessions + const blacklist = JSON.stringify(this.client.blacklist); + const file = path.resolve(dirname, "blacklist.json"); + fs.writeFile(file, blacklist, (err) => { + if (err) throw err; + console.log("Added to blacklist:", user); }); - const success = `Superba ${this.summoner}! You have summoned`; - - response.say = true; - response.content = `${success} ${this.client.config.author}!`; + const author = this.client.config.author; + this.client.say(author, `${user} was banished for reason ${reason}`); return response; } diff --git a/spells/botlist.js b/spells/botlist.js index 2dfc2c2..c10f809 100644 --- a/spells/botlist.js +++ b/spells/botlist.js @@ -19,9 +19,6 @@ module.exports = class BotList { return this.cast(); } - console.log(`[${this.incantation}][${this.spell}]`); - - return { say: false }; } diff --git a/spells/hmm.js b/spells/hmm.js new file mode 100644 index 0000000..3b1021a --- /dev/null +++ b/spells/hmm.js @@ -0,0 +1,41 @@ +// Because freeappsw +module.exports = class Hmm { + constructor(client, from, to, incantation) { + this.name = "Hmm"; + this.client = client; + this.summoner = from; + this.channel = to; + this.incantation = incantation; + this.spell = "hmm poentaro"; + this.cheatCode = "hmmscore"; + this.varsUsed = 1; + this.casted = false; + this.locked = false; + } + + test() { + if (this.incantation === this.spell) { + this.casted = true; + return this.cast.apply(this, arguments); + } + + return { say: false }; + } + + cast(user) { + let response = { + say: false, + debug: {}, + content: "" + }; + + const memories = this.client.brain.memories; + memories.hmmscores = memories.hmmscores || {}; + const hmmscore = memories.hmmscores[user] || 0; + + response.say = true; + response.content = `Hmm score for "${user}": ${hmmscore}`; + + return response; + } +} \ No newline at end of file diff --git a/spells/pardon.js b/spells/pardon.js new file mode 100644 index 0000000..6150d29 --- /dev/null +++ b/spells/pardon.js @@ -0,0 +1,60 @@ +const fs = require("fs"); +const path = require("path"); + +// Used to allow the author to manually banish someone +module.exports = class Pardon { + constructor(client, from, to, incantation) { + this.name = "Pardon"; + this.client = client; + this.summoner = from; + this.channel = to; + this.incantation = incantation; + this.spell = "pardonu"; + this.cheatCode = "pardon"; + this.varsUsed = 1; + this.casted = false; + this.locked = true; + } + + test() { + if (this.incantation === this.spell) { + this.casted = true; + return this.cast.apply(this, arguments); + } + + return { say: false }; + } + + cast(user) { + let response = { + say: false, + debug: {}, + content: "" + }; + + // Log pardon from blacklist + const pardonReason = `${user} was pardoned\n`; + const dirname = this.client.config.dirname; + const log = path.resolve(dirname, "blacklist.log"); + fs.appendFile(log, pardonReason, (err) => { + if (err) throw err; + console.log("Blacklist", pardonReason); + }); + + // Apply to current session + delete this.client.blacklist[user]; + + // Apply to future sessions + const blacklist = JSON.stringify(this.client.blacklist); + const file = path.resolve(dirname, "blacklist.json"); + fs.writeFile(file, blacklist, (err) => { + if (err) throw err; + console.log("Removed from blacklist:", user); + }); + + const author = this.client.config.author; + this.client.say(author, `${user} was pardoned`); + + return response; + } +} \ No newline at end of file diff --git a/spells/summon.js b/spells/summon.js index 3110941..33047b8 100644 --- a/spells/summon.js +++ b/spells/summon.js @@ -88,7 +88,7 @@ module.exports = class Summon { const success = `Superba ${this.summoner}! You have summoned`; response.say = true; - response.content = `${success} ${this.client.config.author}!`; + response.content = `${success} ${user}!`; return response; }