// ==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/1024); // подсчет загруженного траффика
totalMB += (event.loaded/1024/1024);
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/1024); // подсчет загруженного траффика
totalMB += (event.loaded/1024/1024);
if(++l === ulLength) {
log('Скачано XHR-запросами:', totalKB.toFixed(2), 'КБ |', totalMB.toFixed(2), 'МБ'); // вывод объема скачанного
}
};
}
//==========================================================================
function insertData(data0, i) {
div.innerHTML = data0;
post[i].appendChild(div.cloneNode(true));
}
}
})();