<commit_before>module.exports = {
    init: init,
    destroy: destroy
};

const n = 2;

var mappings = {};
var keyCount = 0;
var lineCount = 0;
var characterCount = 0;

var annoyingModeRate = 0;

var lastTimeout = null;

function init(action, utils, config) {
    var options = {
        name: 'markov',
        help: 'Usage: !markov [stats | initial inputs]. With no arguments, it generates a random message.',
    };

    action(options, markov);

    action({ name: 'markovset', op_only: true }, function(bot, from, to, text) {
        if(!text) {
            bot.sayDirect(from, to, 'Give me a rate from 0 to 100');
            return;
        }

        annoyingModeRate = parseInt(text);
        bot.sayDirect(from, to, 'Annoying mode set with a rate of ' + annoyingModeRate + '%');
    });

    action({ name: '_msg' }, function(bot, from, to, text) {
        if(to !== bot.nick && text[0] !== '-') {
            createMapping(text);

            if(text.indexOf(bot.nick) != -1 || (100 * Math.random()) < annoyingModeRate) {
                let split = text.split(' ');
                let idx = Math.floor(Math.random() * (split.length - n + 1));
                let input = split.slice(idx, idx + n);
                var message = generateMarkov(input);
                bot.sayDirect(from, to, false, message.join(' '));
            }
        }
    });

    action({ name: '_action' }, function(bot, from, to, text) {
        if(to !== bot.nick && !text.startsWith('slaps')) {
            createMapping(from + ' ' + text);

            if(text.indexOf(bot.nick) != -1 || (100 * Math.random()) < annoyingModeRate) {
                let split = text.split(' ');
                let idx = Math.floor(Math.random() * (split.length - n + 1));
                let input = split.slice(idx, idx + n);
                var message = generateMarkov(input);
                bot.sayDirect(from, to, false, message.join(' '));
            }
        }
    });

    const fs = require('fs');

    fs.readdirSync('logs/').forEach((file) => {
        if(!file.endsWith('.log')) return;

        var contents = fs.readFileSync('logs/' + file).toString().split('\n');

        contents.forEach((line) => {
            var msg_regex = /^(\[.+?\])  ([<-].+?[>-] |\* )?(.+)$/;
            var match = msg_regex.exec(line);
            if(!match || !match[2]) {
                return;
            }

            createMapping(match[3]);
        });
    });

    keyCount = Object.keys(mappings).length;

    function sayRandomly() {
        var bot = utils.get_bot();
        markov(bot, '', bot.channel, '');

        var milliseconds = Math.floor(Math.random() * 3 * 60 * 60 * 1000) + 30 * 60 * 1000;
        console.log('markov: waiting for ' + milliseconds + ' ms');

        lastTimeout = setTimeout(sayRandomly, milliseconds);
    }

    lastTimeout = setTimeout(sayRandomly, 10000);
}

function destroy() {
    mappings = {};
    clearTimeout(lastTimeout);
}

function cleanString(str) {
    return str.trim().toLowerCase().replace(/[^a-zA-Z0-9,\.]/g, '');
}

function addMapping(key, value) {
    if(mappings[key]) {
        mappings[key].push(value);
    } else {
        mappings[key] = [value];
    }
}

function createMapping(text) {
    lineCount++;
    characterCount += text.length;

    var firstRun = true;
    var lastPieces = [];
    text.split(' ').forEach((piece) => {
        piece = cleanString(piece);

        if(!piece)
            return;

        if(lastPieces.length == n) {
            var key = JSON.stringify(lastPieces);
            var value = piece;

            if(firstRun) {
                addMapping(null, key);
                firstRun = false;
            }

            addMapping(key, value);

            lastPieces.shift();
        }

        lastPieces.push(piece);
    });

    addMapping(JSON.stringify(lastPieces), null);
}

function capitalize(prev, str) {
    if(str && (prev == null || prev.charAt(prev.length - 1) == '.')) {
        return str.substring(0,1).toUpperCase() + str.substring(1);
    } else {
        return str;
    }
}

function generateMessage(min_length, initialInputs) {
    if(initialInputs) {
        var keyArray = initialInputs.map(cleanString);
        var keyString = JSON.stringify(keyArray);
    } else {
        var initialIdx = Math.floor(Math.random() * mappings[null].length);
        var keyString = mappings[null][initialIdx];
        var keyArray = JSON.parse(keyString);
    }

    var prev = null;
    var message = keyArray.map((piece) => {
        var s = capitalize(prev, piece);
        prev = piece;
        return s;
    });

    do {
        if(!mappings[keyString])
            break;

        let tries = 5;
        do {
            let nextIdx = Math.floor(Math.random() * mappings[keyString].length);
            var piece = mappings[keyString][nextIdx];
        } while(piece == null && --tries > 0 && message.length < min_length);

        if(!piece) {
            let idx = mappings[keyString].findIndex((str) => !!str);
            if(idx != -1) {
                piece = mappings[keyString][idx];
            }
        }

        if(!piece) {
            break;
        }

        message.push(capitalize(prev, piece));

        keyArray.shift();
        keyArray.push(piece);

        prev = piece;
        keyString = JSON.stringify(keyArray);
    } while(piece != null);

    return message;
}

