HWM_Def_Notifier

Система уведомлений о защитах

// ==UserScript==
// @name        HWM_Def_Notifier
// @namespace   Рианти
// @description Система уведомлений о защитах
// @include     http://www.heroeswm.ru/*
// @version     1
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_xmlhttpRequest
// @grant       GM_openInTab
// @grant       GM_addStyle
// ==/UserScript==

// <b><center><font color="red">Вы уже в заявке, ожидайте начала битвы!</font></center></b>
try {
    var _notificationSound = 'http://hwm.mcdir.ru/sounds/and-a-happy-new-year-sms.mp3';

    var _updateInterval = 1000 * 60 * 1;
    var _defPage = 'http://www.heroeswm.ru/mapwars.php';
    var _defaultSettingRows = 2;
    var _defaultSettingRows2 = 4;
    var _defaultSettings = '{"dn_timeUntilDef0":"15","dn_playersEnrolled0":"0","dn_totalRows0":"0","dn_percLost0":"0","dn_uncloosed0":"0","dn_timeUntilDef1":"5","dn_playersEnrolled1":"0","dn_totalRows1":"0","dn_percLost1":"0","dn_uncloosed1":"0","dn_notify_interval":"600000","dn_rule_modifier":"false","dn_sound_alert":"true"}';
    var _defaultSettings2 = '{"dn_timeUntilDef0":"5","dn_playersEnrolled0":"6","dn_totalRows0":"0","dn_percLost0":"0","dn_uncloosed0":"0","dn_timeUntilDef1":"2","dn_playersEnrolled1":"0","dn_totalRows1":"0","dn_percLost1":"0","dn_uncloosed1":"1","dn_timeUntilDef2":"10","dn_playersEnrolled2":"12","dn_totalRows2":"7","dn_percLost2":"0","dn_uncloosed2":"0","dn_timeUntilDef3":"15","dn_playersEnrolled3":"0","dn_totalRows3":"0","dn_percLost3":"45","dn_uncloosed3":"0","dn_notify_interval":"600000","dn_rule_modifier":"true","dn_sound_alert":"true"}';
    var _tabId = Math.random().toString();
    var _curCharID = document.querySelector('param[name="FlashVars"]').value.split('|')[3]; // will throw error if run on wrong page, thats ok.
    var _gmVars = {
        settingRows: 'dn_settingRows',
        timeSinceLastNotify: 'dn_timeSinceLastNotify',
        timeSinceLastUpdate: 'dn_timeSinceLastUpdate',
        lastActiveTab: 'dn_lastActiveTab',
        notifyCommand: 'dn_notifyCommand',
        bkSign: 'dn_bkSign_' + _curCharID
    }

    var _bkSign = GM_getValue(_gmVars.bkSign, 'null');
    if (_bkSign == 'null') _bkSign = prompt('Вас приветствует мастер настройки скрипта уведомления о защитах!\n\nВведите номер и название своего клана. Сначала номер, через пробел название.\nБудьте внимательны, если введете неправильно, скрипт придется переустанавливать.\n\nПравильный ввод выглядит так:', '823 Ginger Tail');
    if (_bkSign) GM_setValue(_gmVars.bkSign, _bkSign);
    else {
        alert ('Настройка не завершена :(');
        throw {message: 'HWM_Def_notifier: setup failed'};
    }

    // Последующий сегмент кода отвечает за то, чтоб уведомления исходили только из последней активной вкладки, а не случайной.
    if (document.visibilityState == "visible") updateAsActiveTab();

    document.addEventListener("visibilitychange", function(){
        if (document.visibilityState == "visible") updateAsActiveTab();
    });
    
    checkForNotifyCommand();
    
} catch (e) { console.log (e) }

function updateAsActiveTab(){
    GM_setValue(_gmVars.lastActiveTab, _tabId);
}

