try to take over the world!
当前为
// ==UserScript==
// @name Gazelle Forum: New Posts Indicator
// @namespace https://greasyfork.org/users/321857-anakunda#user-script-list
// @version 1.0
// @description try to take over the world!
// @author Anakunda
// @copyright 2020, Anakunda (https://greasyfork.org/users/321857-anakunda#user-script-list)
// @license GPL-3.0-or-later
// @match https://*/forums.php?action=viewthread&threadid=*
// @connect *
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
'use strict';
const interval = 15;
const siteApiTimeframeStorageKey = 'AJAX time frame';
const gazelleApiFrameReserve = 500; // reserve that amount of ms for service operations
let threadId = new URLSearchParams(document.location.search);
if (threadId.has('threadid')) threadId = parseInt(threadId.get('threadid')); else throw 'Thread Id missing';
let postBody = document.querySelector('textarea#quickpost');
if (postBody == null) throw 'Post body missing';
let replyControls = document.querySelector('form#quickpostform > div.preview_submit'), newPostsIndicator = null;
if (replyControls == null) throw 'Reply controls missing';
if (typeof GM_getValue == 'function') switch (document.domain) {
case 'redacted.ch':
var apiKey = GM_getValue('redacted_api_key');
if (!apiKey) GM_setValue('redacted_api_key', '');
break;
}
function queryAjaxAPI(action, params) {
if (!action) return Promise.reject('Action missing');
let retryCount = 0;
return new Promise(function(resolve, reject) {
params = new URLSearchParams(params || undefined);
params.set('action', action);
let url = '/ajax.php?' + params, xhr = new XMLHttpRequest;
queryInternal();
function queryInternal() {
let now = Date.now();
try { var apiTimeFrame = JSON.parse(window.localStorage[siteApiTimeframeStorageKey]) } catch(e) { apiTimeFrame = {} }
if (!apiTimeFrame.timeStamp || now > apiTimeFrame.timeStamp + 10000 + gazelleApiFrameReserve) {
apiTimeFrame.timeStamp = now;
apiTimeFrame.requestCounter = 1;
} else ++apiTimeFrame.requestCounter;
window.localStorage[siteApiTimeframeStorageKey] = JSON.stringify(apiTimeFrame);
if (apiTimeFrame.requestCounter <= 5) {
xhr.open('GET', url, true);
xhr.setRequestHeader('Accept', 'application/json');
if (apiKey) xhr.setRequestHeader('Authorization', apiKey);
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status == 404) return reject('not found');
if (xhr.status < 200 || xhr.status >= 400) return reject(defaultErrorHandler(xhr));
if (xhr.response.status == 'success') return resolve(xhr.response.response);
if (xhr.response.error == 'not found') return reject(xhr.response.error);
console.warn('queryAjaxAPI.queryInternal(...) response:', xhr, xhr.response);
if (xhr.response.error == 'rate limit exceeded') {
console.warn('queryAjaxAPI.queryInternal(...) ' + xhr.response.error + ':', apiTimeFrame, now, retryCount);
if (retryCount++ <= 10)
return setTimeout(queryInternal, apiTimeFrame.timeStamp + 10000 + gazelleApiFrameReserve - now);
}
reject('API ' + xhr.response.status + ': ' + xhr.response.error);
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.timeout = 10000;
xhr.send();
} else {
let delay = apiTimeFrame.timeStamp + 10000 + gazelleApiFrameReserve - now;
setTimeout(queryInternal, delay);
}
}
});
}
const getThreadSnapshot = (page = 999999) => queryAjaxAPI('forum', { type: 'viewthread', threadid: threadId, page: page });
let snapshot = getThreadSnapshot(), activeTimer;
function checkPosts() {
snapshot.then(snapshot => getThreadSnapshot(snapshot.currentPage).then(function(response) {
let now = new Date();
console.debug(threadId, now.toLocaleTimeString(), response.pages, snapshot.pages,
response.posts.length, snapshot.posts.length);
if (response.pages <= snapshot.pages && response.posts.length <= snapshot.posts.length) return;
activeTimer = clearInterval(activeTimer);
if (newPostsIndicator == null) {
newPostsIndicator = document.createElement('span');
newPostsIndicator.style = 'margin-left: 3em; color: darkorange;';
replyControls.append(newPostsIndicator);
}
newPostsIndicator.textContent = 'This thread received new post(s) [';
let link = document.createElement('a');
link.textContent = 'refresh';
if (response.pages <= snapshot.pages || response.posts.length > snapshot.posts.length) {
link.href = '#';
link.onclick = function(evt) {
document.location.reload();
return false;
};
} else link.href = '/forums.php?action=viewthread&threadid=' + response.threadId +
'&page=' + (response.currentPage + 1);
newPostsIndicator.append(link);
newPostsIndicator.append(']');
}));
}
postBody.onfocus = function(evt) {
if (activeTimer || newPostsIndicator != null) return;
//console.debug('New thread posts monitor activated', evt);
activeTimer = setInterval(checkPosts, interval * 1000);
checkPosts();
};
postBody.onblur = function(evt) {
if (!activeTimer) return;
//console.debug('New thread posts monitor deactivated', evt);
activeTimer = clearInterval(activeTimer);
console.assert(activeTimer === undefined, 'activeTimer === undefined');
};
function defaultErrorHandler(response) {
console.error('HTTP error:', response);
let e = 'HTTP error ' + response.status;
if (response.statusText) e += ' (' + response.statusText + ')';
if (response.error) e += ' (' + response.error + ')';
return e;
}
function defaultTimeoutHandler(response) {
console.error('HTTP timeout:', response);
const e = 'HTTP timeout';
return e;
}