function generateMarkov(initialInputs) {<commit_msg>Removed desperate search for next piece if null is chosen, it lead to long messages and is susceptible to infinite loops. Also added delay to response messages.<commit_after>module.exports = {
    init: init,
    destroy: destroy
};

const n = 2;

var mappings = {};
var keyCount = 0;
var lineCount = 0;
var characterCount = 0;

var annoyingModeRate = 0;

var lastTimeout = null;

function init(action, utils, config) {
    var options = {
        name: 'markov',
        help: 'Usage: !markov [stats | initial inputs]. With no arguments, it generates a random message.',
    };

    action(options, markov);

    action({ name: 'markovset', op_only: true }, function(bot, from, to, text) {
        if(!text) {
            bot.sayDirect(from, to, 'Give me a rate from 0 to 100');
            return;
        }

        annoyingModeRate = parseInt(text);
        bot.sayDirect(from, to, 'Annoying mode set with a rate of ' + annoyingModeRate + '%');
    });

    function onMessage(bot, from, to, text) {
        createMapping(text);

        if(text.indexOf(bot.nick) != -1 || (100 * Math.random()) < annoyingModeRate) {
            let split = text.split(' ');
            let idx = Math.floor(Math.random() * (split.length - n + 1));
            let input = split.slice(idx, idx + n);
            var message = generateMarkov(input);
            setTimeout(() => bot.sayDirect(from, to, false, message.join(' ')), 700);
        }
    }

    action({ name: '_msg' }, function(bot, from, to, text) {
        if(to !== bot.nick && text[0] !== '-') {
            onMessage(bot, from, to, text);
        }
    });

    action({ name: '_action' }, function(bot, from, to, text) {
        if(to !== bot.nick && !text.startsWith('slaps')) {
            onMessage(bot, from, to, from + ' ' + text);
        }
    });

    const fs = require('fs');

    fs.readdirSync('logs/').forEach((file) => {
        if(!file.endsWith('.log')) return;

        var contents = fs.readFileSync('logs/' + file).toString().split('\n');

        contents.forEach((line) => {
            var msg_regex = /^(\[.+?\])  ([<-].+?[>-] |\* )?(.+)$/;
            var match = msg_regex.exec(line);
            if(!match || !match[2]) {
                return;
            }

            createMapping(match[3]);
        });
    });

    keyCount = Object.keys(mappings).length;

    function sayRandomly() {
        var bot = utils.get_bot();
        markov(bot, '', bot.channel, '');

        var milliseconds = Math.floor(Math.random() * 3 * 60 * 60 * 1000) + 30 * 60 * 1000;
        console.log('markov: waiting for ' + milliseconds + ' ms');

        lastTimeout = setTimeout(sayRandomly, milliseconds);
    }

    lastTimeout = setTimeout(sayRandomly, 10000);
}

function destroy() {
    mappings = {};
    clearTimeout(lastTimeout);
}

function cleanString(str) {
    return str.trim().toLowerCase().replace(/[^a-zA-Z0-9,\.]/g, '');
}

function addMapping(key, value) {
    if(mappings[key]) {
        mappings[key].push(value);
    } else {
        mappings[key] = [value];
    }
}

function createMapping(text) {
    lineCount++;
    characterCount += text.length;

    var firstRun = true;
    var lastPieces = [];
    text.split(' ').forEach((piece) => {
        piece = cleanString(piece);

        if(!piece)
            return;

        if(lastPieces.length == n) {
            var key = JSON.stringify(lastPieces);
            var value = piece;

            if(firstRun) {
                addMapping(null, key);
                firstRun = false;
            }

            addMapping(key, value);

            lastPieces.shift();
        }

        lastPieces.push(piece);
    });

    addMapping(JSON.stringify(lastPieces), null);
}

function capitalize(prev, str) {
    if(str && (prev == null || prev.charAt(prev.length - 1) == '.')) {
        return str.substring(0,1).toUpperCase() + str.substring(1);
    } else {
        return str;
    }
}

function generateMessage(min_length, initialInputs) {
    if(initialInputs) {
        var keyArray = initialInputs.map(cleanString);
        var keyString = JSON.stringify(keyArray);
    } else {
        var initialIdx = Math.floor(Math.random() * mappings[null].length);
        var keyString = mappings[null][initialIdx];
        var keyArray = JSON.parse(keyString);
    }

    var prev = null;
    var message = keyArray.map((piece) => {
        var s = capitalize(prev, piece);
        prev = piece;
        return s;
    });

    do {
        if(!mappings[keyString])
            break;

        let tries = 5;
        do {
            let nextIdx = Math.floor(Math.random() * mappings[keyString].length);
            var piece = mappings[keyString][nextIdx];
        } while(piece == null && --tries > 0 && message.length < min_length);

        if(!piece) {
            break;
        }

        message.push(capitalize(prev, piece));

        keyArray.shift();
        keyArray.push(piece);

        prev = piece;
        keyString = JSON.stringify(keyArray);
    } while(piece != null);

    return message;
}

function generateMarkov(initialInputs) {
{'fraction_non_alphanumeric': 0.15576649264356907, 'fraction_numerical': 0.009776934029425723, 'mean_word_length': 2.0645724258289704, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1827008', 'n_tokens_mistral': 3325, 'n_tokens_neox': 3154, 'n_words': 709}