function checkForNotifyCommand(){
    var temp;
    if ((temp = GM_getValue(_gmVars.notifyCommand, '0')) != '0'){
        if (GM_getValue(_gmVars.lastActiveTab, _tabId) == _tabId){
            GM_setValue(_gmVars.notifyCommand, '0');
            notify(temp);
        } else {
            setTimeout(function(){
                var temp;
                if ((temp = GM_getValue(_gmVars.notifyCommand, '0')) != '0'){
                    GM_setValue(_gmVars.notifyCommand, '0');
                    notify(temp);
                }
            }, 2000);
        }
    }

    setTimeout(checkForNotifyCommand, 1000);
}

function sendNotifyCommand(message){
    GM_setValue(_gmVars.notifyCommand, message);
}
// Конец сегмента. Отслылка уведомлений происходит только посредством вызова функции sendNotifyCommand.

function findActiveDefs(doc){
    var regexp = /<font color=(?:"red"|red)><b>([\d:]*)<\/b><\/font>(.*?)<br><\/td><\/tr>/g;
    var regexp2 = /\d\)/g;
    var page = doc.querySelector('body').innerHTML;
    var match, activeDefs = [];
    var time, playersEnrolled;
    var res, count, unclosed;
    while (match = regexp.exec(page)){
        if (match[2].indexOf(_bkSign) == -1) continue;
        time = match[1];
        playersEnrolled = match[2].split('pl_info').length - 1;
        activeDefs.push({time : time, playersEnrolled : playersEnrolled});
    }
    for(var i = 0; i < activeDefs.length; i++){
        count = 0;
        while(res = regexp2.exec(doc.querySelectorAll('td.wb:nth-child(3)')[i].innerHTML)) count++;
        activeDefs[i].totalRows = count;
        try {
            activeDefs[i].curPercLost = parseInt(document.querySelectorAll('td.wb:nth-child(2)')[i].innerHTML.match(/Захвачен врагом на (\d+)/)[1]);
        } catch (e) {
            activeDefs[i].curPercLost = 0;
        }
        unclosed = 0;
        if ( activeDefs[i].playersEnrolled % 3 > 0 || Math.ceil((activeDefs[i].totalRows * 3 - activeDefs[i].playersEnrolled) / 3) < document.querySelectorAll('td.wb:nth-child(2)')[i].innerHTML.split('[Вступить]').length - 1) unclosed = 1;
        activeDefs[i].unclosed = unclosed;
    }

    var temp = activeDefs.reduce(function(o, v, i) { o[i] = v; return o; }, {});

    return activeDefs;
}

function getCurServerTime(doc){
    var regexp = /([\d:]+), \d+ online&nbsp;/;
    var page = doc.querySelector('body').innerHTML;
    var time = page.match(regexp)[1];
    return time;
}

function minutesDifference(time1, time2){
    var t1 = time1.split(':'), t2 = time2.split(':');
    if(parseInt(t1[0]) == 0) t1[0] = '24'; if(parseInt(t2[0]) == 0) t2[0] = '24';
    var difMin = 60 * (parseInt(t2[0]) - parseInt(t1[0])) + parseInt(t2[1]) - parseInt(t1[1]);
    //console.log('minutesDifference', difMin);
    return difMin;
}

function requestPage (url, onloadHandler){
    console.log('loading: ', url);
    try{
        GM_xmlhttpRequest({
            overrideMimeType: 'text/plain; charset=windows-1251',
            synchronous: false,
            url: url,
            method: "GET",
            onload: function(response){
                onloadHandler(new DOMParser().parseFromString(response.responseText, 'text/html').documentElement);
            },
            onerror: function(){ requestPage (url, onloadHandler) },
            ontimeout: function(){ requestPage (url, onloadHandler) },
            timeout: 5000
        });
    } catch (e) {
        console.log(e);
    }
}

function notifyRules(timeUntil, playersEnrolled, totalRows, percLost, unclosed){
    //console.log('notifyRules', timeUntil, playersEnrolled, totalRows, percLost, unclosed);
    for (var i = 0; i < parseInt(GM_getValue(_gmVars.settingRows, _defaultSettingRows)); i++)
        if ( notifyCase(i, timeUntil, playersEnrolled, totalRows, percLost, unclosed) )
            return true;
    return false;
}

