Xorboo / ConsulFriendsMarker

// ==UserScript==
// @name        ConsulFriendsMarker
// @description Mark specific players in mutual space
// @require     https://code.jquery.com/jquery-1.12.4.js
// @version     0.1.1
// @updateURL   https://openuserjs.org/meta/Xorboo/ConsulFriendsMarker.meta.js
// @license     MIT
// @author      Xorboo
// @supportURL  https://consulwar.ru/game/statistics/general/1#Xorboo
// @match       *://consulwar.ru/*
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

/* RELEASE NOTES
  0.1.1
    + basic logic
*/
/* TODO
    Proper players adding/removing from statistics screen
*/


$(function () {
    log('============ Start');

    /* PARAMETERS */
    /*
    * Alliance list parameters
    * HOWTO:
    * Идете в GoogleDocs-таблицу "Составы Альянсов", File->Download as...-> .csv, current sheet
    * Жмете на кнопку выгрузки файла в конце верхнего меню, загружаете скачанный файл, игроки добавляются в список.
    */
   // Показывать ли кнопку загрузки файла. Можно поменять на false когда она уже не будет нужна
    const showCsvButtons = true;
    // Список альянсов, люди из которых НЕ будут добавлены в список игроков
    const ignoredAlliances = [ 'Эквестрийцы' ];
    // Номер столбца, с которого начинается список первого альянса (0 - первый столбец, 1 - второй, ...)
    const CsvStartIndex = 1;

    /* Other parameters */
    // Сюда можно добавить игроков вручную, которые должны быть в списке (в кавычках, через запятую)
    const additionalPlayers = [ '' ];
    // Сюда можно добавить игроков, которых НЕ будет в списке (в кавычках, через запятую)
    const ignoredPLayers = [ '' ];
    /* PARAMETERS END */


    const markColor = '#9dff85';        // Background color of friends marks
    const retryTimeout = 1000;          // Pause between retrying of marking players
    const usersSaveTag = 'ConsulFriendsMarker.friendly_users';
    let fileInput = null;
    let playersMarked = false;
    let loadedPlayers = [];


    Template.main_menu.onRendered(function () {
        appendUI();
        loadPlayers();
    });

    Template.cosmos.onRendered(function() {
        if (!playersMarked) {
            markPlayers();
            playersMarked = true;
        }
    });

    log('============ End');


    const appendUI = function() {
        if (showCsvButtons) {
            var topMenu = $('.main_menu');
            topMenu.append(`<button id="clearPlayersButton" type="button">Clear players</button><input type="file" id="fileInput" />`);
            $('#clearPlayersButton').click(clearUsers);
            fileInput = $('#fileInput');
            fileInput.on('change', loadUsersFromFile);
        }
    };

    const loadPlayers = function(needRewrite = true) {
        const savedData = GM_getValue(usersSaveTag, '[]');
        let users = JSON.parse(savedData);
        if (needRewrite) {
            rewriteUsers(users);
        }

        loadedPlayers = users;
        logObj(loadedPlayers, 'Loaded users');
    };

    const markPlayers = function() {
        let userTooltips = $('#map-battle .usernameTooltip');
        if (userTooltips.length > 0) {
            $.each(userTooltips, (index, value) => {
                let tooltip = $(value);
                let nickname = tooltip.html();
                let isMarked = loadedPlayers.some(n => n === nickname);
                tooltip.css('background-color', isMarked ? markColor : '');
            });
            log('Players marked');
        } else {
            logWarning('Failed to mark players, retrying in {0} ms'.format(retryTimeout));
            setTimeout(() => markPlayers(), retryTimeout);
        }
    };

    const reloadPlayers = function() {
        loadPlayers(false);
        markPlayers();
    };

    const clearUsers = function() {
        rewriteUsers([]);
        printSuccess('Users cleared');
        reloadPlayers();
    };

    const loadUsersFromFile = function() {
        printSuccess('Parsing users file...');

        const file = this.files[0];
        if (!file) {
            printError('No file provided');
            return;
        }

        var reader = new FileReader();
        reader.onload = function() {
            const data = reader.result;
            let lines = data.match(/[^\r\n]+/g);
            const firstLine = lines.splice(0, 1)[0];
            const ignoredIndexes = getIgnoredIndexes(firstLine);

            let nicknames = [];
            let userNickname = Meteor.user().nickname;

            for(let i in lines) {
                const cells = lines[i].split(',');
                for (let index = CsvStartIndex; index < cells.length; index += 2) {
                    if (ignoredIndexes.some(ignoredIndex => ignoredIndex === index)) {
                        continue;
                    }

                    const nickname = cells[index];
                    if (nickname && nickname !== userNickname) {
                        nicknames.push(nickname);
                    }
                }
            }

            rewriteUsers(nicknames);
            reloadPlayers();
            clearFileInput(fileInput);
        };
        reader.readAsText(file);
    };

    const getIgnoredIndexes = function(alliancesLine) {
        indexes = [];

        const cells = alliancesLine.split(',');
        for (let index = CsvStartIndex; index < cells.length; index += 2) {
            const allianceName = cells[index];
            if (allianceName && ignoredAlliances.some(name => name === allianceName)) {
                indexes.push(index);
            }
        }

        logObj(indexes, 'Ignored alliances columns indexes');
        return indexes;
    };

    const rewriteUsers = function(nicknames) {
        nicknames = arrayUnique(nicknames.concat(additionalPlayers));
        removeFromArray(nicknames, ignoredPLayers);

        GM_setValue(usersSaveTag, JSON.stringify(nicknames));
        printSuccess('Load success, nicknames amount: {0}'.format(nicknames.length));
        logObj(nicknames, 'Parsed nicknames');
    };


    // #region Helpers
    const arrayUnique = function(array) {
        var a = array.concat();
        for(var i = 0; i < a.length; i++) {
            for(var j = i + 1; j < a.length; j++) {
                if (a[i] === a[j])
                    a.splice(j--, 1);
            }
        }

        return a;
    };

    const removeFromArray = function(array, removeItems) {
        for (let i = 0; i < array.length; i++) {
            const item = array[i];
            if (removeItems.some(n => n === item)) {
                array.splice(i, 1);
                i--;
            }
        }
    };

    const clearFileInput = function(inputElement) {
        inputElement.wrap('<form>').closest('form').get(0).reset();
        inputElement.unwrap();
    };
    // #endregion

});


// #region Logging
const log = function(text) {
    console.log('ConsulFriendsMarker: ' + text);
};

const logWarning = function(text) {
    console.warn('ConsulFriendsMarker: ' + text);
};

const logError = function(text) {
    console.error('ConsulFriendsMarker: ' + text);
};

const logObj = function(obj, obj_name = '[Object]') {
    log('\'' + obj_name + '\' data:');
    console.dir(obj);
};

const printError = function(text) {
    logError('ERROR: ' + text);
    if (Notifications === undefined) {
        alert('Error: ' + text);
    } else {
        Notifications.error(text);
    }
};

const printSuccess = function(text) {
    log(text);
    if (Notifications === undefined) {
        alert('Success: ' + text);
    } else {
        Notifications.success(text);
    }
};

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;
        });
    };
}
// #endregion