Release 4chan X v1.9.6.0.

This commit is contained in:
ccd0 2014-10-05 22:00:04 -07:00
parent fb30a59fe7
commit 0ffddd8a79
14 changed files with 646 additions and 468 deletions

View File

@ -2,6 +2,16 @@ The attributions below are for work that has been incorporated into the script a
The links to individual versions below are to copies of the script with the update URL removed. If you want automatic updates, install the script from the links on the [main page](https://github.com/ccd0/4chan-x).
<!-- v1.9.6.x -->
### v1.9.6.0
*2014-10-05* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.6.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.6.0/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Update cooldown timers to include 4chan's new 5-minute global thread cooldown.
- Fix various cooldown bugs.
- Improve error messages for invalid filter entries.
- Fix bug causing hidden threads list to be reverted to a prior state due to syncing with native catalog.
<!-- v1.9.5.x -->
### v1.9.5.3
*2014-10-03* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.5.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.5.3/builds/4chan-X-noupdate.crx "Chromium version")]

View File

@ -1,5 +1,5 @@
/*
* 4chan X - Version 1.9.5.3
* 4chan X - Version 1.9.6.0
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X beta
// @version 1.9.5.3
// @version 1.9.6.0
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript
// ==UserScript==
// @name 4chan X beta
// @version 1.9.5.3
// @version 1.9.6.0
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@ -24,7 +24,7 @@
// ==/UserScript==
/*
* 4chan X - Version 1.9.5.3
* 4chan X - Version 1.9.6.0
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -380,7 +380,7 @@
doc = d.documentElement;
g = {
VERSION: '1.9.5.3',
VERSION: '1.9.6.0',
NAMESPACE: '4chan X.',
NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -797,8 +797,13 @@
var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
if (newValue != null) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
}
};
$.on(window, 'storage', onChange);
@ -4659,7 +4664,7 @@
Filter = {
filters: {},
init: function() {
var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (g.VIEW === 'catalog' || !Conf['Filter']) {
return;
}
@ -4670,14 +4675,14 @@
this.filters[key] = [];
_ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
if (filter[0] === '#') {
line = _ref[_i];
if (line[0] === '#') {
continue;
}
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) {
if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue;
}
filter = filter.replace(regexp[0], '');
filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue;
@ -4689,7 +4694,7 @@
regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) {
err = _error;
new Notice('warning', err.message, 60);
new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60);
continue;
}
}
@ -5288,16 +5293,64 @@
ThreadHiding = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
return;
}
this.db = new DataBoard('hiddenThreads');
this.syncCatalog();
if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({
name: 'Thread Hiding',
cb: this.node
});
},
catalogSet: function(board) {
var hiddenThreads, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: board.ID,
defaultValue: {}
});
for (threadID in hiddenThreads) {
hiddenThreads[threadID] = true;
}
return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads));
},
catalogWatch: function() {
this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
return $.ready(function() {
return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), {
attributes: true,
subtree: true,
attributeFilter: ['style']
});
});
},
catalogSave: function() {
var hiddenThreads2, threadID;
hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreads2) {
if (!(threadID in ThreadHiding.hiddenThreads)) {
ThreadHiding.db.set({
boardID: g.BOARD.ID,
threadID: threadID,
val: {
makeStub: Conf['Stubs']
}
});
}
}
for (threadID in ThreadHiding.hiddenThreads) {
if (!(threadID in hiddenThreads2)) {
ThreadHiding.db["delete"]({
boardID: g.BOARD.ID,
threadID: threadID
});
}
}
return ThreadHiding.hiddenThreads = hiddenThreads2;
},
node: function() {
var data;
if (data = ThreadHiding.db.get({
@ -5321,56 +5374,6 @@
}
}
},
syncCatalog: function() {
var hiddenThreads, hiddenThreadsOnCatalog, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: g.BOARD.ID,
defaultValue: {}
});
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreadsOnCatalog) {
if (!(threadID in hiddenThreads)) {
hiddenThreads[threadID] = {};
}
}
for (threadID in hiddenThreads) {
if (!(threadID in hiddenThreadsOnCatalog)) {
delete hiddenThreads[threadID];
}
}
if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) {
ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog);
}
return ThreadHiding.db.set({
boardID: g.BOARD.ID,
val: hiddenThreads
});
},
cleanCatalog: function(hiddenThreadsOnCatalog) {
return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() {
var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
if (this.status !== 200) {
return;
}
threads = {};
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in hiddenThreadsOnCatalog) {
threads[thread.no] = hiddenThreadsOnCatalog[thread.no];
}
}
}
if (Object.keys(threads).length) {
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads));
} else {
return localStorage.removeItem("4chan-hide-t-" + g.BOARD);
}
});
},
menu: {
init: function() {
var apply, div, hideStubLink, makeStub;
@ -5501,8 +5504,6 @@
return $.prepend(root, thread.stub);
},
saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) {
ThreadHiding.db.set({
boardID: thread.board.ID,
@ -5511,15 +5512,13 @@
makeStub: makeStub
}
});
hiddenThreadsOnCatalog[thread] = true;
} else {
ThreadHiding.db["delete"]({
boardID: thread.board.ID,
threadID: thread.ID
});
delete hiddenThreadsOnCatalog[thread];
}
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog));
return ThreadHiding.catalogSet(thread.board);
},
toggle: function(thread) {
if (!(thread instanceof Thread)) {
@ -7145,14 +7144,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.';
}
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({
delay: 2
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.addDelay(post, 2);
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({
delay: m[1]
});
QR.cooldown.addDelay(post, +m[1]);
} else {
QR.cooldown.auto = false;
}
@ -7213,12 +7208,7 @@
}
post.rm();
}
QR.cooldown.set({
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
QR.cooldown.add(req.uploadEndTime, threadID, postID);
URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
if (URL) {
if (Conf['Open Post in New Tab']) {
@ -7455,119 +7445,184 @@
};
QR.cooldown = {
seconds: 0,
init: function() {
var key, setTimers, type;
var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) {
return;
}
setTimers = (function(_this) {
return function(e) {
return QR.cooldown.types = e.detail;
return QR.cooldown.delays = e.detail;
};
})(this);
$.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) {
QR.cooldown.types[type] = +QR.cooldown.types[type];
QR.cooldown.maxDelay = 0;
_ref = QR.cooldown.delays;
for (type in _ref) {
delay = _ref[type];
if (type !== 'thread') {
QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay);
}
}
key = "cooldown." + g.BOARD;
$.get(key, {}, function(item) {
QR.cooldown.cooldowns = item[key];
QR.cooldown.delays['thread_global'] = 300;
keys = QR.cooldown.keys = {
local: "cooldown." + g.BOARD,
global: 'cooldown.global'
};
items = {};
for (scope in keys) {
key = keys[scope];
items[key] = {};
}
$.get(items, function(items) {
for (scope in keys) {
key = keys[scope];
QR.cooldown[scope] = items[key];
}
return QR.cooldown.start();
});
return $.sync(key, QR.cooldown.sync);
_results = [];
for (scope in keys) {
key = keys[scope];
_results.push($.sync(key, QR.cooldown.sync(scope)));
}
return _results;
},
start: function() {
if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) {
if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) {
return;
}
QR.cooldown.isCounting = true;
return QR.cooldown.count();
},
sync: function(cooldowns) {
var id;
for (id in cooldowns) {
QR.cooldown.cooldowns[id] = cooldowns[id];
}
return QR.cooldown.start();
sync: function(scope) {
return function(cooldowns) {
QR.cooldown[scope] = cooldowns || {};
return QR.cooldown.start();
};
},
set: function(data) {
var cooldown, delay, isReply, post, req, start, threadID;
add: function(start, threadID, postID) {
var boardID;
if (!Conf['Cooldown']) {
return;
}
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay;
start = req ? req.uploadEndTime : Date.now();
if (delay) {
cooldown = {
delay: delay
};
} else {
cooldown = {
isReply: isReply,
threadID: threadID
};
boardID = g.BOARD.ID;
QR.cooldown.set('local', start, {
threadID: threadID,
postID: postID
});
if (threadID === postID) {
QR.cooldown.set('global', start, {
boardID: boardID,
threadID: threadID,
postID: postID
});
}
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start();
},
unset: function(id) {
delete QR.cooldown.cooldowns[id];
if (Object.keys(QR.cooldown.cooldowns).length) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
addDelay: function(post, delay) {
var cooldown;
if (!Conf['Cooldown']) {
return;
}
cooldown = QR.cooldown.categorize(post);
cooldown.delay = delay;
QR.cooldown.set('local', Date.now(), cooldown);
return QR.cooldown.start();
},
"delete": function(post) {
var cooldown, id, _ref;
if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) {
return;
}
$.forceSync(QR.cooldown.keys.local);
_ref = QR.cooldown.local;
for (id in _ref) {
cooldown = _ref[id];
if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) {
delete QR.cooldown.local[id];
}
}
return QR.cooldown.save('local');
},
categorize: function(post) {
if (post.thread === 'new') {
return {
type: 'thread'
};
} else {
return $["delete"]("cooldown." + g.BOARD);
return {
type: !!post.file ? 'image' : 'reply',
threadID: +post.thread
};
}
},
set: function(scope, id, value) {
$.forceSync(QR.cooldown.keys[scope]);
QR.cooldown[scope][id] = value;
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
},
save: function(scope) {
if (Object.keys(QR.cooldown[scope]).length) {
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
} else {
return $["delete"](QR.cooldown.keys[scope]);
}
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref;
if (!Object.keys(QR.cooldown.cooldowns).length) {
$["delete"]("cooldown." + g.BOARD);
delete QR.cooldown.isCounting;
delete QR.cooldown.seconds;
QR.status();
return;
}
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
now = Date.now();
post = QR.posts[0];
isReply = post.thread !== 'new';
hasFile = !!post.file;
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
for (start in cooldowns) {
cooldown = cooldowns[start];
start = +start;
if ('delay' in cooldown) {
if (cooldown.delay) {
seconds = Math.max(seconds, cooldown.delay--);
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
_ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
seconds = 0;
_ref1 = QR.cooldown.keys;
for (scope in _ref1) {
key = _ref1[scope];
$.forceSync(key);
save = false;
_ref2 = QR.cooldown[scope];
for (start in _ref2) {
cooldown = _ref2[start];
start = +start;
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) {
QR.cooldown.unset(start);
delete QR.cooldown[scope][start];
save = true;
continue;
}
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
QR.cooldown.unset(start);
if (cooldown.delay != null) {
if (cooldown.delay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
} else if (cooldown.type === type && cooldown.threadID === threadID) {
seconds = Math.max(seconds, cooldown.delay - elapsed);
}
continue;
}
if (isReply && +post.thread === cooldown.threadID) {
type += '_intra';
maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
if (maxDelay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
continue;
}
seconds = Math.max(seconds, types[type] - elapsed);
if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) {
suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : '';
seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed);
}
}
if (save) {
QR.cooldown.save(scope);
}
}
update = seconds !== null || !!QR.cooldown.seconds;
if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) {
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
} else {
delete QR.cooldown.isCounting;
}
update = seconds !== QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
if (update) {
QR.status();
@ -10188,6 +10243,7 @@
$.on(link, 'click', DeleteLink["delete"]);
} else {
if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly);
}
s = 'Deleted';
@ -13406,7 +13462,7 @@
className: 'dialog'
});
$.extend(dialog, {
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.5.3</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.6.0</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript
// ==UserScript==
// @name 4chan X
// @version 1.9.5.3
// @version 1.9.6.0
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@ -23,7 +23,7 @@
// ==/UserScript==
/*
* 4chan X - Version 1.9.5.3
* 4chan X - Version 1.9.6.0
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -379,7 +379,7 @@
doc = d.documentElement;
g = {
VERSION: '1.9.5.3',
VERSION: '1.9.6.0',
NAMESPACE: '4chan X.',
NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -796,8 +796,13 @@
var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
if (newValue != null) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
}
};
$.on(window, 'storage', onChange);
@ -4658,7 +4663,7 @@
Filter = {
filters: {},
init: function() {
var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (g.VIEW === 'catalog' || !Conf['Filter']) {
return;
}
@ -4669,14 +4674,14 @@
this.filters[key] = [];
_ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
if (filter[0] === '#') {
line = _ref[_i];
if (line[0] === '#') {
continue;
}
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) {
if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue;
}
filter = filter.replace(regexp[0], '');
filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue;
@ -4688,7 +4693,7 @@
regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) {
err = _error;
new Notice('warning', err.message, 60);
new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60);
continue;
}
}
@ -5287,16 +5292,64 @@
ThreadHiding = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
return;
}
this.db = new DataBoard('hiddenThreads');
this.syncCatalog();
if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({
name: 'Thread Hiding',
cb: this.node
});
},
catalogSet: function(board) {
var hiddenThreads, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: board.ID,
defaultValue: {}
});
for (threadID in hiddenThreads) {
hiddenThreads[threadID] = true;
}
return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads));
},
catalogWatch: function() {
this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
return $.ready(function() {
return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), {
attributes: true,
subtree: true,
attributeFilter: ['style']
});
});
},
catalogSave: function() {
var hiddenThreads2, threadID;
hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreads2) {
if (!(threadID in ThreadHiding.hiddenThreads)) {
ThreadHiding.db.set({
boardID: g.BOARD.ID,
threadID: threadID,
val: {
makeStub: Conf['Stubs']
}
});
}
}
for (threadID in ThreadHiding.hiddenThreads) {
if (!(threadID in hiddenThreads2)) {
ThreadHiding.db["delete"]({
boardID: g.BOARD.ID,
threadID: threadID
});
}
}
return ThreadHiding.hiddenThreads = hiddenThreads2;
},
node: function() {
var data;
if (data = ThreadHiding.db.get({
@ -5320,56 +5373,6 @@
}
}
},
syncCatalog: function() {
var hiddenThreads, hiddenThreadsOnCatalog, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: g.BOARD.ID,
defaultValue: {}
});
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreadsOnCatalog) {
if (!(threadID in hiddenThreads)) {
hiddenThreads[threadID] = {};
}
}
for (threadID in hiddenThreads) {
if (!(threadID in hiddenThreadsOnCatalog)) {
delete hiddenThreads[threadID];
}
}
if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) {
ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog);
}
return ThreadHiding.db.set({
boardID: g.BOARD.ID,
val: hiddenThreads
});
},
cleanCatalog: function(hiddenThreadsOnCatalog) {
return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() {
var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
if (this.status !== 200) {
return;
}
threads = {};
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in hiddenThreadsOnCatalog) {
threads[thread.no] = hiddenThreadsOnCatalog[thread.no];
}
}
}
if (Object.keys(threads).length) {
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads));
} else {
return localStorage.removeItem("4chan-hide-t-" + g.BOARD);
}
});
},
menu: {
init: function() {
var apply, div, hideStubLink, makeStub;
@ -5500,8 +5503,6 @@
return $.prepend(root, thread.stub);
},
saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) {
ThreadHiding.db.set({
boardID: thread.board.ID,
@ -5510,15 +5511,13 @@
makeStub: makeStub
}
});
hiddenThreadsOnCatalog[thread] = true;
} else {
ThreadHiding.db["delete"]({
boardID: thread.board.ID,
threadID: thread.ID
});
delete hiddenThreadsOnCatalog[thread];
}
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog));
return ThreadHiding.catalogSet(thread.board);
},
toggle: function(thread) {
if (!(thread instanceof Thread)) {
@ -7144,14 +7143,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.';
}
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({
delay: 2
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.addDelay(post, 2);
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({
delay: m[1]
});
QR.cooldown.addDelay(post, +m[1]);
} else {
QR.cooldown.auto = false;
}
@ -7212,12 +7207,7 @@
}
post.rm();
}
QR.cooldown.set({
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
QR.cooldown.add(req.uploadEndTime, threadID, postID);
URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
if (URL) {
if (Conf['Open Post in New Tab']) {
@ -7454,119 +7444,184 @@
};
QR.cooldown = {
seconds: 0,
init: function() {
var key, setTimers, type;
var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) {
return;
}
setTimers = (function(_this) {
return function(e) {
return QR.cooldown.types = e.detail;
return QR.cooldown.delays = e.detail;
};
})(this);
$.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) {
QR.cooldown.types[type] = +QR.cooldown.types[type];
QR.cooldown.maxDelay = 0;
_ref = QR.cooldown.delays;
for (type in _ref) {
delay = _ref[type];
if (type !== 'thread') {
QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay);
}
}
key = "cooldown." + g.BOARD;
$.get(key, {}, function(item) {
QR.cooldown.cooldowns = item[key];
QR.cooldown.delays['thread_global'] = 300;
keys = QR.cooldown.keys = {
local: "cooldown." + g.BOARD,
global: 'cooldown.global'
};
items = {};
for (scope in keys) {
key = keys[scope];
items[key] = {};
}
$.get(items, function(items) {
for (scope in keys) {
key = keys[scope];
QR.cooldown[scope] = items[key];
}
return QR.cooldown.start();
});
return $.sync(key, QR.cooldown.sync);
_results = [];
for (scope in keys) {
key = keys[scope];
_results.push($.sync(key, QR.cooldown.sync(scope)));
}
return _results;
},
start: function() {
if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) {
if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) {
return;
}
QR.cooldown.isCounting = true;
return QR.cooldown.count();
},
sync: function(cooldowns) {
var id;
for (id in cooldowns) {
QR.cooldown.cooldowns[id] = cooldowns[id];
}
return QR.cooldown.start();
sync: function(scope) {
return function(cooldowns) {
QR.cooldown[scope] = cooldowns || {};
return QR.cooldown.start();
};
},
set: function(data) {
var cooldown, delay, isReply, post, req, start, threadID;
add: function(start, threadID, postID) {
var boardID;
if (!Conf['Cooldown']) {
return;
}
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay;
start = req ? req.uploadEndTime : Date.now();
if (delay) {
cooldown = {
delay: delay
};
} else {
cooldown = {
isReply: isReply,
threadID: threadID
};
boardID = g.BOARD.ID;
QR.cooldown.set('local', start, {
threadID: threadID,
postID: postID
});
if (threadID === postID) {
QR.cooldown.set('global', start, {
boardID: boardID,
threadID: threadID,
postID: postID
});
}
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start();
},
unset: function(id) {
delete QR.cooldown.cooldowns[id];
if (Object.keys(QR.cooldown.cooldowns).length) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
addDelay: function(post, delay) {
var cooldown;
if (!Conf['Cooldown']) {
return;
}
cooldown = QR.cooldown.categorize(post);
cooldown.delay = delay;
QR.cooldown.set('local', Date.now(), cooldown);
return QR.cooldown.start();
},
"delete": function(post) {
var cooldown, id, _ref;
if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) {
return;
}
$.forceSync(QR.cooldown.keys.local);
_ref = QR.cooldown.local;
for (id in _ref) {
cooldown = _ref[id];
if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) {
delete QR.cooldown.local[id];
}
}
return QR.cooldown.save('local');
},
categorize: function(post) {
if (post.thread === 'new') {
return {
type: 'thread'
};
} else {
return $["delete"]("cooldown." + g.BOARD);
return {
type: !!post.file ? 'image' : 'reply',
threadID: +post.thread
};
}
},
set: function(scope, id, value) {
$.forceSync(QR.cooldown.keys[scope]);
QR.cooldown[scope][id] = value;
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
},
save: function(scope) {
if (Object.keys(QR.cooldown[scope]).length) {
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
} else {
return $["delete"](QR.cooldown.keys[scope]);
}
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref;
if (!Object.keys(QR.cooldown.cooldowns).length) {
$["delete"]("cooldown." + g.BOARD);
delete QR.cooldown.isCounting;
delete QR.cooldown.seconds;
QR.status();
return;
}
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
now = Date.now();
post = QR.posts[0];
isReply = post.thread !== 'new';
hasFile = !!post.file;
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
for (start in cooldowns) {
cooldown = cooldowns[start];
start = +start;
if ('delay' in cooldown) {
if (cooldown.delay) {
seconds = Math.max(seconds, cooldown.delay--);
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
_ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
seconds = 0;
_ref1 = QR.cooldown.keys;
for (scope in _ref1) {
key = _ref1[scope];
$.forceSync(key);
save = false;
_ref2 = QR.cooldown[scope];
for (start in _ref2) {
cooldown = _ref2[start];
start = +start;
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) {
QR.cooldown.unset(start);
delete QR.cooldown[scope][start];
save = true;
continue;
}
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
QR.cooldown.unset(start);
if (cooldown.delay != null) {
if (cooldown.delay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
} else if (cooldown.type === type && cooldown.threadID === threadID) {
seconds = Math.max(seconds, cooldown.delay - elapsed);
}
continue;
}
if (isReply && +post.thread === cooldown.threadID) {
type += '_intra';
maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
if (maxDelay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
continue;
}
seconds = Math.max(seconds, types[type] - elapsed);
if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) {
suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : '';
seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed);
}
}
if (save) {
QR.cooldown.save(scope);
}
}
update = seconds !== null || !!QR.cooldown.seconds;
if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) {
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
} else {
delete QR.cooldown.isCounting;
}
update = seconds !== QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
if (update) {
QR.status();
@ -10187,6 +10242,7 @@
$.on(link, 'click', DeleteLink["delete"]);
} else {
if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly);
}
s = 'Deleted';
@ -13405,7 +13461,7 @@
className: 'dialog'
});
$.extend(dialog, {
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.5.3</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.6.0</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

@ -1,6 +1,6 @@
// ==UserScript==
// @name 4chan X
// @version 1.9.5.3
// @version 1.9.6.0
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript
// ==UserScript==
// @name 4chan X
// @version 1.9.5.3
// @version 1.9.6.0
// @minGMVer 1.14
// @minFFVer 26
// @namespace 4chan-X
@ -24,7 +24,7 @@
// ==/UserScript==
/*
* 4chan X - Version 1.9.5.3
* 4chan X - Version 1.9.6.0
*
* Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -380,7 +380,7 @@
doc = d.documentElement;
g = {
VERSION: '1.9.5.3',
VERSION: '1.9.6.0',
NAMESPACE: '4chan X.',
NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -797,8 +797,13 @@
var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
if (newValue != null) {
oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
}
};
$.on(window, 'storage', onChange);
@ -4659,7 +4664,7 @@
Filter = {
filters: {},
init: function() {
var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
if (g.VIEW === 'catalog' || !Conf['Filter']) {
return;
}
@ -4670,14 +4675,14 @@
this.filters[key] = [];
_ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i];
if (filter[0] === '#') {
line = _ref[_i];
if (line[0] === '#') {
continue;
}
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) {
if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue;
}
filter = filter.replace(regexp[0], '');
filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global';
if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue;
@ -4689,7 +4694,7 @@
regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) {
err = _error;
new Notice('warning', err.message, 60);
new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60);
continue;
}
}
@ -5288,16 +5293,64 @@
ThreadHiding = {
init: function() {
if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) {
return;
}
this.db = new DataBoard('hiddenThreads');
this.syncCatalog();
if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({
name: 'Thread Hiding',
cb: this.node
});
},
catalogSet: function(board) {
var hiddenThreads, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: board.ID,
defaultValue: {}
});
for (threadID in hiddenThreads) {
hiddenThreads[threadID] = true;
}
return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads));
},
catalogWatch: function() {
this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
return $.ready(function() {
return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), {
attributes: true,
subtree: true,
attributeFilter: ['style']
});
});
},
catalogSave: function() {
var hiddenThreads2, threadID;
hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreads2) {
if (!(threadID in ThreadHiding.hiddenThreads)) {
ThreadHiding.db.set({
boardID: g.BOARD.ID,
threadID: threadID,
val: {
makeStub: Conf['Stubs']
}
});
}
}
for (threadID in ThreadHiding.hiddenThreads) {
if (!(threadID in hiddenThreads2)) {
ThreadHiding.db["delete"]({
boardID: g.BOARD.ID,
threadID: threadID
});
}
}
return ThreadHiding.hiddenThreads = hiddenThreads2;
},
node: function() {
var data;
if (data = ThreadHiding.db.get({
@ -5321,56 +5374,6 @@
}
}
},
syncCatalog: function() {
var hiddenThreads, hiddenThreadsOnCatalog, threadID;
hiddenThreads = ThreadHiding.db.get({
boardID: g.BOARD.ID,
defaultValue: {}
});
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
for (threadID in hiddenThreadsOnCatalog) {
if (!(threadID in hiddenThreads)) {
hiddenThreads[threadID] = {};
}
}
for (threadID in hiddenThreads) {
if (!(threadID in hiddenThreadsOnCatalog)) {
delete hiddenThreads[threadID];
}
}
if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) {
ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog);
}
return ThreadHiding.db.set({
boardID: g.BOARD.ID,
val: hiddenThreads
});
},
cleanCatalog: function(hiddenThreadsOnCatalog) {
return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() {
var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1;
if (this.status !== 200) {
return;
}
threads = {};
_ref = this.response;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
page = _ref[_i];
_ref1 = page.threads;
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
thread = _ref1[_j];
if (thread.no in hiddenThreadsOnCatalog) {
threads[thread.no] = hiddenThreadsOnCatalog[thread.no];
}
}
}
if (Object.keys(threads).length) {
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads));
} else {
return localStorage.removeItem("4chan-hide-t-" + g.BOARD);
}
});
},
menu: {
init: function() {
var apply, div, hideStubLink, makeStub;
@ -5501,8 +5504,6 @@
return $.prepend(root, thread.stub);
},
saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) {
ThreadHiding.db.set({
boardID: thread.board.ID,
@ -5511,15 +5512,13 @@
makeStub: makeStub
}
});
hiddenThreadsOnCatalog[thread] = true;
} else {
ThreadHiding.db["delete"]({
boardID: thread.board.ID,
threadID: thread.ID
});
delete hiddenThreadsOnCatalog[thread];
}
return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog));
return ThreadHiding.catalogSet(thread.board);
},
toggle: function(thread) {
if (!(thread instanceof Thread)) {
@ -7145,14 +7144,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.';
}
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({
delay: 2
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.addDelay(post, 2);
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({
delay: m[1]
});
QR.cooldown.addDelay(post, +m[1]);
} else {
QR.cooldown.auto = false;
}
@ -7213,12 +7208,7 @@
}
post.rm();
}
QR.cooldown.set({
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
QR.cooldown.add(req.uploadEndTime, threadID, postID);
URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0;
if (URL) {
if (Conf['Open Post in New Tab']) {
@ -7455,119 +7445,184 @@
};
QR.cooldown = {
seconds: 0,
init: function() {
var key, setTimers, type;
var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) {
return;
}
setTimers = (function(_this) {
return function(e) {
return QR.cooldown.types = e.detail;
return QR.cooldown.delays = e.detail;
};
})(this);
$.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) {
QR.cooldown.types[type] = +QR.cooldown.types[type];
QR.cooldown.maxDelay = 0;
_ref = QR.cooldown.delays;
for (type in _ref) {
delay = _ref[type];
if (type !== 'thread') {
QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay);
}
}
key = "cooldown." + g.BOARD;
$.get(key, {}, function(item) {
QR.cooldown.cooldowns = item[key];
QR.cooldown.delays['thread_global'] = 300;
keys = QR.cooldown.keys = {
local: "cooldown." + g.BOARD,
global: 'cooldown.global'
};
items = {};
for (scope in keys) {
key = keys[scope];
items[key] = {};
}
$.get(items, function(items) {
for (scope in keys) {
key = keys[scope];
QR.cooldown[scope] = items[key];
}
return QR.cooldown.start();
});
return $.sync(key, QR.cooldown.sync);
_results = [];
for (scope in keys) {
key = keys[scope];
_results.push($.sync(key, QR.cooldown.sync(scope)));
}
return _results;
},
start: function() {
if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) {
if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) {
return;
}
QR.cooldown.isCounting = true;
return QR.cooldown.count();
},
sync: function(cooldowns) {
var id;
for (id in cooldowns) {
QR.cooldown.cooldowns[id] = cooldowns[id];
}
return QR.cooldown.start();
sync: function(scope) {
return function(cooldowns) {
QR.cooldown[scope] = cooldowns || {};
return QR.cooldown.start();
};
},
set: function(data) {
var cooldown, delay, isReply, post, req, start, threadID;
add: function(start, threadID, postID) {
var boardID;
if (!Conf['Cooldown']) {
return;
}
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay;
start = req ? req.uploadEndTime : Date.now();
if (delay) {
cooldown = {
delay: delay
};
} else {
cooldown = {
isReply: isReply,
threadID: threadID
};
boardID = g.BOARD.ID;
QR.cooldown.set('local', start, {
threadID: threadID,
postID: postID
});
if (threadID === postID) {
QR.cooldown.set('global', start, {
boardID: boardID,
threadID: threadID,
postID: postID
});
}
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start();
},
unset: function(id) {
delete QR.cooldown.cooldowns[id];
if (Object.keys(QR.cooldown.cooldowns).length) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
addDelay: function(post, delay) {
var cooldown;
if (!Conf['Cooldown']) {
return;
}
cooldown = QR.cooldown.categorize(post);
cooldown.delay = delay;
QR.cooldown.set('local', Date.now(), cooldown);
return QR.cooldown.start();
},
"delete": function(post) {
var cooldown, id, _ref;
if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) {
return;
}
$.forceSync(QR.cooldown.keys.local);
_ref = QR.cooldown.local;
for (id in _ref) {
cooldown = _ref[id];
if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) {
delete QR.cooldown.local[id];
}
}
return QR.cooldown.save('local');
},
categorize: function(post) {
if (post.thread === 'new') {
return {
type: 'thread'
};
} else {
return $["delete"]("cooldown." + g.BOARD);
return {
type: !!post.file ? 'image' : 'reply',
threadID: +post.thread
};
}
},
set: function(scope, id, value) {
$.forceSync(QR.cooldown.keys[scope]);
QR.cooldown[scope][id] = value;
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
},
save: function(scope) {
if (Object.keys(QR.cooldown[scope]).length) {
return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]);
} else {
return $["delete"](QR.cooldown.keys[scope]);
}
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref;
if (!Object.keys(QR.cooldown.cooldowns).length) {
$["delete"]("cooldown." + g.BOARD);
delete QR.cooldown.isCounting;
delete QR.cooldown.seconds;
QR.status();
return;
}
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
now = Date.now();
post = QR.posts[0];
isReply = post.thread !== 'new';
hasFile = !!post.file;
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
for (start in cooldowns) {
cooldown = cooldowns[start];
start = +start;
if ('delay' in cooldown) {
if (cooldown.delay) {
seconds = Math.max(seconds, cooldown.delay--);
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
_ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
seconds = 0;
_ref1 = QR.cooldown.keys;
for (scope in _ref1) {
key = _ref1[scope];
$.forceSync(key);
save = false;
_ref2 = QR.cooldown[scope];
for (start in _ref2) {
cooldown = _ref2[start];
start = +start;
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) {
QR.cooldown.unset(start);
delete QR.cooldown[scope][start];
save = true;
continue;
}
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
QR.cooldown.unset(start);
if (cooldown.delay != null) {
if (cooldown.delay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
} else if (cooldown.type === type && cooldown.threadID === threadID) {
seconds = Math.max(seconds, cooldown.delay - elapsed);
}
continue;
}
if (isReply && +post.thread === cooldown.threadID) {
type += '_intra';
maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
if (maxDelay <= elapsed) {
delete QR.cooldown[scope][start];
save = true;
continue;
}
seconds = Math.max(seconds, types[type] - elapsed);
if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) {
suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : '';
seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed);
}
}
if (save) {
QR.cooldown.save(scope);
}
}
update = seconds !== null || !!QR.cooldown.seconds;
if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) {
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
} else {
delete QR.cooldown.isCounting;
}
update = seconds !== QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
if (update) {
QR.status();
@ -10188,6 +10243,7 @@
$.on(link, 'click', DeleteLink["delete"]);
} else {
if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly);
}
s = 'Deleted';
@ -13406,7 +13462,7 @@
className: 'dialog'
});
$.extend(dialog, {
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.5.3</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
innerHTML: "<nav><div class=sections-list></div><p class='imp-exp-result warning'></p><div class=credits><a class=export>Export</a>&nbsp|&nbsp<a class=import>Import</a>&nbsp|&nbsp<a class=reset>Reset Settings</a>&nbsp|&nbsp<input type=file hidden><a href='https://github.com/ccd0/4chan-x' target=_blank>4chan X</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/blob/master/CHANGELOG.md' target=_blank>1.9.6.0</a>&nbsp|&nbsp<a href='https://github.com/ccd0/4chan-x/issues' target=_blank>Issues</a>&nbsp|&nbsp<a href=javascript:; class='close fa fa-times' title=Close></a></div></nav><div class=section-container><section></section></div>"
});
$.on($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.9.5.3' />
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X-beta.crx' version='1.9.6.0' />
</app>
</gupdate>

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'>
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.9.5.3' />
<updatecheck codebase='https://ccd0.github.io/4chan-x/builds/4chan-X.crx' version='1.9.6.0' />
</app>
</gupdate>

View File

@ -3,7 +3,7 @@
"description": "Cross-browser userscript for maximum lurking on 4chan.",
"meta": {
"name": "4chan X",
"version": "1.9.5.3",
"version": "1.9.6.0",
"repo": "https://github.com/ccd0/4chan-x/",
"page": "https://github.com/ccd0/4chan-x",
"downloads": "https://ccd0.github.io/4chan-x/builds/",