function notifyCase(id, timeUntil, playersEnrolled, totalRows, percLost, unclosed){
    var playersEnrolledReq = parseInt(_settings.getSetting('dn_playersEnrolled' + id));
    if (_settings.getSetting('dn_rule_modifier') == 'true') playersEnrolledReq = Math.max(0, playersEnrolledReq - 3 * Math.floor(percLost / 15));

    if(timeUntil > parseInt(_settings.getSetting('dn_timeUntilDef' + id))) return false;
    if(totalRows * 3 - playersEnrolled <= playersEnrolledReq) return false;
    if(parseInt(_settings.getSetting('dn_totalRows' + id)) != 0 && parseInt(_settings.getSetting('dn_totalRows' + id)) != totalRows) return false;
    if(parseInt(_settings.getSetting('dn_percLost' + id)) > percLost) return false;
    if(parseInt(_settings.getSetting('dn_uncloosed' + id)) == 1 && unclosed == 1) return false;
    return true;
}

function notify(message){
    //console.log(_settings.getSetting('dn_sound_alert'));
    if (_settings.getSetting('dn_sound_alert')) new Audio(_notificationSound).play();
    setTimeout(function(){
        alert(message);
        if(document.location.href.indexOf(_defPage) == -1) GM_openInTab(_defPage);
    }, 100);
}

function main(){
    if (parseInt(_settings.getSetting('dn_notify_interval')) == 0) return;
    if (parseInt(GM_getValue(_gmVars.timeSinceLastNotify, '0')) < new Date().getTime() - parseInt(_settings.getSetting('dn_notify_interval')) && parseInt(GM_getValue(_gmVars.timeSinceLastUpdate, '0')) < new Date().getTime() - _updateInterval){
        requestPage(_defPage, function(doc){
            try{
                //console.log(doc);
                var sTime = getCurServerTime(doc);
                //console.log('time:',sTime );
                var defs = findActiveDefs(doc);
                //console.log('defs:',defs );
                for (var i = 0; i < defs.length; i++){
                    if (notifyRules(minutesDifference(sTime, defs[i].time), defs[i].playersEnrolled, defs[i].totalRows, defs[i].curPercLost, defs[i].unclosed)){
                        var message = 'Уведомление: защита в ' + defs[i].time;
                        GM_setValue(_gmVars.timeSinceLastNotify, new Date().getTime());
                        sendNotifyCommand(message);
                        break;
                    }
                }
                GM_setValue(_gmVars.timeSinceLastUpdate, new Date().getTime());
            } catch (e) {
                console.log(e);
            }
        });
        GM_setValue(_gmVars.timeSinceLastUpdate, new Date().getTime());
    }
    setTimeout(main, Math.max(parseInt(GM_getValue(_gmVars.timeSinceLastUpdate, '0')) - new Date().getTime() + (1 + 0.1 * Math.random()) * _updateInterval, parseInt(GM_getValue(_gmVars.timeSinceLastNotify, '0')) - new Date().getTime() + (1 + 0.1 * Math.random()) * parseInt(_settings.getSetting('dn_notify_interval'))));
    //console.log('checking in: ', Math.max(parseInt(GM_getValue(_gmVars.timeSinceLastUpdate, '0')) - new Date().getTime() + (1 + 0.01 * Math.random()) * _updateInterval, parseInt(GM_getValue(_gmVars.timeSinceLastNotify, '0')) - new Date().getTime() + (1 + 0.01 * Math.random()) * parseInt(_settings.getSetting('dn_notify_interval'))));
}

