diff --git a/4chan_x.user.js b/4chan_x.user.js
index 966222bde..b9d5e6b9a 100644
--- a/4chan_x.user.js
+++ b/4chan_x.user.js
@@ -1738,7 +1738,7 @@
$.on(link.firstChild, 'click', function() {
QR.open();
if (!g.REPLY) {
- $('select', QR.el).value = 'new';
+ QR.threadSelector.value = 'new';
}
return $('textarea', QR.el).focus();
});
@@ -1836,44 +1836,118 @@
},
cooldown: {
init: function() {
- var length, timeout, _ref;
if (!Conf['Cooldown']) {
return;
}
- _ref = $.get("/" + g.BOARD + "/cooldown", {}), timeout = _ref.timeout, length = _ref.length;
- if (timeout) {
- QR.cooldown.start(timeout, length);
+ QR.cooldown.types = {
+ thread: (function() {
+ switch (g.BOARD) {
+ case 'q':
+ return 86400;
+ case 'b':
+ case 'soc':
+ case 'r9k':
+ return 600;
+ default:
+ return 300;
+ }
+ })(),
+ sage: g.BOARD === 'q' ? 600 : 60,
+ file: g.BOARD === 'q' ? 300 : 30,
+ post: g.BOARD === 'q' ? 60 : 30
+ };
+ QR.cooldown.cooldowns = $.get("" + g.BOARD + ".cooldown", {});
+ QR.cooldown.start();
+ return $.sync("" + g.BOARD + ".cooldown", QR.cooldown.sync);
+ },
+ start: function() {
+ if (QR.cooldown.isCounting) {
+ return;
}
- return $.sync("/" + g.BOARD + "/cooldown", QR.cooldown.start);
+ QR.cooldown.isCounting = true;
+ return QR.cooldown.count();
},
- start: function(timeout, length) {
- var seconds;
- seconds = Math.floor((timeout - Date.now()) / 1000);
- return QR.cooldown.count(seconds, length);
+ sync: function(cooldowns) {
+ QR.cooldown.cooldowns = cooldowns;
+ return QR.cooldown.start();
},
- set: function(seconds) {
+ set: function(data) {
+ var cooldown, hasFile, isReply, isSage, start, type;
if (!Conf['Cooldown']) {
return;
}
- QR.cooldown.count(seconds, seconds);
- return $.set("/" + g.BOARD + "/cooldown", {
- timeout: Date.now() + seconds * $.SECOND,
- length: seconds
- });
+ start = Date.now();
+ if (data.delay) {
+ cooldown = {
+ delay: data.delay
+ };
+ } else {
+ isSage = /sage/i.test(data.post.email);
+ hasFile = !!data.post.file;
+ isReply = data.isReply;
+ type = !isReply ? 'thread' : isSage ? 'sage' : hasFile ? 'file' : 'post';
+ cooldown = {
+ isReply: isReply,
+ isSage: isSage,
+ hasFile: hasFile,
+ timeout: start + QR.cooldown.types[type] * $.SECOND
+ };
+ }
+ QR.cooldown.cooldowns[start] = cooldown;
+ $.set("" + g.BOARD + ".cooldown", QR.cooldown.cooldowns);
+ return QR.cooldown.start();
},
- count: function(seconds, length) {
- if (!((0 <= seconds && seconds <= length))) {
+ unset: function(id) {
+ delete QR.cooldown.cooldowns[id];
+ return $.set("" + g.BOARD + ".cooldown", QR.cooldown.cooldowns);
+ },
+ count: function() {
+ var cooldown, cooldowns, elapsed, hasFile, isReply, isSage, now, post, seconds, start, type, types, _ref;
+ if (Object.keys(QR.cooldown.cooldowns).length) {
+ setTimeout(QR.cooldown.count, 1000);
+ } else {
+ $["delete"]("" + g.BOARD + ".cooldown");
+ QR.cooldown.isCounting = false;
return;
}
- setTimeout(QR.cooldown.count, 1000, seconds - 1, length);
- QR.cooldown.seconds = seconds;
- if (seconds === 0) {
- $["delete"]("/" + g.BOARD + "/cooldown");
- if (QR.cooldown.auto) {
- QR.submit();
+ if ((isReply = g.REPLY ? true : QR.threadSelector.value !== 'new')) {
+ post = QR.replies[0];
+ isSage = /sage/i.test(post.email);
+ hasFile = !!post.file;
+ }
+ now = Date.now();
+ seconds = null;
+ _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns;
+ for (start in cooldowns) {
+ cooldown = cooldowns[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;
+ }
+ type = isReply && cooldown.isReply ? isSage && cooldown.isSage ? 'sage' : hasFile && cooldown.hasFile ? 'file' : 'post' : !(isReply || cooldown.isReply) ? type = 'thread' : void 0;
+ if (type) {
+ elapsed = Math.floor((now - start) / 1000);
+ if (elapsed >= 0) {
+ seconds = Math.max(seconds, types[type] - elapsed);
+ }
+ type = '';
+ }
+ if (!((start <= now && now <= cooldown.timeout))) {
+ QR.cooldown.unset(start);
}
}
- return QR.status();
+ QR.cooldown.seconds = seconds;
+ if (seconds !== null) {
+ QR.status();
+ }
+ if (seconds === 0 && QR.cooldown.auto) {
+ return QR.submit();
+ }
}
},
quote: function(e) {
@@ -1883,7 +1957,7 @@
}
QR.open();
if (!g.REPLY) {
- $('select', QR.el).value = $.x('ancestor::div[parent::div[@class="board"]]', this).id.slice(1);
+ QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', this).id.slice(1);
}
id = this.previousSibling.hash.slice(2);
text = ">>" + id + "\n";
@@ -2173,10 +2247,7 @@
(QR.replies[index - 1] || QR.replies[index + 1]).select();
}
QR.replies.splice(index, 1);
- if (typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function") {
- _base1.revokeObjectURL(this.url);
- }
- return delete this;
+ return typeof (_base1 = window.URL || window.webkitURL).revokeObjectURL === "function" ? _base1.revokeObjectURL(this.url) : void 0;
};
return _Class;
@@ -2342,11 +2413,12 @@
id = thread.id.slice(1);
threads += "";
}
- $.prepend($('.move > span', QR.el), $.el('select', {
+ QR.threadSelector = $.el('select', {
innerHTML: threads,
title: 'Create a new thread / Reply to a thread'
- }));
- $.on($('select', QR.el), 'mousedown', function(e) {
+ });
+ $.prepend($('.move > span', QR.el), QR.threadSelector);
+ $.on(QR.threadSelector, 'mousedown', function(e) {
return e.stopPropagation();
});
}
@@ -2380,7 +2452,7 @@
$.on($("[name=" + name + "]", QR.el), 'input', function() {
var _ref2;
QR.selected[this.name] = this.value;
- if (QR.cooldown.auto && QR.selected === QR.replies[0] && (0 < (_ref2 = QR.cooldown.seconds) && _ref2 < 6)) {
+ if (QR.cooldown.auto && QR.selected === QR.replies[0] && (0 < (_ref2 = QR.cooldown.seconds) && _ref2 <= 5)) {
return QR.cooldown.auto = false;
}
});
@@ -2406,7 +2478,7 @@
}
QR.abort();
reply = QR.replies[0];
- threadID = g.THREAD_ID || $('select', QR.el).value;
+ threadID = g.THREAD_ID || QR.threadSelector.value;
if (threadID === 'new') {
if (((_ref = g.BOARD) === 'vg' || _ref === 'q') && !reply.sub) {
err = 'New threads require a subject.';
@@ -2506,7 +2578,7 @@
return QR.ajax = $.ajax($.id('postForm').parentNode.action, callbacks, opts);
},
response: function(html) {
- var bs, doc, err, msg, persona, postID, reply, sage, seconds, threadID, _, _ref, _ref1;
+ var bs, doc, err, msg, persona, postID, reply, threadID, _, _ref, _ref1;
doc = d.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = html;
if (doc.title === '4chan - Banned') {
@@ -2527,7 +2599,9 @@
err.textContent = 'Error: You seem to have mistyped the CAPTCHA.';
}
QR.cooldown.auto = QR.captchaIsEnabled ? !!$.get('captchas', []).length : true;
- QR.cooldown.set(2);
+ QR.cooldown.set({
+ delay: 2
+ });
} else {
QR.cooldown.auto = false;
}
@@ -2551,13 +2625,14 @@
postID: postID
}
}));
+ QR.cooldown.set({
+ post: reply,
+ isReply: threadID !== '0'
+ });
if (threadID === '0') {
location.pathname = "/" + g.BOARD + "/res/" + postID;
} else {
QR.cooldown.auto = QR.replies.length > 1;
- sage = /sage/i.test(reply.email);
- seconds = g.BOARD === 'q' ? sage ? 600 : reply.file ? 300 : 60 : sage ? 60 : 30;
- QR.cooldown.set(seconds);
if (Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto) {
$.open("//boards.4chan.org/" + g.BOARD + "/res/" + threadID + "#p" + postID);
}
diff --git a/changelog b/changelog
index 3b568d710..971bddfa3 100644
--- a/changelog
+++ b/changelog
@@ -1,4 +1,8 @@
master
+- Mayhem
+ Added thread creation QR cooldown.
+ Fix QR cooldown timer between non-sage and sage posts. You can submit a non-sage post 30 seconds after a sage one.
+ Fix /q/ QR cooldowns.
2.35.4
- Mayhem
diff --git a/script.coffee b/script.coffee
index 67bc142f0..d5c15bc64 100644
--- a/script.coffee
+++ b/script.coffee
@@ -1342,7 +1342,7 @@ QR =
link = $.el 'h1', innerHTML: "#{if g.REPLY then 'Reply to Thread' else 'Start a Thread'}"
$.on link.firstChild, 'click', ->
QR.open()
- $('select', QR.el).value = 'new' unless g.REPLY
+ QR.threadSelector.value = 'new' unless g.REPLY
$('textarea', QR.el).focus()
$.before $.id('postForm'), link
@@ -1418,32 +1418,108 @@ QR =
cooldown:
init: ->
return unless Conf['Cooldown']
- {timeout, length} = $.get "/#{g.BOARD}/cooldown", {}
- QR.cooldown.start timeout, length if timeout
- $.sync "/#{g.BOARD}/cooldown", QR.cooldown.start
- start: (timeout, length) ->
- seconds = Math.floor (timeout - Date.now()) / 1000
- QR.cooldown.count seconds, length
- set: (seconds) ->
+ QR.cooldown.types =
+ thread: switch g.BOARD
+ when 'q' then 86400
+ when 'b', 'soc', 'r9k' then 600
+ else 300
+ sage: if g.BOARD is 'q' then 600 else 60
+ file: if g.BOARD is 'q' then 300 else 30
+ post: if g.BOARD is 'q' then 60 else 30
+ QR.cooldown.cooldowns = $.get "#{g.BOARD}.cooldown", {}
+ QR.cooldown.start()
+ $.sync "#{g.BOARD}.cooldown", QR.cooldown.sync
+ start: ->
+ return if QR.cooldown.isCounting
+ QR.cooldown.isCounting = true
+ QR.cooldown.count()
+ sync: (cooldowns) ->
+ QR.cooldown.cooldowns = cooldowns
+ QR.cooldown.start()
+ set: (data) ->
return unless Conf['Cooldown']
- QR.cooldown.count seconds, seconds
- $.set "/#{g.BOARD}/cooldown",
- timeout: Date.now() + seconds * $.SECOND
- length: seconds
- count: (seconds, length) ->
- return unless 0 <= seconds <= length
- setTimeout QR.cooldown.count, 1000, seconds-1, length
+ start = Date.now()
+ if data.delay
+ cooldown = delay: data.delay
+ else
+ isSage = /sage/i.test data.post.email
+ hasFile = !!data.post.file
+ isReply = data.isReply
+ type =
+ unless isReply
+ 'thread'
+ else
+ if isSage
+ 'sage'
+ else if hasFile
+ 'file'
+ else
+ 'post'
+ cooldown =
+ isReply: isReply
+ isSage: isSage
+ hasFile: hasFile
+ timeout: start + QR.cooldown.types[type] * $.SECOND
+ QR.cooldown.cooldowns[start] = cooldown
+ $.set "#{g.BOARD}.cooldown", QR.cooldown.cooldowns
+ QR.cooldown.start()
+ unset: (id) ->
+ delete QR.cooldown.cooldowns[id]
+ $.set "#{g.BOARD}.cooldown", QR.cooldown.cooldowns
+ count: ->
+ if Object.keys(QR.cooldown.cooldowns).length
+ setTimeout QR.cooldown.count, 1000
+ else
+ $.delete "#{g.BOARD}.cooldown"
+ QR.cooldown.isCounting = false
+ return
+
+ if (isReply = if g.REPLY then true else QR.threadSelector.value isnt 'new')
+ post = QR.replies[0]
+ isSage = /sage/i.test post.email
+ hasFile = !!post.file
+ now = Date.now()
+ seconds = null
+ {types, cooldowns} = QR.cooldown
+
+ for start, cooldown of cooldowns
+ if 'delay' of cooldown
+ if cooldown.delay
+ seconds = Math.max seconds, cooldown.delay--
+ else
+ seconds = Math.max seconds, 0
+ QR.cooldown.unset start
+ continue
+
+ # Only cooldowns relevant to this post can set the seconds value.
+ # Unset outdated cooldowns that can no longer impact us.
+ type =
+ if isReply and cooldown.isReply
+ if isSage and cooldown.isSage
+ 'sage'
+ else if hasFile and cooldown.hasFile
+ 'file'
+ else
+ 'post'
+ else unless isReply or cooldown.isReply
+ type = 'thread'
+ if type
+ elapsed = Math.floor (now - start) / 1000
+ if elapsed >= 0 # clock changed since then?
+ seconds = Math.max seconds, types[type] - elapsed
+ type = ''
+ unless start <= now <= cooldown.timeout
+ QR.cooldown.unset start
+
QR.cooldown.seconds = seconds
- if seconds is 0
- $.delete "/#{g.BOARD}/cooldown"
- QR.submit() if QR.cooldown.auto
- QR.status()
+ QR.status() if seconds isnt null
+ QR.submit() if seconds is 0 and QR.cooldown.auto
quote: (e) ->
e?.preventDefault()
QR.open()
unless g.REPLY
- $('select', QR.el).value = $.x('ancestor::div[parent::div[@class="board"]]', @).id[1..]
+ QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', @).id[1..]
# Make sure we get the correct number, even with XXX censors
id = @previousSibling.hash[2..]
text = ">>#{id}\n"
@@ -1674,7 +1750,6 @@ QR =
(QR.replies[index-1] or QR.replies[index+1]).select()
QR.replies.splice index, 1
(window.URL or window.webkitURL).revokeObjectURL? @url
- delete @
captcha:
init: ->
@@ -1804,10 +1879,11 @@ QR =
for thread in $$ '.thread'
id = thread.id[1..]
threads += ""
- $.prepend $('.move > span', QR.el), $.el 'select'
+ QR.threadSelector = $.el 'select'
innerHTML: threads
title: 'Create a new thread / Reply to a thread'
- $.on $('select', QR.el), 'mousedown', (e) -> e.stopPropagation()
+ $.prepend $('.move > span', QR.el), QR.threadSelector
+ $.on QR.threadSelector, 'mousedown', (e) -> e.stopPropagation()
$.on $('#autohide', QR.el), 'change', QR.toggleHide
$.on $('.close', QR.el), 'click', QR.close
$.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump'
@@ -1828,7 +1904,7 @@ QR =
QR.selected[@name] = @value
# Disable auto-posting if you're typing in the first reply
# during the last 5 seconds of the cooldown.
- if QR.cooldown.auto and QR.selected is QR.replies[0] and 0 < QR.cooldown.seconds < 6
+ if QR.cooldown.auto and QR.selected is QR.replies[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false
QR.status.input = $ 'input[type=submit]', QR.el
@@ -1851,7 +1927,7 @@ QR =
QR.abort()
reply = QR.replies[0]
- threadID = g.THREAD_ID or $('select', QR.el).value
+ threadID = g.THREAD_ID or QR.threadSelector.value
# prevent errors
if threadID is 'new'
@@ -1972,7 +2048,7 @@ QR =
true
# Too many frequent mistyped captchas will auto-ban you!
# On connection error, the post most likely didn't go through.
- QR.cooldown.set 2
+ QR.cooldown.set delay: 2
else # stop auto-posting
QR.cooldown.auto = false
QR.status()
@@ -1997,29 +2073,16 @@ QR =
threadID: threadID
postID: postID
+ QR.cooldown.set
+ post: reply
+ isReply: threadID isnt '0'
+
if threadID is '0' # new thread
# auto-noko
location.pathname = "/#{g.BOARD}/res/#{postID}"
else
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.replies.length > 1
- sage = /sage/i.test reply.email
- seconds =
- # 300 seconds cooldown for new threads
- # q: 86400 seconds
- # b soc r9k: 600 seconds
- if g.BOARD is 'q'
- if sage
- 600
- else if reply.file
- 300
- else
- 60
- else if sage
- 60
- else
- 30
- QR.cooldown.set seconds
if Conf['Open Reply in New Tab'] and !g.REPLY and !QR.cooldown.auto
$.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}"