/* fore.st is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. fore.st 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with fore.st. If not, see < http://www.gnu.org/licenses/ >. (C) 2022- by rainbownapkin, */ import fs from 'fs'; var ChannelModule = require("./module"); //global vars var tokes = loadTokes(); var solotokes = ["", "https://ourfore.st/img/femotes/onetoker.jpg","https://ourfore.st/img/femotes/solotoke.jpg","https://ourfore.st/img/femotes/1toker.gif"]; //global functions function loadTokes(){//load tokes as array from file const rawContents = fs.readFileSync("tokebot/tokes").toString('utf8'); var spcReg = /^\s*$/g; var t = rawContents.split("\n").filter(function(i){ return !spcReg.test(i); }); return t; } //used to store ES6 maps as arrays in JSON function mapReplacer(key, value) { if(value instanceof Map) { return { dataType: 'Map', value: Array.from(value.entries()), // or with spread: value: [...value] }; } else { return value; } } function mapReviver(key, value) { if(typeof value === 'object' && value !== null) { if (value.dataType === 'Map') { return new Map(value.value); } } return value; } function randi(len) {//get random number from zero to len, meant for use to pull random items from an array return Math.floor(Math.random() * len); //The maximum is exclusive and the minimum is inclusive } //constructor function TokebotModule(_channel){ ChannelModule.apply(this, arguments); //mod command registration this.channel.modules.chat.registerCommand("!resettoke", this.resettoke.bind(this)); //admin command registration this.channel.modules.chat.registerCommand("!updatetokes", this.updatetokesCmd.bind(this)); this.channel.modules.chat.registerCommand("!reloadtokes", this.updatetokesCmd.bind(this)); this.channel.modules.chat.registerCommand("!tokesay", this.tokesayCmd.bind(this)); this.channel.modules.chat.registerCommand("!tokeannounce", this.tokeyellCmd.bind(this)); this.channel.modules.chat.registerCommand("!tokeyell", this.tokeyellCmd.bind(this)); this.channel.modules.chat.registerCommand("!tokewhisper", this.tokewhisperCmd.bind(this)); //!toke command registration this.updatetokes(); this.channel.modules.chat.registerCommand("!r", this.randotoke.bind(this)); this.loadtfile();//load up toke stats from toke file. } //protoype definition TokebotModule.prototype = Object.create(ChannelModule.prototype); //tokebot object properties TokebotModule.prototype.toking = 0; TokebotModule.prototype.tokers = []; TokebotModule.prototype.cdown = 3; TokebotModule.prototype.cdel = 120; TokebotModule.prototype.ctime = 120; TokebotModule.prototype.statmap = null; //mod commands TokebotModule.prototype.resettoke = function(user, msg, _meta){ if(user.account.effectiveRank >= 2 && this.toking == 2){ this.ctime = 0; this.tokewhisper("!toke cooldown reset.", user.account.name); } } //siteowner commands TokebotModule.prototype.updatetokesCmd = function(user, msg, _meta){ if(user.account.effectiveRank >= 256){ this.updatetokes(); this.tokewhisper("Reloading !toke commands...", user.account.name); } } TokebotModule.prototype.tokesayCmd = function(user, msg, _meta){ if(user.account.effectiveRank >= 256){ var fmsg = msg.split(" "); fmsg.shift(); this.tokesay(fmsg.join(' '), true); } } TokebotModule.prototype.tokeyellCmd = function(user, msg, _meta){ if(user.account.effectiveRank >= 256){ var fmsg = msg.split(" "); fmsg.shift(); this.tokesay(fmsg.join(' '), false); } } TokebotModule.prototype.tokewhisperCmd = function(user, msg, _meta){ if(user.account.effectiveRank >= 256){ var fmsg = msg.split(" "); fmsg.shift(); this.tokewhisper(fmsg.join(' ')); } } //extra user commands TokebotModule.prototype.randotoke = function(user, msg, _meta){ this.toke(user, '!' + tokes[randi(tokes.length)],_meta); } //main toke logic (adapted from chozobot implementation) TokebotModule.prototype.toke = function (user, msg, _meta){ var name = user.getName() /*if(name === "Ten"){//use in case of anger bot.sendChatMsg(">:^("); return; }*/ switch (this.toking){ case 0://ready to start toke this.tokesay("A group toke has been started by " + name + "! We'll be taking a toke in 60 seconds - join in by posting " + msg); this.cdown = 3; this.toking = 1; this.tokers.push(name); setTimeout(this.countdown, 57000, this); break; case 1://taking toke if(this.tokers.includes(name)){ this.tokewhisper(" You're already taking part in this toke!", name); }else{ this.tokesay(name + " joined the toke! Post " + msg + " to take part!"); this.tokers.push(name); this.cdown = 3; } break; case 2://cooldown this.tokewhisper(" Please wait " + this.ctime + " seconds before starting a new group toke.", name); break; } }; TokebotModule.prototype.countdown = function (tb){ tb.toking = 1;//set toking mode tb.tokesay(tb.cdown + "...");//send countdown msg --tb.cdown;//count down if(tb.cdown <= 0){//if cdown hits 0 setTimeout(tb.endtoke, 1000, tb); }else{ setTimeout(tb.countdown, 1000, tb);//call endtoke } }; TokebotModule.prototype.endtoke = function (tb){ if(tb.cdown != 0){ setTimeout(tb.countdown, 1000, tb); return; } if(tb.tokers.length > 1){ let callstring = ''; for(let i = 0; i < tb.tokers.length - 1; i++){ callstring += tb.tokers[i] + ', '; } callstring += tb.tokers[tb.tokers.length - 1]; tb.tokesay("Take a toke " + callstring + "! " + tb.tokers.length + " tokers!"); }else{ tb.tokesay("Take a toke " + tb.tokers.toString() + ". " + (solotokes[randi(solotokes.length)])); } tb.logtoke(); tb.tokers = []; tb.toking = 2;//reset toking mode setTimeout(tb.cooldown, 1000, tb); }; TokebotModule.prototype.cooldown = function (tb){ if(tb.ctime > 0){ tb.toking = 2; --tb.ctime; setTimeout(tb.cooldown, 1000, tb); }else{ tb.toking = 0; tb.ctime = tb.cdel; } }; //helper functions(mostly just syntactic sugar) TokebotModule.prototype.updatetokes = function (){ tokes = loadTokes(); if(this.channel.modules.chat){//register !toke commands if(tokes == null){//if for some reason tokes file couldnt be loaded this.channel.modules.chat.registerCommand("!toke", this.toke.bind(this)); console.log("[tokebot] Unable to load toke commands from ./tokebot/tokes, defaulting to !toke definition"); }else{//if we we're able to pull toke commands var _this = this;//we need to use this, might put this up higher to replace the tb parameter in other member functions tokes.forEach(function(tokec){ _this.channel.modules.chat.registerCommand("!" + tokec, _this.toke.bind(_this)); }); } } } TokebotModule.prototype.tokesay = function (msg,quiet){ var msgobj = { username: "tokebot", msg: msg, meta:{ addClass: (quiet ? null : "shout"), addClassToNameAndTimestamp: true, forceShowName: (quiet ? true : false), //It's loud enough when announcing. Toke chats are rare enouhg to be more prominent :P modflair: 3 }, time: Date.now() } this.channel.users.forEach(function (u) { u.socket.emit("chatMsg",msgobj); }); }; TokebotModule.prototype.tokewhisper = function (msg, usr){//(msg, username) if(this.channel.modules.chat != null){ if(usr != null){ this.channel.modules.chat.sendModMessage(msg,-1,"tokebot",usr); }else{ var _this = this this.channel.users.forEach(function(u){ _this.channel.modules.chat.sendModMessage(msg,-1,"tokebot",u.account.name); }); } } } //filesystem manipulation functions TokebotModule.prototype.writetokelog = function(){//append a toke to current channels toke log var _this = this; fs.appendFile("tokebot/" + this.channel.name + "_toke.log" ,('[' + this.tokers.toString() + '],' + this.tokers.length + ',' + new Date().getTime() + "\n"), function(err){ if(err){ console.log("[chan: " + _this.channel.name + "] TOKE LOG WRITE ERROR: " + err); } }); } TokebotModule.prototype.loadtfile = function(){//load tokefile into statmap(default to new Map() on error) var _this = this; fs.readFile("tokebot/" + this.channel.name + "_tokefile", function(err,data){ if(err){ console.log("[chan: " + _this.channel.name + "] TOKE FILE READ ERROR (may overwrite existing stat file!): " + err); _this.statmap = new Map(); }else{ _this.statmap = JSON.parse(data, mapReviver); } }); } TokebotModule.prototype.writetfile = function(){//write statmap to tokefile var _this = this; fs.writeFile("tokebot/" + this.channel.name + "_tokefile", JSON.stringify(_this.statmap, mapReplacer), function(err,data){ if(err){ console.log("[chan: " + _this.channel.name + "] TOKE FILE WRITE ERROR: " + err); } }); } TokebotModule.prototype.logtoke = function(){ this.writetokelog();//save toke to toke log var _this = this; this.tokers.forEach(function (u){//for all tokers var ct = _this.statmap.get(u);//lets make this a lil prettier :P _this.statmap.set(u,(ct == null ? 1 : ++ct));//increment toke counter }); this.writetfile();//write statmap to tfile } module.exports = TokebotModule;