Greasy Fork is available in English.
Вывод версий приложений в Избранном 4pda, показ обновленных приложений
// ==UserScript== // @name Versions 4pda // @namespace http://4pda.to/forum/index.php // @version 1.6.1 // @description:ru Вывод версий приложений в Избранном 4pda, показ обновленных приложений // @author Azat-777 // @icon http://s.4pda.to/kkRM7z1nbI3gbG5E7r0a561qtdKnE2GlKhz1ipnv.png // @match http*://4pda.to/forum/index.php?act=fav* // @match http*://4pda.to/forum/index.php?showtopic=* // @grant GM_xmlhttpRequest // @license MIT // @history:ru 26.07.2017: допиливание мелочей // @history:ru 31.07.2017: расширение функционала скрипта: подробная информация о каждом пользователе в топиках // @history:ru 04.08.2017: добавлено мигание 'NEW' // @history:ru 02.01.2018: небольшие правки кода // @history:ru 10.05.2018: изменение списка обновленных приложений // @history:ru 11.05.2018: мелкие правки и исправления // @history:ru 20.05.2018: добавлено удаление пробелов в начале и конце названий версий, чтобы из-за пробелов версия не определялась как новая // @history:ru 16.08.2018: теперь обновления не исчезают с обновлением страницы, для ручного скрытия обновлений добавлена кнопка // @history:ru 17.08.2018: правка вчерашних ошибок, добавление мелочей (title и переход к последнему непрочитанному сообщению в теме // @history:ru 17.08.2018: обновленного приложения); реализация скрытия обновлений по одному: убрад мигание NEW для обновлений, т.к. уже неактуально // @history:ru 20.08.2018: починен показ кнопки скрытия отдельного обновления; в консоли выводится объем загруженого XHR-запросами траффика // @history:ru 06.05.2019: кроме слова 'версия' другой текст, если он был, не удалялся, поправлено // @history:ru 01.06.2019: слово 'версия' не заменялось на 'v.', если после него не было пробела. Недоработка в регулярках. Поправлено // @history:ru 02.06.2019: добавлена функция отключения проверки обновлений для выбранных приложений (могут быть ошибки) // @history:ru 14.02.2020: исправлена ошибка, при которой выводился текст на месте версии // @history:ru 05.07.2022: исправлена ошибка в коде, из-за которой таблица с обновленными версиями приложений появлялась снова после скрытия // @history:ru 09.06.2023: мелкие и незначительные правки для обеспечения полной работоспособности скрипта, наверное... // @description Вывод версий приложений в Избранном 4pda, показ обновленных приложений // ==/UserScript== (function() { 'use strict'; //============================================================ // получение ссылки текущей страницы var URL = window.document.URL; //log(URL); //============================================================ // удаляем рекламу и центрируем логотип 4pda var tbody = document.getElementsByTagName('tbody')[0], td = tbody.getElementsByTagName('td'); td[1].remove(); td[0].align = 'center'; //============================================================ var favURL = '4pda.to/forum/index.php?act=fav', i, head = document.getElementsByTagName('head')[0]; //var topicURL = 'http://4pda.to/forum/index.php?showtopic='; // спойлер с объявлениями всегда скрыт if(document.querySelector('#gc_1, #go_1')) { document.querySelector('#go_1').style.display = 'none'; document.querySelector('#gc_1').style.display = 'none'; } var l = 0, // счетчик totalKB = 0, totalMB = 0; function log(text) { return console.log(text); } // Избранное if (~URL.indexOf(favURL)) { //localStorage //localStorage.clear(); //============================================================ // модифицирование встроенной функции модерации тем в своем избранном var savedIDs, form = document.querySelector('#fav-sel-form'), select = form.querySelector('select'), option = document.createElement('option'), values = ''; option.value = 'not_show_updates'; option.innerHTML = 'Не уведомлять об обновлениях (mod)'; select.insertBefore(option, select.firstChild); // сохранение IDов выбранных тем в localStorage option.onclick = function() { checkSavedIDs(); if(savedIDs == '-1') { values = form.querySelector('input').value; } else { values = savedIDs + ',' + form.querySelector('input').value; } values = values.split(','); values = unique(values); localStorage.setItem('savedIDs', values); checkBan(); } // удаление из массива одинаковых IDов function unique(arr) { var obj = {}; for (var i = 0; i < arr.length; i++) { var str = arr[i]; obj[str] = true; // запомнить строку в виде свойства объекта } return Object.keys(obj); // или собрать ключи перебором для IE8- } checkSavedIDs(); function checkSavedIDs() { if(localStorage.getItem('savedIDs') === null) { localStorage.setItem('savedIDs', '-1'); } savedIDs = localStorage.getItem('savedIDs'); savedIDs = savedIDs.split(','); } // добавление в строке названий приложений их версий var ver; // находим таблицу var tbl = document.getElementsByClassName('ipbtable')[0]; var tbody2 = tbl.getElementsByTagName('tbody')[0]; var _tr = tbody2.getElementsByTagName('tr'); // запихиваем в tr нужные нам строки таблицы var tr = [], id; for(i=0; i<_tr.length; i++) { if (_tr[i].hasAttribute('data-item-fid')) { // отсортировываем из таблицы только темы tr.push(_tr[i]); // запихиваем в массив tr } } var trLength = tr.length var name = []; // названия тем for (i=0; i<trLength; i++) { var tmp = tr[i].getElementsByTagName('td')[1].getElementsByTagName('span')[0].getElementsByTagName('a')[0]; id = tr[i].getAttribute('data-item-fid'); getVersion(tmp.getAttribute('href'), i, id); //getVersion(tmp.href, i); name.push(tmp); } //===================================================== // добавление счетчика с количеством новых версий приложений var count = 0; var main_tbl = document.createElement('table'); main_tbl.id = 'main_tbl'; main_tbl.innerHTML = `<tbody><tr><td id="one" style="vertical-align: top;"></td> <td id="two" style="vertical-align: top;"></td></tr></tbody>`; var _span = document.createElement('span'); _span.style.color = 'black'; var _span2 = document.createElement('span'); _span2.style.color = 'black'; var navstrip = document.getElementById('navstrip'); //===================================================== var app_name, saveToHideName = [], saveToHideVer = []; /*_new = ' <mytag class="new" style="color: red"><b>NEW</b></mytag>',*/ function getVersion(link, i, id) { var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest; var xhr = new XHR(); xhr.open('GET', link, true); xhr.send(); xhr.onload = function() { if(this.readyState === 4) { if (this.status === 200) { var response = xhr.responseText; var parser = new DOMParser(); var doc = parser.parseFromString(response, 'text/html'); var tbl = doc.getElementsByClassName('ipbtable'); for (var j=0; j<tbl.length; j++) { if (tbl[j].hasAttribute('data-post')) { var tbody = tbl[j].getElementsByTagName('tbody')[0], tr2 = tbody.getElementsByTagName('tr')[1], td = tr2.getElementsByTagName('td')[1], div = td.getElementsByClassName('postcolor')[0], span = div.getElementsByTagName('span'); for (var k=0; k<span.length; k++) { // версии приложений if (span[k].getAttribute('style') == 'font-size:12pt;line-height:100%') { if (~span[k].innerHTML.toLowerCase().indexOf('верси')) { // замена var replace_ver = span[k].innerHTML, alt_ver, t; // если тема не была открыта if (~name[i].innerHTML.indexOf('<strong>')) { replace_ver = replace_ver.toLowerCase().replace(/[А-Яа-я\s]*верси[ия]:[\s]*/, 'v.').replace(/<[\/]*b[r]*>/g, '').trim(); if(~replace_ver.indexOf('v.')) { alt_ver = replace_ver; } else { alt_ver = '---'; } var alt_name; alt_name = name[i].innerHTML.replace(/<[\/]*strong>/g, ''); // если приложение в списке игнорируемых, т.е. не проходит проверку на обновления t = true; for(var l=0; l<savedIDs.length; l++) { if(id == savedIDs[l]) { t = false; getBannedApps(alt_name, alt_ver, id); //continue; } } // если приложение не в игноре + сравнение версий: текущей полученной и сохраненной в локальном хранилище if (t && alt_ver.localeCompare(localStorage.getItem(alt_name)) !== 0) { showNotif(alt_name, alt_ver); //console.log(name[i].innerHTML, alt_ver); } } // если тема была открыта и просмотрена else { replace_ver = replace_ver.toLowerCase().replace(/<b>[А-Яа-я\s]*верси[ия]:[\s]*/, 'v.').replace(/<[\/]*b>/g, '').trim(); if(~replace_ver.indexOf('v.')) { alt_ver = replace_ver; } else { alt_ver = '---'; } // если приложение в списке игнорируемых, т.е. не проходит проверку на обновления t = true; for(l=0; l<savedIDs.length; l++) { if(id == savedIDs[l]) { t = false; getBannedApps(name[i].innerHTML, alt_ver, id); //continue; } } // если приложение не в игноре + сравнение версий: текущей полученной и сохраненной в локальном хранилище if (t && alt_ver.localeCompare(localStorage.getItem(name[i].innerHTML)) !== 0 ) { showNotif(name[i].innerHTML, alt_ver); } } // вывод обновленных приложений вверху function showNotif(alt_name, alt_ver) { hideBtn.style.display = 'inline'; // показываем скрытую кнопку, если есть обновления //replace_ver += _new; // прибавляем тэг 'NEW' для новой версии count++; var goto = '<a href="'+link+'&view=getnewpost"><img src="//4pda.to/s/PXtiWhZDsz1g8WshSfmv6ItmpiBfFE4lDMF4ZupTkMv.gif" alt=">N" title="Перейти к первому непрочитанному" border="0"></a> ' app_name = goto + '<a href="'+link+'" title="Перейти к первому сообщению">'+alt_name + '</a>'; saveToHideName.push(alt_name); saveToHideVer.push(alt_ver); showUpdates(app_name, alt_ver); } // если replace_ver содержит готовый шаблон "v.xxx", добавляем его к названию приложения if(~replace_ver.indexOf('v.')) { replace_ver = '<font color="#8A2BE2"> ' + replace_ver + '</font>'; // добавление цвета для наглядности name[i].innerHTML += replace_ver; } } break; } } break; } } } } }; xhr.onerror = function() { log('onerror'); alert('Ошибка'); }; xhr.onloadend = function(event) { //log('onloadend'); totalKB += (event.loaded/####); // подсчет загруженного траффика totalMB += (event.loaded/####/####); if(++l === trLength) { addEvent(); // вешаем обработчик событий строки (появление/скрытие кноки "Скрыть") hideApp(); // скрытие строки с обновленным приложением console.log('Скачано XHR-запросами:', totalKB.toFixed(2), 'КБ |', totalMB.toFixed(2), 'МБ'); // вывод объема скачанного } }; xhr.onprogress = function(event) { //log('onprogress'); }; } // переопределяем стиль для кнопок var btnStyle = document.createElement('style'); btnStyle.type = 'text/css'; var _s = ` .myBtn { display: inline-block; font-family: arial,sans-serif; font-size: 10px; font-weight: bold; color: rgb(68,68,68); text-decoration: none; user-select: none; padding: .1em 1.2em; outline: none; border: 1px solid rgba(0,0,0,.1); border-radius: 2px; background: rgb(245,245,245) linear-gradient(#f4f4f4, #f1f1f1); transition: all .218s ease 0s; } .myBtn:hover { color: rgb(24,24,24); border: 1px solid rgb(198,198,198); background: #f7f7f7 linear-gradient(#f7f7f7, #f1f1f1); box-shadow: 0 1px 2px rgba(0,0,0,.1); } .myBtn:active { color: rgb(51,51,51); border: 1px solid rgb(204,204,204); background: rgb(238,238,238) linear-gradient(rgb(238,238,238), rgb(224,224,224)); box-shadow: 0 1px 2px rgba(0,0,0,.1) inset; }`; var _st = document.createTextNode(_s); btnStyle.appendChild(_st); head.appendChild(btnStyle); //navstrip.appendChild(_span); navstrip.appendChild(main_tbl); var one = document.querySelector('#main_tbl #one'), two = document.querySelector('#main_tbl #two'); one.appendChild(_span); two.appendChild(_span2); _span.innerHTML = 'Обновлений: <font id="_cnt" color="red">' + count + '</font> <input id="hideBtn" class="myBtn" type="button" value="Скрыть обновления" style="display: none;" /> <br/>' + `<table class="_tbl" style="border-collapse: collapse; border: 0px"> <thead> <tr> <th class="brown-right-line">#</th> <th class="brown-right-line">Название</th> <th>Версия</th> </tr> </thead> <tbody> </tbody> </table>`; var _tbl = document.querySelector('._tbl'), _tbody = _tbl.querySelector('tbody'), _cnt = document.querySelector('#_cnt'), n = 0; _tbl.style.display = 'none'; var tblStyle = document.createElement('style'); tblStyle.type = 'text/css'; var s = ` ._tbl th { color: brown; background-color: white; text-align: center; padding: 2px; letter-spacing: 0px; } ._tbl td { font-size: 10px; padding: 0 5px; } ._tbl .brown-right-line { border-right: 1px solid; } ._tbl .black-right-line { border-right: 1px solid; }`; var st = document.createTextNode(s); tblStyle.appendChild(st); head.appendChild(tblStyle); // кнопка скрытия обновлений вручную var hideBtn = document.querySelector('#hideBtn'); hideBtn.onclick = function() { hideBtn.style.display = 'none'; // сразу сохраняем обновленные версии в память, чтобы при следующем обновлении не всплыли в таблице обновлений for(var i=0; i<saveToHideName.length; i++) { localStorage.setItem(saveToHideName[i], saveToHideVer[i]); } // скрываем таблицу с обновлениями и обнуляем счетчик _tbl.style.display = 'none'; count = 0; _cnt.innerHTML = count; for(; _tbody.querySelectorAll('tr').length > 0;) { _tbl.deleteRow(1); } } _span2.innerHTML = '<div id="ban"> <input id="showBanList" class="myBtn" type="button" value="Показать ЧС" /> <input id="clearBanList" class="myBtn" type="button" value="Очистить ЧС" disabled /> </div>' + `<table class="_tbl" id="ban_tbl" style="border-collapse: collapse; border: 0px; display: none;"> <thead> <tr> <th class="brown-right-line">#</th> <th class="brown-right-line">Название</th> <th class="brown-right-line">Версия</th> <th>ID темы</th> </tr> </thead> <tbody id="tbody2"> </tbody> </table>`; checkBan(); function checkBan() { var tb = document.querySelector('#tbody2'); if(savedIDs.length > 0 && savedIDs[0] !== '-1') { _span2.style.display = ''; } else { _span2.style.display = 'none'; } } var showBanList = document.querySelector('#showBanList'), clearBanList = document.querySelector('#clearBanList'), ban_tbl = document.querySelector('#ban_tbl'); showBanList.onclick = function() { if(ban_tbl.style.display == 'none') { ban_tbl.style.display = 'block'; showBanList.value = 'Скрыть ЧС'; clearBanList.disabled = false; clearBanList.style.textDecoration = ''; } else { ban_tbl.style.display = 'none'; showBanList.value = 'Показать ЧС'; clearBanList.disabled = true; clearBanList.style.textDecoration = 'line-through'; //clearBanList.style.setProperty("text-decoration", "line-through"); } } clearBanList.style.textDecoration = 'line-through'; clearBanList.onclick = function() { localStorage.setItem('savedIDs', '-1'); checkSavedIDs(); //log(savedIDs); } // добавлению в таблицу скрытых приложений, их версии и id их темы var num = 0; function getBannedApps(app_name, ver, id) { num++; var _tbody2 = document.querySelector('#tbody2'); var row =_tbody2.insertRow(-1), cell1 = row.insertCell(-1), cell2 = row.insertCell(-1), cell3 = row.insertCell(-1), cell4 = row.insertCell(-1), cell5 = row.insertCell(-1); row.className = 'myTr'; cell1.className = 'black-right-line one'; cell2.className = 'black-right-line'; cell3.className = 'black-right-line'; cell1.innerHTML = num; cell2.innerHTML = app_name; cell3.innerHTML = ver; cell4.innerHTML = id; cell5.innerHTML = '<input class="myBtn hidden" type="button" value="Удалить" style="display: none;">'; } // показ количества обновлений и вывод их в таблице function showUpdates(app_name, ver) { _tbl.style.display = 'block'; n++; var row = _tbody.insertRow(-1), cell1 = row.insertCell(-1), cell2 = row.insertCell(-1), cell3 = row.insertCell(-1), cell4 = row.insertCell(-1); row.className = 'myTr'; cell1.className = 'black-right-line one'; cell2.className = 'black-right-line'; cell1.innerHTML = n; _cnt.innerHTML = count; cell2.innerHTML = app_name; cell3.innerHTML = ver; cell4.innerHTML = '<input class="myBtn hidden" type="button" value="Скрыть" style="display: none;">'; } function addEvent() { var myTr = document.querySelectorAll('.myTr'); for(var i=0; i<myTr.length; i++) { myTr[i].addEventListener('mouseover', function showButton() { this.querySelector('.hidden').style.display = 'block'; }); myTr[i].addEventListener('mouseout', function hideButton() { this.querySelector('.hidden').style.display = 'none'; }); } } function hideApp() { var hBut = document.querySelectorAll('.myBtn.hidden'); for(var i=0; i<hBut.length; i++) { hBut[i].onclick = function() { var n = this.parentNode.parentNode.firstChild.innerHTML if(this.value === 'Скрыть') { var name = this.parentNode.parentNode.children[1].children[1].innerHTML, ver = this.parentNode.parentNode.children[2].innerHTML; localStorage.setItem(name, ver); // сброс # таблицы и удаление строк(и) _tbl.deleteRow(n); let num = _tbl.querySelectorAll('td.one'); // если было скрыто последнее обновление, скрываем шапку таблицы и кнопку "Скрыть обновления" if(num.length === 0) { _tbl.style.display = 'none'; hideBtn.style.display = 'none'; } for(var j=0; j<num.length; j++) { num[j].innerHTML = j+1; } _cnt.innerHTML = j; } else { let id = this.parentNode.parentNode.children[3].innerHTML, arr = localStorage.getItem('savedIDs'); arr = arr.split(','); for(let k=0; k<arr.length; k++) { if(id === arr[k]) { arr.splice(k, 1); } } if(arr.length !== 0) { localStorage.setItem('savedIDs', arr); } else { localStorage.setItem('savedIDs', '-1'); } // сброс # таблицы и удаление строк(и) ban_tbl.deleteRow(n); let num = ban_tbl.querySelectorAll('td.one'); // если было скрыто последнее обновление, скрываем шапку таблицы и кнопку "Скрыть обновления" if(num.length === 0) { ban_tbl.style.display = 'none'; var banBtns = document.querySelector('#ban'); banBtns.style.display = 'none'; } for(j=0; j<num.length; j++) { num[j].innerHTML = j+1; } } } } } //========================================================================== // мигание 'NEW' // уже неактуально /*function mig() { var isRed = true; var nw = document.querySelectorAll('.new'); var dln = nw.length; if (dln !== 0) { var morganie = setInterval(function() { if (isRed) { for (i=0; i<dln; i++) { nw[i].style.color = 'blue'; } isRed = false; } else { for (i=0; i<dln; i++) { nw[i].style.color = 'red'; } isRed = true; } }, 300); } }*/ } else { // Топики var post = document.querySelectorAll('.postdetails > center'), userLink = document.getElementsByClassName('normalname'), link = [], // собираем все ссылки на профили ulLength = userLink.length; for (i=0; i<ulLength; i++) { getUserData(userLink[i].querySelector('a').getAttribute('href'), i); } //========================================================================== var data0, data1, data2, data3; // пол, город, дата рождения, местное время // создание области для новых данных var div = document.createElement('div'); div.style.border = '1px solid lightblue'; div.style.padding = '5px'; div.id = 'myDiv'; // Стиль для новой области var style = document.createElement('style'); style.type = 'text/css'; var h = '#myDiv:hover {background: PaleTurquoise; color: blue; font-size: 10pt;}'; var hover = document.createTextNode(h); style.appendChild(hover); //head = document.getElementsByTagName('head')[0]; head.appendChild(style); function getUserData(link, i) { var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest; var xhr = new XHR(); xhr.open('GET', link, true); xhr.send(); xhr.onload = function() { if(this.readyState === 4) { if (this.status === 200) { var response = xhr.responseText; var parser = new DOMParser(); var doc = parser.parseFromString(response, 'text/html'); var main = doc.getElementsByClassName('info-list width1 black-link')[0]; main.style.marginLeft = 0; main.style.paddingLeft = 0; main.style.display = 'block'; main.style.listStyle = 'none'; var t = main.querySelectorAll('li'), tt = ''; for (var l=0; l<t.length; l++) { tt += t[l].innerHTML.replace(/<[^>]+>/g,'').replace(/(Город:)/, '$1 ').replace(/(юзера:)/, '$1 ').replace(/(рождения:)/, '$1 ') + '<br/>'; } insertData(tt, i); } } }; xhr.onerror = function() { log('error'); alert('Ошибка'); }; xhr.onloadend = function(event) { //log('onloadend'); totalKB += (event.loaded/####); // подсчет загруженного траффика totalMB += (event.loaded/####/####); if(++l === ulLength) { log('Скачано XHR-запросами:', totalKB.toFixed(2), 'КБ |', totalMB.toFixed(2), 'МБ'); // вывод объема скачанного } }; } //========================================================================== function insertData(data0, i) { div.innerHTML = data0; post[i].appendChild(div.cloneNode(true)); } } })();