function addInterface(){
    GM_addStyle('#settingsInner tr{position: relative; left: 6px;} .copyright td{text-align: right;font: 12px "Times New Roman",Times,serif;color: #000;}');
    if (navigator.userAgent.toLowerCase().indexOf('chrome') == -1) GM_addStyle('#settingsInner {border-collapse: collapse;}');
    else GM_addStyle('#settingsInner td {top: 21px!important; right: 0px!important;}');
    var table = document.querySelector('td[align="center"][width="50%"]').parentElement.parentElement;
    var tr = document.createElement('tr');
    tr.innerHTML = '<td></td><td align="middle"><span id="dn_settingBoxControl" style="cursor:pointer; color:blue;">Настройки уведомлений</span></td><td></td>';
    table.appendChild(tr);
    document.getElementById('dn_settingBoxControl').onclick = function(){
        var els = document.getElementsByClassName('dn_settingBox');
        for(var i = 0; i < els.length; i++){
            if(els[i].style.display == 'none') els[i].style.display = '';
            else els[i].style.display = 'none';
        }
        if(els[0].style.display != 'none'){ // Вкл/выкл таймер обновления страницы (взаимодействие со скриптом на странице)
            setTimeout("clearTimeout(Timer)", 0);
        } else {
            setTimeout("Timer=setTimeout('Refresh()', 1000);", 0);
        }
    }
    drawSettingsBox();
}

function drawSettingsBox(drawAgain){
    var table = document.querySelector('td[align="center"][width="50%"]').parentElement.parentElement;
    var t;
    if(t = document.getElementsByClassName('dn_settingBox')[0]) table.removeChild(t);

    var tr = document.createElement('tr');
    tr.className = 'dn_settingBox';
    if (!drawAgain) tr.style.display = 'none';
    var row = settingsRow();
    var inputRows = '<tr align="center"><td colspan="9"><i>Уведомлять если</i></td></tr>' + row;
    for (var i = 1; i < parseInt(GM_getValue(_gmVars.settingRows, _defaultSettingRows)); i++) inputRows += '<tr align="center"><td colspan="9"><i>или</i></td></tr>' + row;
    tr.innerHTML = '<td colspan="3"><form id="dn_settingsForm"><table id="settingsInner" align="center" style="border-width: 1px 1px 1px;border-style: solid solid solid;border-color: #000;width: 85%;">' + inputRows + '<tr style="padding-bottom: 7px;"><td colspan="6" align="left"><input type="button" id="dn_addRule" value="Добавить условие"> <input type="button" id="dn_reduceRule" value="Убрать условие"> <select class="dn_defaults" align="center"><option value="0">Готовые настройки</option><option value="1">Для активного защитника</option><option value="2">Для страхующего дефы</option></select></td><td colspan="3" align="right">Частота уведомлений: <select id="dn_notify_interval"></select></td></tr><tr><td colspan="7" align="left"><input type="checkbox" id="dn_rule_modifier" name="dn_rule_modifier"> <label for="dn_rule_modifier">Уменьшать требования к заполненности на 3 места за каждые 15% потерянного контроля</label></td><td colspan="2" align="right"><input type="checkbox" id="dn_sound_alert" name="dn_sound_alert" checked> <label for="dn_sound_alert">Звук уведомления</label></td></tr><tr class="copyright" style="border-top: 1px solid rgb(0, 0, 0); border-right: medium hidden; border-left: medium hidden; border-bottom: medium hidden;"><td style="position: relative; top: 1px; right: 6px; border-bottom: 0px none;" colspan="10" align="right">2015, © <a href="http://www.heroeswm.ru/pl_info.php?id=712329" target="_blank">Рианти</a></td></tr></table></form></td>';
    table.appendChild(tr);
    fetchValuesToSettings();
    document.getElementById('dn_addRule').onclick = addRow;
    document.getElementById('dn_reduceRule').onclick = reduceRow;
    document.querySelector('select[class="dn_defaults"]').onchange = setDefaults;
}

