Вывод версий приложений в Избранном 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));
- }
- }
- })();