321 lines
9.5 KiB
JavaScript
321 lines
9.5 KiB
JavaScript
const fs = require('fs');
|
|
const tmi = require('tmi.js');
|
|
|
|
// Twitch bot options
|
|
const optsFile = fs.readFileSync('twitch.config');
|
|
const opts = JSON.parse(optsFile);
|
|
|
|
const client = tmi.client(opts);
|
|
|
|
// Buttsbot options
|
|
const syllableRegex = /[^aeiouy]*[aeiouy]+(?:[^aeiouy]*$|[^aeiouy](?=[^aeiouy]))?/gi;
|
|
|
|
let configFile = fs.readFileSync('buttsbot.config')
|
|
let configurations = JSON.parse(configFile);
|
|
|
|
let defaultConfig = {
|
|
word: "butt",
|
|
chance: 20,
|
|
pity: 0,
|
|
cooldown: 0,
|
|
limit: 10,
|
|
syllableCount: 2,
|
|
ignoredUsers: []
|
|
};
|
|
|
|
let messageCounter = JSON.parse(fs.readFileSync('counter'));
|
|
let pityTracker = {};
|
|
let cooldownTracker = {};
|
|
|
|
// Client
|
|
client.on('message', onMessageHandler);
|
|
client.on('connected', onConnectedHandler);
|
|
|
|
client.connect();
|
|
|
|
// Helper methods
|
|
function syllabify(words) {
|
|
return words.match(syllableRegex);
|
|
}
|
|
|
|
function randomIntFromInterval(min, max) { // min and max included
|
|
return Math.floor(Math.random() * (max - min + 1) + min)
|
|
}
|
|
|
|
function isNumeric(n) {
|
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
}
|
|
|
|
// Event handlers
|
|
function onMessageHandler (channel, userstate, message, self) {
|
|
var message = message.trim();
|
|
var messageChannel = "#" + userstate['username'];
|
|
|
|
if (channel === "#butt5b0t") {
|
|
if (message === "!subscribe") {
|
|
client.join(messageChannel);
|
|
client.say(channel, "Joined channel " + messageChannel);
|
|
|
|
if (!opts.channels.includes(messageChannel)) {
|
|
opts.channels.push(messageChannel);
|
|
fs.writeFileSync('twitch.config', JSON.stringify(opts));
|
|
}
|
|
|
|
if (!(messageChannel in configurations)) {
|
|
configurations[messageChannel] = defaultConfig;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
}
|
|
}
|
|
|
|
if (message === "!unsubscribe") {
|
|
client.part(messageChannel);
|
|
client.say(channel, "Left channel " + messageChannel);
|
|
|
|
if (opts.channels.includes(messageChannel)) {
|
|
var indexChannel = opts.channels.indexOf(messageChannel);
|
|
opts.channels.splice(indexChannel, 1);
|
|
fs.writeFileSync('twitch.config', JSON.stringify(opts));
|
|
}
|
|
|
|
if (messageChannel in configurations) {
|
|
delete configurations[messageChannel];
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
}
|
|
}
|
|
} else {
|
|
if (self) return;
|
|
|
|
let config = (channel in configurations) ? configurations[channel] : defaultConfig;
|
|
|
|
if (message.startsWith("!buttsbot")) {
|
|
if (userstate['room-id'] === userstate['user-id'] || userstate['mod']) {
|
|
|
|
var parts = message.split(' ');
|
|
var action = parts[1];
|
|
var value = parts[2];
|
|
|
|
switch (action) {
|
|
case "config":
|
|
client.say(channel, "Chance: " + config.chance + "% - Cooldown: " + config.cooldown + " seconds - Pity: " + config.pity + " - Word: " + config.word + " - Min. Syllables: " + config.syllableCount + " - Ignored Users: " + config.ignoredUsers.join(', '));
|
|
break;
|
|
case "word":
|
|
config.word = value;
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Word set to " + value);
|
|
break;
|
|
case "chance":
|
|
if (isNumeric(value)) {
|
|
if (value > 100)
|
|
value = 100;
|
|
if (value < 0)
|
|
value = 0;
|
|
config.chance = parseInt(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Chance set to " + value + "%");
|
|
|
|
messageCounter[channel] = { total: 0, converted: 0 };
|
|
fs.writeFileSync('counter', JSON.stringify(messageCounter));
|
|
} else {
|
|
client.say(channel, "Expected a number, got " + value);
|
|
}
|
|
break;
|
|
case "pity":
|
|
if (isNumeric(value)) {
|
|
if (value < 0)
|
|
value = 0;
|
|
config.pity = parseInt(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Pity set to " + value);
|
|
} else {
|
|
client.say(channel, "Expected a number, got " + value);
|
|
}
|
|
break;
|
|
case "ignore":
|
|
if (value && value !== "") {
|
|
console.log(`* ignore: value ok`);
|
|
value = value.replace("@", "");
|
|
if (!config.ignoredUsers.includes(value)) {
|
|
console.log(`* ignore: value not already ignored`);
|
|
config.ignoredUsers.push(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
}
|
|
client.say(channel, "User " + value + " will be ignored from now on.");
|
|
} else {
|
|
client.say(channel, "Excepted username, got empty value");
|
|
}
|
|
break;
|
|
case "rmignore":
|
|
if (value && value !== "") {
|
|
console.log(`* rmignore: value ok`);
|
|
value = value.replace("@", "");
|
|
if (config.ignoredUsers.includes(value)) {
|
|
console.log(`* rmignore: value ignored`);
|
|
|
|
let index = config.ignoredUsers.indexOf(value);
|
|
config.ignoredUsers.splice(index, 1);
|
|
configurations[channel] = config;
|
|
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
}
|
|
client.say(channel, "User " + value + " will no longer be ignored.");
|
|
} else {
|
|
client.say(channel, "Expected username, got empty value");
|
|
}
|
|
break;
|
|
case "limit":
|
|
if (isNumeric(value)) {
|
|
config.limit = parseInt(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Set new limit: 1 butt per " + value + " words");
|
|
} else {
|
|
client.say(channel, "Expected a number, got " + value);
|
|
}
|
|
break;
|
|
case "syllables":
|
|
if (isNumeric(value)) {
|
|
config.syllableCount = parseInt(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Set required syllables to " + value);
|
|
} else {
|
|
client.say(channel, "Expected a number, got " + value);
|
|
}
|
|
break;
|
|
case "cooldown":
|
|
if (isNumeric(value)) {
|
|
config.cooldown = parseInt(value);
|
|
configurations[channel] = config;
|
|
fs.writeFileSync('buttsbot.config', JSON.stringify(configurations));
|
|
client.say(channel, "Set cooldown to " + value + " seconds");
|
|
} else {
|
|
client.say(channel, "Expected a number, got " + value);
|
|
}
|
|
default:
|
|
client.say(channel, "Action '" + action + "' not recognized.");
|
|
}
|
|
} else {
|
|
console.log(`* buttsbot: User not authorized`);
|
|
}
|
|
} else {
|
|
let currentTime = Math.floor(new Date().getTime()/1000.0);
|
|
|
|
if (cooldownTracker[channel] && config.cooldown > 0) {
|
|
if (currentTime - cooldownTracker[channel] < config.cooldown)
|
|
return;
|
|
}
|
|
|
|
// ignore commands
|
|
if (message.startsWith("!"))
|
|
return;
|
|
|
|
// ignore ignored users
|
|
if (config.ignoredUsers.includes(userstate['display-name']) || config.ignoredUsers.includes(userstate['username'])) {
|
|
console.log(`* User ${userstate['username']} is on the ignore list.`);
|
|
return;
|
|
}
|
|
|
|
// ignore messages containing URLs
|
|
var regex = /((http(s)?(\:\/\/))*(www\.)?([\w\-\.\/])*(\.[a-zA-Z]{2,3}\/?))[^\s\b\n|]*/;
|
|
var matches = message.match(regex);
|
|
if (matches && matches.length > 0) {
|
|
console.log(`* Message contained URL - skip`);
|
|
return;
|
|
}
|
|
|
|
// split messages into word array and try to determine syllabes
|
|
var words = message.split(' ');
|
|
var syllables = words.map(syllabify);
|
|
|
|
// calculate syllabe count
|
|
var syllableCount = 0;
|
|
syllables.forEach((s) => {
|
|
if (s !== null)
|
|
syllableCount += s.length;
|
|
});
|
|
|
|
// ignore message if it doesn't contain enough syllabes
|
|
if (syllableCount < config.syllableCount) {
|
|
console.log(`* Message had too few syllables`);
|
|
return;
|
|
}
|
|
|
|
var counter = {
|
|
total: 0,
|
|
converted: 0
|
|
};
|
|
|
|
if (channel in messageCounter) {
|
|
console.log(`* Load counter for ${channel}`);
|
|
counter = messageCounter[channel];
|
|
}
|
|
|
|
counter.total++;
|
|
|
|
if (pityTracker[channel])
|
|
pityTracker[channel]++;
|
|
else
|
|
pityTracker[channel] = 1;
|
|
|
|
// random chance
|
|
// TODO: find a better alternative
|
|
var number = Math.random() * 100;
|
|
if (number <= config.chance || (config.pity != 0 && pityTracker[channel] && pityTracker[channel] >= config.pity)) {
|
|
|
|
pityTracker[channel] = 0;
|
|
|
|
counter.converted++;
|
|
|
|
var buttCount = Math.ceil(words.length / config.limit);
|
|
|
|
var randomNumbersUsed = [];
|
|
|
|
for (var i = 0; i < buttCount; i++) {
|
|
|
|
var random = randomIntFromInterval(1, words.length);
|
|
|
|
while (randomNumbersUsed.includes(random)) random = randomIntFromInterval(1, words.length);
|
|
|
|
randomNumbersUsed.push(random);
|
|
|
|
var word = syllables[random - 1];
|
|
|
|
if (word) {
|
|
var randomSyllable = randomIntFromInterval(1, word.length);
|
|
|
|
var firstLetterOfSyllable = word[randomSyllable - 1][0];
|
|
|
|
word[randomSyllable - 1] = firstLetterOfSyllable == firstLetterOfSyllable.toUpperCase() ? config.word.toUpperCase() : config.word;
|
|
}
|
|
}
|
|
|
|
var newMessage = "";
|
|
|
|
syllables.forEach((s) => {
|
|
if (s != null)
|
|
newMessage += s.join('') + ' ';
|
|
});
|
|
|
|
client.say(channel, newMessage.trim());
|
|
if (config.cooldown > 0) cooldownTracker[channel] = currentTime;
|
|
}
|
|
|
|
console.log(channel + ": " + JSON.stringify(counter));
|
|
|
|
messageCounter[channel] = counter;
|
|
|
|
fs.writeFileSync('counter', JSON.stringify(messageCounter));
|
|
|
|
console.log(JSON.stringify(pityTracker));
|
|
}
|
|
}
|
|
}
|
|
|
|
function onConnectedHandler (addr, port) {
|
|
console.log(`* Connected to ${addr}:${port}`);
|
|
}
|