function settingsRow(){
    function Setting(header, inputType, inputClass){ this.header = header; this.inputType = inputType; this.inputClass = inputClass; };
    var settings = [
        new Setting('Времени до боя меньше', 'select', 'dn_timeUntilDef'),
        new Setting('Свободных мест больше', 'select', 'dn_playersEnrolled'),
        new Setting('Всего дорожек', 'select', 'dn_totalRows'),
        new Setting('Слито процентов', 'select', 'dn_percLost'),
        new Setting('Незакрытые заявки', 'select', 'dn_uncloosed')
    ]
    var output = '<tr>';
    for (var i = 0; i < settings.length; i++){
        output += '<td align="center">' + settings[i].header + '</td><td style="width: 2%;"> </td>';
    }
    output += '</tr><tr>';
    for (i = 0; i < settings.length; i++){
        if (settings[i].inputType != 'select') output += '<td align="center"><input type="' + settings[i].inputType + '" class="' +  settings[i].inputClass + '"></td>';
        else output += '<td align="center"><select class="' +  settings[i].inputClass + '"></td>';
        if (i != settings.length - 1) output += '<td style="width: 2%;"> </td>';
    }
    return output + '</tr><tr><td colspan="9"><hr></td></tr>';
}

function reduceRow(){
    var curRows = parseInt(GM_getValue(_gmVars.settingRows, _defaultSettingRows));
    if (curRows > 1) curRows--;
    GM_setValue(_gmVars.settingRows, curRows);
    drawSettingsBox(1);
    _settings.importAgain();
}

function addRow(){
    var curRows = parseInt(GM_getValue(_gmVars.settingRows, _defaultSettingRows));
    if (curRows < 10) curRows++;
    GM_setValue(_gmVars.settingRows, curRows);
    drawSettingsBox(1);
    _settings.importAgain();
}

function setDefaults(e){
    var selectedDefauls = e.target.value;
    var rows, settings;
    if (selectedDefauls == '0') return;
    else if (selectedDefauls == '1'){
        rows = _defaultSettingRows;
        settings = _defaultSettings;
    } else {
        rows = _defaultSettingRows2;
        settings = _defaultSettings2;
    }
    GM_setValue(_gmVars.settingRows, rows);
    GM_setValue('dn_settings', settings);
    drawSettingsBox(1);
    _settings = new ScriptSettings('dn_settings', 'dn_settingsForm');
    setTimeout( function (){ GM_setValue('dn_settings', settings) }, 300); // Затычка бага, найти баг в будущем.
}

function fetchValuesToSettings(){
    for(var i = 0; i < document.getElementsByClassName('dn_timeUntilDef').length; i++){
        var el = document.getElementsByClassName('dn_timeUntilDef')[i];
        el.id = el.className + i;
        for (var j = 1; j <= 15; j++){
            var newEl = document.createElement('option');
            newEl.value = j;
            newEl.innerHTML = j + ' мин.';
            el.appendChild(newEl);
        }
        var el = document.getElementsByClassName('dn_playersEnrolled')[i];
        el.id = el.className + i;
        for (var j = 0; j <= 20; j++){
            var newEl = document.createElement('option');
            newEl.value = j;
            newEl.innerHTML = j;
            el.appendChild(newEl);
        }
        var el = document.getElementsByClassName('dn_totalRows')[i];
        el.id = el.className + i;
        var newEl = document.createElement('option');
        newEl.value = 0;
        newEl.innerHTML = 'Неважно';
        el.appendChild(newEl);
        for (var j = 3; j <= 6; j++){
            if (j == 6) j++
            var newEl = document.createElement('option');
            newEl.value = j;
            newEl.innerHTML = j;
            el.appendChild(newEl);
        }
        var el = document.getElementsByClassName('dn_percLost')[i];
        el.id = el.className + i;
        for (var j = 0; j <= 50; j += 15){
            var newEl = document.createElement('option');
            newEl.value = j;
            if(j == 0) newEl.innerHTML = 'Неважно';
            else newEl.innerHTML = 'больше ' + j + '%';
            el.appendChild(newEl);
        }
        var el = document.getElementsByClassName('dn_uncloosed')[i];
        el.id = el.className + i;
        var newEl = document.createElement('option');
        newEl.value = 0; newEl.innerHTML = 'Неважно';
        el.appendChild(newEl);
        var newEl = document.createElement('option');
        newEl.value = 1; newEl.innerHTML = 'Да';
        el.appendChild(newEl);
    }
    var el = document.getElementById('dn_notify_interval');
    for (var j = 0; j <= 15; j++){
        var newEl = document.createElement('option');
        newEl.value = j * 1000 * 60;
        if(j == 0) newEl.innerHTML = 'Не уведомлять';
        else newEl.innerHTML = j + ' мин';
        el.appendChild(newEl);
    }
}

