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). 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.x -->
### v1.9.5.3 ### 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")] *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. * Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE * https://github.com/ccd0/4chan-x/blob/master/LICENSE

Binary file not shown.

View File

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

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript // Generated by CoffeeScript
// ==UserScript== // ==UserScript==
// @name 4chan X beta // @name 4chan X beta
// @version 1.9.5.3 // @version 1.9.6.0
// @minGMVer 1.14 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
@ -24,7 +24,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* 4chan X - Version 1.9.5.3 * 4chan X - Version 1.9.6.0
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE * https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -380,7 +380,7 @@
doc = d.documentElement; doc = d.documentElement;
g = { g = {
VERSION: '1.9.5.3', VERSION: '1.9.6.0',
NAMESPACE: '4chan X.', NAMESPACE: '4chan X.',
NAME: '4chan X', NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -797,8 +797,13 @@
var cb, key, newValue; var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue; key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) { if (cb = $.syncing[key]) {
oldValue[key] = newValue; if (newValue != null) {
return cb(JSON.parse(newValue), key); oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
} }
}; };
$.on(window, 'storage', onChange); $.on(window, 'storage', onChange);
@ -4659,7 +4664,7 @@
Filter = { Filter = {
filters: {}, filters: {},
init: function() { 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']) { if (g.VIEW === 'catalog' || !Conf['Filter']) {
return; return;
} }
@ -4670,14 +4675,14 @@
this.filters[key] = []; this.filters[key] = [];
_ref = Conf[key].split('\n'); _ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i]; line = _ref[_i];
if (filter[0] === '#') { if (line[0] === '#') {
continue; continue;
} }
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue; continue;
} }
filter = filter.replace(regexp[0], ''); filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; 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)) { if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue; continue;
@ -4689,7 +4694,7 @@
regexp = RegExp(regexp[1], regexp[2]); regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) { } catch (_error) {
err = _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; continue;
} }
} }
@ -5288,16 +5293,64 @@
ThreadHiding = { ThreadHiding = {
init: function() { 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; return;
} }
this.db = new DataBoard('hiddenThreads'); this.db = new DataBoard('hiddenThreads');
this.syncCatalog(); if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({ return Thread.callbacks.push({
name: 'Thread Hiding', name: 'Thread Hiding',
cb: this.node 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() { node: function() {
var data; var data;
if (data = ThreadHiding.db.get({ 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: { menu: {
init: function() { init: function() {
var apply, div, hideStubLink, makeStub; var apply, div, hideStubLink, makeStub;
@ -5501,8 +5504,6 @@
return $.prepend(root, thread.stub); return $.prepend(root, thread.stub);
}, },
saveHiddenState: function(thread, makeStub) { saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) { if (thread.isHidden) {
ThreadHiding.db.set({ ThreadHiding.db.set({
boardID: thread.board.ID, boardID: thread.board.ID,
@ -5511,15 +5512,13 @@
makeStub: makeStub makeStub: makeStub
} }
}); });
hiddenThreadsOnCatalog[thread] = true;
} else { } else {
ThreadHiding.db["delete"]({ ThreadHiding.db["delete"]({
boardID: thread.board.ID, boardID: thread.board.ID,
threadID: thread.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) { toggle: function(thread) {
if (!(thread instanceof Thread)) { if (!(thread instanceof Thread)) {
@ -7145,14 +7144,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.'; 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.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({ QR.cooldown.addDelay(post, 2);
delay: 2 } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({ QR.cooldown.addDelay(post, +m[1]);
delay: m[1]
});
} else { } else {
QR.cooldown.auto = false; QR.cooldown.auto = false;
} }
@ -7213,12 +7208,7 @@
} }
post.rm(); post.rm();
} }
QR.cooldown.set({ QR.cooldown.add(req.uploadEndTime, threadID, postID);
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
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; 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 (URL) {
if (Conf['Open Post in New Tab']) { if (Conf['Open Post in New Tab']) {
@ -7455,119 +7445,184 @@
}; };
QR.cooldown = { QR.cooldown = {
seconds: 0,
init: function() { init: function() {
var key, setTimers, type; var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
setTimers = (function(_this) { setTimers = (function(_this) {
return function(e) { return function(e) {
return QR.cooldown.types = e.detail; return QR.cooldown.delays = e.detail;
}; };
})(this); })(this);
$.on(window, 'cooldown:timers', setTimers); $.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers); $.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) { QR.cooldown.maxDelay = 0;
QR.cooldown.types[type] = +QR.cooldown.types[type]; _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; QR.cooldown.delays['thread_global'] = 300;
$.get(key, {}, function(item) { keys = QR.cooldown.keys = {
QR.cooldown.cooldowns = item[key]; 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 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() { 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; return;
} }
QR.cooldown.isCounting = true; QR.cooldown.isCounting = true;
return QR.cooldown.count(); return QR.cooldown.count();
}, },
sync: function(cooldowns) { sync: function(scope) {
var id; return function(cooldowns) {
for (id in cooldowns) { QR.cooldown[scope] = cooldowns || {};
QR.cooldown.cooldowns[id] = cooldowns[id]; return QR.cooldown.start();
} };
return QR.cooldown.start();
}, },
set: function(data) { add: function(start, threadID, postID) {
var cooldown, delay, isReply, post, req, start, threadID; var boardID;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; boardID = g.BOARD.ID;
start = req ? req.uploadEndTime : Date.now(); QR.cooldown.set('local', start, {
if (delay) { threadID: threadID,
cooldown = { postID: postID
delay: delay });
}; if (threadID === postID) {
} else { QR.cooldown.set('global', start, {
cooldown = { boardID: boardID,
isReply: isReply, threadID: threadID,
threadID: threadID postID: postID
}; });
} }
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start(); return QR.cooldown.start();
}, },
unset: function(id) { addDelay: function(post, delay) {
delete QR.cooldown.cooldowns[id]; var cooldown;
if (Object.keys(QR.cooldown.cooldowns).length) { if (!Conf['Cooldown']) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); 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 { } 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() { count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
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);
now = Date.now(); now = Date.now();
post = QR.posts[0]; _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
isReply = post.thread !== 'new'; seconds = 0;
hasFile = !!post.file; _ref1 = QR.cooldown.keys;
seconds = null; for (scope in _ref1) {
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; key = _ref1[scope];
for (start in cooldowns) { $.forceSync(key);
cooldown = cooldowns[start]; save = false;
start = +start; _ref2 = QR.cooldown[scope];
if ('delay' in cooldown) { for (start in _ref2) {
if (cooldown.delay) { cooldown = _ref2[start];
seconds = Math.max(seconds, cooldown.delay--); start = +start;
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
elapsed = Math.floor((now - start) / $.SECOND); elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) { if (elapsed < 0) {
QR.cooldown.unset(start); delete QR.cooldown[scope][start];
save = true;
continue; continue;
} }
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; if (cooldown.delay != null) {
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); if (cooldown.delay <= elapsed) {
if (!((start <= now && now <= start + maxTimer * $.SECOND))) { delete QR.cooldown[scope][start];
QR.cooldown.unset(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) { maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
type += '_intra'; 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; QR.cooldown.seconds = seconds;
if (update) { if (update) {
QR.status(); QR.status();
@ -10188,6 +10243,7 @@
$.on(link, 'click', DeleteLink["delete"]); $.on(link, 'click', DeleteLink["delete"]);
} else { } else {
if (resDoc.title === 'Updating index...') { if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly); (post.origin || post).kill(fileOnly);
} }
s = 'Deleted'; s = 'Deleted';
@ -13406,7 +13462,7 @@
className: 'dialog' className: 'dialog'
}); });
$.extend(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($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript // Generated by CoffeeScript
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.9.5.3 // @version 1.9.6.0
// @minGMVer 1.14 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
@ -23,7 +23,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* 4chan X - Version 1.9.5.3 * 4chan X - Version 1.9.6.0
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE * https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -379,7 +379,7 @@
doc = d.documentElement; doc = d.documentElement;
g = { g = {
VERSION: '1.9.5.3', VERSION: '1.9.6.0',
NAMESPACE: '4chan X.', NAMESPACE: '4chan X.',
NAME: '4chan X', NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -796,8 +796,13 @@
var cb, key, newValue; var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue; key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) { if (cb = $.syncing[key]) {
oldValue[key] = newValue; if (newValue != null) {
return cb(JSON.parse(newValue), key); oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
} }
}; };
$.on(window, 'storage', onChange); $.on(window, 'storage', onChange);
@ -4658,7 +4663,7 @@
Filter = { Filter = {
filters: {}, filters: {},
init: function() { 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']) { if (g.VIEW === 'catalog' || !Conf['Filter']) {
return; return;
} }
@ -4669,14 +4674,14 @@
this.filters[key] = []; this.filters[key] = [];
_ref = Conf[key].split('\n'); _ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i]; line = _ref[_i];
if (filter[0] === '#') { if (line[0] === '#') {
continue; continue;
} }
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue; continue;
} }
filter = filter.replace(regexp[0], ''); filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; 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)) { if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue; continue;
@ -4688,7 +4693,7 @@
regexp = RegExp(regexp[1], regexp[2]); regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) { } catch (_error) {
err = _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; continue;
} }
} }
@ -5287,16 +5292,64 @@
ThreadHiding = { ThreadHiding = {
init: function() { 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; return;
} }
this.db = new DataBoard('hiddenThreads'); this.db = new DataBoard('hiddenThreads');
this.syncCatalog(); if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({ return Thread.callbacks.push({
name: 'Thread Hiding', name: 'Thread Hiding',
cb: this.node 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() { node: function() {
var data; var data;
if (data = ThreadHiding.db.get({ 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: { menu: {
init: function() { init: function() {
var apply, div, hideStubLink, makeStub; var apply, div, hideStubLink, makeStub;
@ -5500,8 +5503,6 @@
return $.prepend(root, thread.stub); return $.prepend(root, thread.stub);
}, },
saveHiddenState: function(thread, makeStub) { saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) { if (thread.isHidden) {
ThreadHiding.db.set({ ThreadHiding.db.set({
boardID: thread.board.ID, boardID: thread.board.ID,
@ -5510,15 +5511,13 @@
makeStub: makeStub makeStub: makeStub
} }
}); });
hiddenThreadsOnCatalog[thread] = true;
} else { } else {
ThreadHiding.db["delete"]({ ThreadHiding.db["delete"]({
boardID: thread.board.ID, boardID: thread.board.ID,
threadID: thread.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) { toggle: function(thread) {
if (!(thread instanceof Thread)) { if (!(thread instanceof Thread)) {
@ -7144,14 +7143,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.'; 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.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({ QR.cooldown.addDelay(post, 2);
delay: 2 } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({ QR.cooldown.addDelay(post, +m[1]);
delay: m[1]
});
} else { } else {
QR.cooldown.auto = false; QR.cooldown.auto = false;
} }
@ -7212,12 +7207,7 @@
} }
post.rm(); post.rm();
} }
QR.cooldown.set({ QR.cooldown.add(req.uploadEndTime, threadID, postID);
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
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; 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 (URL) {
if (Conf['Open Post in New Tab']) { if (Conf['Open Post in New Tab']) {
@ -7454,119 +7444,184 @@
}; };
QR.cooldown = { QR.cooldown = {
seconds: 0,
init: function() { init: function() {
var key, setTimers, type; var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
setTimers = (function(_this) { setTimers = (function(_this) {
return function(e) { return function(e) {
return QR.cooldown.types = e.detail; return QR.cooldown.delays = e.detail;
}; };
})(this); })(this);
$.on(window, 'cooldown:timers', setTimers); $.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers); $.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) { QR.cooldown.maxDelay = 0;
QR.cooldown.types[type] = +QR.cooldown.types[type]; _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; QR.cooldown.delays['thread_global'] = 300;
$.get(key, {}, function(item) { keys = QR.cooldown.keys = {
QR.cooldown.cooldowns = item[key]; 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 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() { 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; return;
} }
QR.cooldown.isCounting = true; QR.cooldown.isCounting = true;
return QR.cooldown.count(); return QR.cooldown.count();
}, },
sync: function(cooldowns) { sync: function(scope) {
var id; return function(cooldowns) {
for (id in cooldowns) { QR.cooldown[scope] = cooldowns || {};
QR.cooldown.cooldowns[id] = cooldowns[id]; return QR.cooldown.start();
} };
return QR.cooldown.start();
}, },
set: function(data) { add: function(start, threadID, postID) {
var cooldown, delay, isReply, post, req, start, threadID; var boardID;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; boardID = g.BOARD.ID;
start = req ? req.uploadEndTime : Date.now(); QR.cooldown.set('local', start, {
if (delay) { threadID: threadID,
cooldown = { postID: postID
delay: delay });
}; if (threadID === postID) {
} else { QR.cooldown.set('global', start, {
cooldown = { boardID: boardID,
isReply: isReply, threadID: threadID,
threadID: threadID postID: postID
}; });
} }
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start(); return QR.cooldown.start();
}, },
unset: function(id) { addDelay: function(post, delay) {
delete QR.cooldown.cooldowns[id]; var cooldown;
if (Object.keys(QR.cooldown.cooldowns).length) { if (!Conf['Cooldown']) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); 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 { } 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() { count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
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);
now = Date.now(); now = Date.now();
post = QR.posts[0]; _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
isReply = post.thread !== 'new'; seconds = 0;
hasFile = !!post.file; _ref1 = QR.cooldown.keys;
seconds = null; for (scope in _ref1) {
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; key = _ref1[scope];
for (start in cooldowns) { $.forceSync(key);
cooldown = cooldowns[start]; save = false;
start = +start; _ref2 = QR.cooldown[scope];
if ('delay' in cooldown) { for (start in _ref2) {
if (cooldown.delay) { cooldown = _ref2[start];
seconds = Math.max(seconds, cooldown.delay--); start = +start;
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
elapsed = Math.floor((now - start) / $.SECOND); elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) { if (elapsed < 0) {
QR.cooldown.unset(start); delete QR.cooldown[scope][start];
save = true;
continue; continue;
} }
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; if (cooldown.delay != null) {
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); if (cooldown.delay <= elapsed) {
if (!((start <= now && now <= start + maxTimer * $.SECOND))) { delete QR.cooldown[scope][start];
QR.cooldown.unset(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) { maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
type += '_intra'; 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; QR.cooldown.seconds = seconds;
if (update) { if (update) {
QR.status(); QR.status();
@ -10187,6 +10242,7 @@
$.on(link, 'click', DeleteLink["delete"]); $.on(link, 'click', DeleteLink["delete"]);
} else { } else {
if (resDoc.title === 'Updating index...') { if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly); (post.origin || post).kill(fileOnly);
} }
s = 'Deleted'; s = 'Deleted';
@ -13405,7 +13461,7 @@
className: 'dialog' className: 'dialog'
}); });
$.extend(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($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

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

View File

@ -1,7 +1,7 @@
// Generated by CoffeeScript // Generated by CoffeeScript
// ==UserScript== // ==UserScript==
// @name 4chan X // @name 4chan X
// @version 1.9.5.3 // @version 1.9.6.0
// @minGMVer 1.14 // @minGMVer 1.14
// @minFFVer 26 // @minFFVer 26
// @namespace 4chan-X // @namespace 4chan-X
@ -24,7 +24,7 @@
// ==/UserScript== // ==/UserScript==
/* /*
* 4chan X - Version 1.9.5.3 * 4chan X - Version 1.9.6.0
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/ccd0/4chan-x/blob/master/LICENSE * https://github.com/ccd0/4chan-x/blob/master/LICENSE
@ -380,7 +380,7 @@
doc = d.documentElement; doc = d.documentElement;
g = { g = {
VERSION: '1.9.5.3', VERSION: '1.9.6.0',
NAMESPACE: '4chan X.', NAMESPACE: '4chan X.',
NAME: '4chan X', NAME: '4chan X',
FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions',
@ -797,8 +797,13 @@
var cb, key, newValue; var cb, key, newValue;
key = _arg.key, newValue = _arg.newValue; key = _arg.key, newValue = _arg.newValue;
if (cb = $.syncing[key]) { if (cb = $.syncing[key]) {
oldValue[key] = newValue; if (newValue != null) {
return cb(JSON.parse(newValue), key); oldValue[key] = newValue;
return cb(JSON.parse(newValue), key);
} else {
delete oldValue[key];
return cb(void 0, key);
}
} }
}; };
$.on(window, 'storage', onChange); $.on(window, 'storage', onChange);
@ -4659,7 +4664,7 @@
Filter = { Filter = {
filters: {}, filters: {},
init: function() { 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']) { if (g.VIEW === 'catalog' || !Conf['Filter']) {
return; return;
} }
@ -4670,14 +4675,14 @@
this.filters[key] = []; this.filters[key] = [];
_ref = Conf[key].split('\n'); _ref = Conf[key].split('\n');
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
filter = _ref[_i]; line = _ref[_i];
if (filter[0] === '#') { if (line[0] === '#') {
continue; continue;
} }
if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { if (!(regexp = line.match(/\/(.+)\/(\w*)/))) {
continue; continue;
} }
filter = filter.replace(regexp[0], ''); filter = line.replace(regexp[0], '');
boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; 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)) { if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) {
continue; continue;
@ -4689,7 +4694,7 @@
regexp = RegExp(regexp[1], regexp[2]); regexp = RegExp(regexp[1], regexp[2]);
} catch (_error) { } catch (_error) {
err = _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; continue;
} }
} }
@ -5288,16 +5293,64 @@
ThreadHiding = { ThreadHiding = {
init: function() { 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; return;
} }
this.db = new DataBoard('hiddenThreads'); this.db = new DataBoard('hiddenThreads');
this.syncCatalog(); if (g.VIEW === 'catalog') {
return this.catalogWatch();
}
this.catalogSet(g.BOARD);
return Thread.callbacks.push({ return Thread.callbacks.push({
name: 'Thread Hiding', name: 'Thread Hiding',
cb: this.node 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() { node: function() {
var data; var data;
if (data = ThreadHiding.db.get({ 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: { menu: {
init: function() { init: function() {
var apply, div, hideStubLink, makeStub; var apply, div, hideStubLink, makeStub;
@ -5501,8 +5504,6 @@
return $.prepend(root, thread.stub); return $.prepend(root, thread.stub);
}, },
saveHiddenState: function(thread, makeStub) { saveHiddenState: function(thread, makeStub) {
var hiddenThreadsOnCatalog;
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {};
if (thread.isHidden) { if (thread.isHidden) {
ThreadHiding.db.set({ ThreadHiding.db.set({
boardID: thread.board.ID, boardID: thread.board.ID,
@ -5511,15 +5512,13 @@
makeStub: makeStub makeStub: makeStub
} }
}); });
hiddenThreadsOnCatalog[thread] = true;
} else { } else {
ThreadHiding.db["delete"]({ ThreadHiding.db["delete"]({
boardID: thread.board.ID, boardID: thread.board.ID,
threadID: thread.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) { toggle: function(thread) {
if (!(thread instanceof Thread)) { if (!(thread instanceof Thread)) {
@ -7145,14 +7144,10 @@
err = 'This CAPTCHA is no longer valid because it has expired.'; 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.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false;
QR.cooldown.set({ QR.cooldown.addDelay(post, 2);
delay: 2 } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) {
});
} else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) {
QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true;
QR.cooldown.set({ QR.cooldown.addDelay(post, +m[1]);
delay: m[1]
});
} else { } else {
QR.cooldown.auto = false; QR.cooldown.auto = false;
} }
@ -7213,12 +7208,7 @@
} }
post.rm(); post.rm();
} }
QR.cooldown.set({ QR.cooldown.add(req.uploadEndTime, threadID, postID);
req: req,
post: post,
isReply: isReply,
threadID: threadID
});
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; 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 (URL) {
if (Conf['Open Post in New Tab']) { if (Conf['Open Post in New Tab']) {
@ -7455,119 +7445,184 @@
}; };
QR.cooldown = { QR.cooldown = {
seconds: 0,
init: function() { init: function() {
var key, setTimers, type; var delay, items, key, keys, scope, setTimers, type, _ref, _results;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
setTimers = (function(_this) { setTimers = (function(_this) {
return function(e) { return function(e) {
return QR.cooldown.types = e.detail; return QR.cooldown.delays = e.detail;
}; };
})(this); })(this);
$.on(window, 'cooldown:timers', setTimers); $.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
$.off(window, 'cooldown:timers', setTimers); $.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) { QR.cooldown.maxDelay = 0;
QR.cooldown.types[type] = +QR.cooldown.types[type]; _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; QR.cooldown.delays['thread_global'] = 300;
$.get(key, {}, function(item) { keys = QR.cooldown.keys = {
QR.cooldown.cooldowns = item[key]; 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 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() { 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; return;
} }
QR.cooldown.isCounting = true; QR.cooldown.isCounting = true;
return QR.cooldown.count(); return QR.cooldown.count();
}, },
sync: function(cooldowns) { sync: function(scope) {
var id; return function(cooldowns) {
for (id in cooldowns) { QR.cooldown[scope] = cooldowns || {};
QR.cooldown.cooldowns[id] = cooldowns[id]; return QR.cooldown.start();
} };
return QR.cooldown.start();
}, },
set: function(data) { add: function(start, threadID, postID) {
var cooldown, delay, isReply, post, req, start, threadID; var boardID;
if (!Conf['Cooldown']) { if (!Conf['Cooldown']) {
return; return;
} }
req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; boardID = g.BOARD.ID;
start = req ? req.uploadEndTime : Date.now(); QR.cooldown.set('local', start, {
if (delay) { threadID: threadID,
cooldown = { postID: postID
delay: delay });
}; if (threadID === postID) {
} else { QR.cooldown.set('global', start, {
cooldown = { boardID: boardID,
isReply: isReply, threadID: threadID,
threadID: threadID postID: postID
}; });
} }
QR.cooldown.cooldowns[start] = cooldown;
$.set("cooldown." + g.BOARD, QR.cooldown.cooldowns);
return QR.cooldown.start(); return QR.cooldown.start();
}, },
unset: function(id) { addDelay: function(post, delay) {
delete QR.cooldown.cooldowns[id]; var cooldown;
if (Object.keys(QR.cooldown.cooldowns).length) { if (!Conf['Cooldown']) {
return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); 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 { } 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() { count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2;
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);
now = Date.now(); now = Date.now();
post = QR.posts[0]; _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID;
isReply = post.thread !== 'new'; seconds = 0;
hasFile = !!post.file; _ref1 = QR.cooldown.keys;
seconds = null; for (scope in _ref1) {
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; key = _ref1[scope];
for (start in cooldowns) { $.forceSync(key);
cooldown = cooldowns[start]; save = false;
start = +start; _ref2 = QR.cooldown[scope];
if ('delay' in cooldown) { for (start in _ref2) {
if (cooldown.delay) { cooldown = _ref2[start];
seconds = Math.max(seconds, cooldown.delay--); start = +start;
} else {
seconds = Math.max(seconds, 0);
QR.cooldown.unset(start);
}
continue;
}
if (isReply === cooldown.isReply) {
elapsed = Math.floor((now - start) / $.SECOND); elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) { if (elapsed < 0) {
QR.cooldown.unset(start); delete QR.cooldown[scope][start];
save = true;
continue; continue;
} }
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; if (cooldown.delay != null) {
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); if (cooldown.delay <= elapsed) {
if (!((start <= now && now <= start + maxTimer * $.SECOND))) { delete QR.cooldown[scope][start];
QR.cooldown.unset(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) { maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread'];
type += '_intra'; 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; QR.cooldown.seconds = seconds;
if (update) { if (update) {
QR.status(); QR.status();
@ -10188,6 +10243,7 @@
$.on(link, 'click', DeleteLink["delete"]); $.on(link, 'click', DeleteLink["delete"]);
} else { } else {
if (resDoc.title === 'Updating index...') { if (resDoc.title === 'Updating index...') {
QR.cooldown["delete"](post);
(post.origin || post).kill(fileOnly); (post.origin || post).kill(fileOnly);
} }
s = 'Deleted'; s = 'Deleted';
@ -13406,7 +13462,7 @@
className: 'dialog' className: 'dialog'
}); });
$.extend(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($('.export', Settings.dialog), 'click', Settings["export"]);
$.on($('.import', Settings.dialog), 'click', Settings["import"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]);

Binary file not shown.

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'> <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> </app>
</gupdate> </gupdate>

View File

@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
<app appid='lacclbnghgdicfifcamcmcnilckjamag'> <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> </app>
</gupdate> </gupdate>

View File

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