function ScriptSettings(settingsKey, formId){
    function save(){
        GM_setValue(_settingsKey, JSON.stringify(_settings));
    }

    function load(){
        var stringSettings = GM_getValue(_settingsKey);
        if (!stringSettings) importFromDocument();
        else _settings = JSON.parse(stringSettings);
    }

    function apply(){
        try{
            var element;
            for(var setting in _settings){
                element = document.getElementById(setting);
                if(element.type == 'checkbox') element.checked = _settings[setting];
                else if (element.type == 'radio') element.selected = _settings[setting];
                else element.value = _settings[setting];
            }
            document.getElementById(_formId).onchange = function(){_self.changed()};
        } catch (e) {
            try{
                document.getElementById(_formId).onchange = function(){_self.changed()};
            } catch (ee){
                // Page without settings
            }
        }
    }

    function importFromDocument(){
        try{
            var element, value, formElements = document.getElementById(_formId).elements;
            for(var i = 0; i < formElements.length; i++){
                element = formElements[i];
                if(element.id != null && element.id != '' && element.type != 'button') {
                    if (element.type == 'checkbox') value = element.checked;
                    else if (element.type == 'radio') value = element.selected;
                    else value = element.value;
                    _settings[element.id] = value;
                }
            }
        } catch ( e ) {
            // not set on page
            _settings = JSON.parse(_defaultSettings);
        }
        save();
    }

    function enableGMvars() {
        if (!this.GM_getValue || typeof this.GM_getValue != 'function') {
            if (typeof(Storage) === "undefined") console.log( 'Local storage must be enabled to use scriptSettings.' );
            else {
                this.GM_getValue = function (key, def) { return localStorage[key] || def; }
                this.GM_setValue = function (key, value) { return localStorage[key] = value; }
                this.GM_deleteValue = function (key) { return delete localStorage[key]; }
            }
        }
    }

    this.changed = function(){
        var element, value;
        for(var setting in _settings){
            element = document.getElementById(setting);
            if(element.type == 'checkbox') value = element.checked;
            else if (element.type == 'radio') value = element.selected;
            else value = element.value;
            _settings[setting] = this.validateValue(element.type, element.id, value);
            if(element.type == 'checkbox') element.checked = _settings[setting];
            else if (element.type == 'radio') element.selected = _settings[setting];
            else element.value = _settings[setting];
        }
        save(); this.onchange();
    }

    this.importAgain = function(){
        apply(); this.forget(); _settings = {}; load();
    }

    this.validateValue = function(elementType, elementId, elementValue){return elementValue};

    this.onchange = function(){};

    this.getSetting = function(setting){
        if(_settings[setting] != null) return _settings[setting];
        throw {message: 'undefined setting requested: ' + setting};
    }

    this.forget = function(){
        GM_deleteValue(_settingsKey);
    }

    var _self = this;
    var _settings = {};
    var _settingsKey = settingsKey;
    var _formId = formId;

    enableGMvars();
    load(); apply();
}

try{
    if (document.location.href.indexOf('mapwars.php') != -1){
        addInterface();
    }
    var _settings = new ScriptSettings('dn_settings', 'dn_settingsForm');
    main();
} catch (e) {
    console.log(e);
}