diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4326dac..635079655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -### v2.9.9 +**MayhemYDG**: +- Fix captcha submission: + Captchas were reloaded the instant a post was submitted to 4chan. Unfortunately, a recent change to reCAPTCHA made it so reloading captchas invalidates the ones that loaded but not yet used. This is now fixed by only unloading the captcha, and only load new ones after the post is submitted.
+ This also kills captcha caching, so the feature was removed. + +### v2.9.9 *2014-03-27* **MayhemYDG**: diff --git a/LICENSE b/LICENSE index 96451be83..8e27d3cee 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* appchan x - Version 2.9.9 - 2014-03-27 +* appchan x - Version 2.9.9 - 2014-04-02 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index e9d63675a..3f4914d79 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -25,7 +25,7 @@ // ==/UserScript== /* -* appchan x - Version 2.9.9 - 2014-03-27 +* appchan x - Version 2.9.9 - 2014-04-02 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -8063,6 +8063,9 @@ if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } + if (Conf['Embedding'] || Conf['Link Title']) { + this.embedProcess = Function('link', "var data = this.services(link); if (data) { " + ((Conf['Embedding'] ? 'this.embed(data);\n' : '') + (Conf['Title Link'] ? 'this.title(data);' : '')) + " }"); + } return Post.callbacks.push({ name: 'Linkify', cb: this.node @@ -8132,17 +8135,7 @@ Linkify.embedProcess(Linkify.makeLink(link, this)); } }, - embedProcess: function(link) { - var data; - if (data = Linkify.services(link)) { - if (Conf['Embedding']) { - Linkify.embed(data); - } - if (Conf['Link Title']) { - return Linkify.title(data); - } - } - }, + embedProcess: function() {}, regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, makeRange: function(startNode, endNode, startOffset, endOffset) { var range; @@ -9354,6 +9347,10 @@ onload: QR.response, onerror: function() { delete QR.req; + if (QR.captcha.isEnabled) { + QR.captcha.destroy(); + QR.captcha.setup(); + } post.unlock(); QR.cooldown.auto = false; QR.status(); @@ -9383,9 +9380,12 @@ return QR.status(); }, response: function() { - var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; + var URL, ban, board, err, h1, isReply, m, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; req = QR.req; delete QR.req; + if (QR.captcha.isEnabled) { + QR.captcha.destroy(); + } post = QR.posts[0]; post.unlock(); resDoc = req.response; @@ -9407,15 +9407,13 @@ if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { if (/mistyped/i.test(err.textContent)) { err = 'You seem to have mistyped the CAPTCHA.'; - } else if (/expired/i.test(err.textContent)) { - 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 = false; QR.cooldown.set({ delay: 2 }); } 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.cooldown.set({ delay: m[1] }); @@ -9454,22 +9452,6 @@ }); postsCount = QR.posts.length - 1; QR.cooldown.auto = postsCount && isReply; - if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) { - notif = new Notification('Quick reply warning', { - body: "You are running low on cached captchas. Cache count: " + captchasCount + ".", - icon: Favicon.logo - }); - notif.onclick = function() { - QR.open(); - QR.captcha.nodes.input.focus(); - return window.focus(); - }; - notif.onshow = function() { - return setTimeout(function() { - return notif.close(); - }, 7 * $.SECOND); - }; - } if (!(Conf['Persistent QR'] || QR.cooldown.auto)) { QR.close(); } else { @@ -9525,24 +9507,21 @@ QR.captcha = { init: function() { - var container, imgContainer, input; + var imgContainer, input; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - container = $.id('captchaContainer'); - if (!(this.isEnabled = !!container)) { + if (!(this.isEnabled = !!$.id('captchaContainer'))) { return; } imgContainer = $.el('div', { className: 'captcha-img', title: 'Reload reCAPTCHA', - innerHTML: '
', - hidden: true + innerHTML: '
' }); input = $.el('input', { className: 'captcha-input field', title: 'Verification', - placeholder: 'Focus to load reCAPTCHA', autocomplete: 'off', spellcheck: false, tabIndex: 45 @@ -9551,47 +9530,41 @@ img: imgContainer.firstChild.firstChild, input: input }; - $.on(input, 'focus', this.setup); $.on(input, 'blur', QR.focusout); $.on(input, 'focus', QR.focusin); $.addClass(QR.nodes.el, 'has-captcha'); $.after(QR.nodes.com.parentNode, [imgContainer, input]); + this.beforeSetup(); + return this.afterSetup(); + }, + beforeSetup: function() { + var img, input, _ref; + _ref = this.nodes, img = _ref.img, input = _ref.input; + img.parentNode.parentNode.hidden = true; + input.value = ''; + input.placeholder = 'Focus to load reCAPTCHA'; + $.on(input, 'focus', this.setup); this.setupObserver = new MutationObserver(this.afterSetup); - this.setupObserver.observe(container, { + return this.setupObserver.observe($.id('captchaContainer'), { childList: true }); - if (Conf['Auto-load captcha']) { - this.setup(); - } - return this.afterSetup(); }, setup: function() { return $.globalEval('loadRecaptcha()'); }, afterSetup: function() { - var challenge, img, input, setLifetime, _ref; + var challenge, img, input, _ref; if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { return; } QR.captcha.setupObserver.disconnect(); delete QR.captcha.setupObserver; - setLifetime = function(e) { - return QR.captcha.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); - $.off(window, 'captcha:timeout', setLifetime); _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input; img.parentNode.parentNode.hidden = false; + input.placeholder = 'Verification'; $.off(input, 'focus', QR.captcha.setup); $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); $.on(img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); - $.get('captchas', [], function(_arg) { - var captchas; - captchas = _arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', QR.captcha.sync); QR.captcha.nodes.challenge = challenge; new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { childList: true, @@ -9600,94 +9573,31 @@ }); return QR.captcha.load(); }, - sync: function(captchas) { - QR.captcha.captchas = captchas; - return QR.captcha.count(); + destroy: function() { + $.globalEval('Recaptcha.destroy()'); + return this.beforeSetup(); }, getOne: function() { - var captcha, challenge, response; - this.clear(); - if (captcha = this.captchas.shift()) { - challenge = captcha.challenge, response = captcha.response; - this.count(); - $.set('captchas', this.captchas); - } else { - challenge = this.nodes.img.alt; - if (response = this.nodes.input.value) { - this.reload(); - } - } - if (response) { - response = response.trim(); - if (!/\s/.test(response)) { - response += " " + response; - } + var challenge, response; + challenge = this.nodes.img.alt; + response = this.nodes.input.value.trim(); + if (response && !/\s/.test(response)) { + response = "" + response + " " + response; } return { challenge: challenge, response: response }; }, - save: function() { - var response; - if (!(response = this.nodes.input.value.trim())) { - return; - } - this.captchas.push({ - challenge: this.nodes.img.alt, - response: response, - timeout: this.timeout - }); - this.count(); - this.reload(); - return $.set('captchas', this.captchas); - }, - clear: function() { - var captcha, i, now, _i, _len, _ref; - if (!this.captchas) { - return; - } - now = Date.now(); - _ref = this.captchas; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - captcha = _ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - return $.set('captchas', this.captchas); - }, load: function() { var challenge; if (!this.nodes.challenge.firstChild) { return; } - this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; challenge = this.nodes.challenge.firstChild.value; this.nodes.img.alt = challenge; this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; - this.nodes.input.value = null; - return this.clear(); - }, - count: function() { - var count; - count = this.captchas ? this.captchas.length : 0; - this.nodes.input.placeholder = (function() { - switch (count) { - case 0: - return 'Verification (Shift + Enter to cache)'; - case 1: - return 'Verification (1 cached captcha)'; - default: - return "Verification (" + count + " cached captchas)"; - } - })(); - return this.nodes.input.alt = count; + return this.nodes.input.value = null; }, reload: function(focus) { $.globalEval('Recaptcha.reload("t")'); @@ -9698,8 +9608,6 @@ keydown: function(e) { if (e.keyCode === 8 && !this.nodes.input.value) { this.reload(); - } else if (e.keyCode === 13 && e.shiftKey) { - this.save(); } else { return; } @@ -10031,6 +9939,9 @@ node.disabled = lock; } } + if (QR.captcha.isEnabled) { + QR.captcha.nodes.input.disabled = lock; + } this.nodes.rm.style.visibility = lock ? 'hidden' : ''; (lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput); this.nodes.spoiler.disabled = lock; @@ -16133,18 +16044,18 @@ if (this.isClone) { return; } - return this.nodes.date.textContent = Time.funk(Time, this.info.date); + return this.nodes.date.textContent = Time.funk(this.info.date); }, createFunc: function(format) { var code; code = format.replace(/%([A-Za-z])/g, function(s, c) { if (c in Time.formatters) { - return "' + Time.formatters." + c + ".call(date) + '"; + return "' + this.formatters." + c + ".call(date) + '"; } else { return s; } }); - return Function('Time', 'date', "return '" + code + "'"); + return Function('date', "return '" + code + "'"); }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], diff --git a/builds/crx/script.js b/builds/crx/script.js index 497dafc2e..77b0e2ff1 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* appchan x - Version 2.9.9 - 2014-03-27 +* appchan x - Version 2.9.9 - 2014-04-02 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -8116,6 +8116,9 @@ if (Conf['Comment Expansion']) { ExpandComment.callbacks.push(this.node); } + if (Conf['Embedding'] || Conf['Link Title']) { + this.embedProcess = Function('link', "var data = this.services(link); if (data) { " + ((Conf['Embedding'] ? 'this.embed(data);\n' : '') + (Conf['Title Link'] ? 'this.title(data);' : '')) + " }"); + } return Post.callbacks.push({ name: 'Linkify', cb: this.node @@ -8185,17 +8188,7 @@ Linkify.embedProcess(Linkify.makeLink(link, this)); } }, - embedProcess: function(link) { - var data; - if (data = Linkify.services(link)) { - if (Conf['Embedding']) { - Linkify.embed(data); - } - if (Conf['Link Title']) { - return Linkify.title(data); - } - } - }, + embedProcess: function() {}, regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, makeRange: function(startNode, endNode, startOffset, endOffset) { var range; @@ -9398,6 +9391,10 @@ onload: QR.response, onerror: function() { delete QR.req; + if (QR.captcha.isEnabled) { + QR.captcha.destroy(); + QR.captcha.setup(); + } post.unlock(); QR.cooldown.auto = false; QR.status(); @@ -9427,9 +9424,12 @@ return QR.status(); }, response: function() { - var URL, ban, board, captchasCount, err, h1, isReply, m, notif, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; + var URL, ban, board, err, h1, isReply, m, post, postID, postsCount, req, resDoc, threadID, _, _ref, _ref1; req = QR.req; delete QR.req; + if (QR.captcha.isEnabled) { + QR.captcha.destroy(); + } post = QR.posts[0]; post.unlock(); resDoc = req.response; @@ -9451,15 +9451,13 @@ if (/captcha|verification/i.test(err.textContent) || err === 'Connection error with sys.4chan.org.') { if (/mistyped/i.test(err.textContent)) { err = 'You seem to have mistyped the CAPTCHA.'; - } else if (/expired/i.test(err.textContent)) { - 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 = false; QR.cooldown.set({ delay: 2 }); } 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.cooldown.set({ delay: m[1] }); @@ -9498,22 +9496,6 @@ }); postsCount = QR.posts.length - 1; QR.cooldown.auto = postsCount && isReply; - if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) { - notif = new Notification('Quick reply warning', { - body: "You are running low on cached captchas. Cache count: " + captchasCount + ".", - icon: Favicon.logo - }); - notif.onclick = function() { - QR.open(); - QR.captcha.nodes.input.focus(); - return window.focus(); - }; - notif.onshow = function() { - return setTimeout(function() { - return notif.close(); - }, 7 * $.SECOND); - }; - } if (!(Conf['Persistent QR'] || QR.cooldown.auto)) { QR.close(); } else { @@ -9569,24 +9551,21 @@ QR.captcha = { init: function() { - var container, imgContainer, input; + var imgContainer, input; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - container = $.id('captchaContainer'); - if (!(this.isEnabled = !!container)) { + if (!(this.isEnabled = !!$.id('captchaContainer'))) { return; } imgContainer = $.el('div', { className: 'captcha-img', title: 'Reload reCAPTCHA', - innerHTML: '
', - hidden: true + innerHTML: '
' }); input = $.el('input', { className: 'captcha-input field', title: 'Verification', - placeholder: 'Focus to load reCAPTCHA', autocomplete: 'off', spellcheck: false, tabIndex: 45 @@ -9595,47 +9574,41 @@ img: imgContainer.firstChild.firstChild, input: input }; - $.on(input, 'focus', this.setup); $.on(input, 'blur', QR.focusout); $.on(input, 'focus', QR.focusin); $.addClass(QR.nodes.el, 'has-captcha'); $.after(QR.nodes.com.parentNode, [imgContainer, input]); + this.beforeSetup(); + return this.afterSetup(); + }, + beforeSetup: function() { + var img, input, _ref; + _ref = this.nodes, img = _ref.img, input = _ref.input; + img.parentNode.parentNode.hidden = true; + input.value = ''; + input.placeholder = 'Focus to load reCAPTCHA'; + $.on(input, 'focus', this.setup); this.setupObserver = new MutationObserver(this.afterSetup); - this.setupObserver.observe(container, { + return this.setupObserver.observe($.id('captchaContainer'), { childList: true }); - if (Conf['Auto-load captcha']) { - this.setup(); - } - return this.afterSetup(); }, setup: function() { return $.globalEval('loadRecaptcha()'); }, afterSetup: function() { - var challenge, img, input, setLifetime, _ref; + var challenge, img, input, _ref; if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { return; } QR.captcha.setupObserver.disconnect(); delete QR.captcha.setupObserver; - setLifetime = function(e) { - return QR.captcha.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); - $.off(window, 'captcha:timeout', setLifetime); _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input; img.parentNode.parentNode.hidden = false; + input.placeholder = 'Verification'; $.off(input, 'focus', QR.captcha.setup); $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); $.on(img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); - $.get('captchas', [], function(_arg) { - var captchas; - captchas = _arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', QR.captcha.sync); QR.captcha.nodes.challenge = challenge; new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { childList: true, @@ -9644,94 +9617,31 @@ }); return QR.captcha.load(); }, - sync: function(captchas) { - QR.captcha.captchas = captchas; - return QR.captcha.count(); + destroy: function() { + $.globalEval('Recaptcha.destroy()'); + return this.beforeSetup(); }, getOne: function() { - var captcha, challenge, response; - this.clear(); - if (captcha = this.captchas.shift()) { - challenge = captcha.challenge, response = captcha.response; - this.count(); - $.set('captchas', this.captchas); - } else { - challenge = this.nodes.img.alt; - if (response = this.nodes.input.value) { - this.reload(); - } - } - if (response) { - response = response.trim(); - if (!/\s/.test(response)) { - response += " " + response; - } + var challenge, response; + challenge = this.nodes.img.alt; + response = this.nodes.input.value.trim(); + if (response && !/\s/.test(response)) { + response = "" + response + " " + response; } return { challenge: challenge, response: response }; }, - save: function() { - var response; - if (!(response = this.nodes.input.value.trim())) { - return; - } - this.captchas.push({ - challenge: this.nodes.img.alt, - response: response, - timeout: this.timeout - }); - this.count(); - this.reload(); - return $.set('captchas', this.captchas); - }, - clear: function() { - var captcha, i, now, _i, _len, _ref; - if (!this.captchas) { - return; - } - now = Date.now(); - _ref = this.captchas; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - captcha = _ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - return $.set('captchas', this.captchas); - }, load: function() { var challenge; if (!this.nodes.challenge.firstChild) { return; } - this.timeout = Date.now() + this.lifetime * $.SECOND - $.MINUTE; challenge = this.nodes.challenge.firstChild.value; this.nodes.img.alt = challenge; this.nodes.img.src = "//www.google.com/recaptcha/api/image?c=" + challenge; - this.nodes.input.value = null; - return this.clear(); - }, - count: function() { - var count; - count = this.captchas ? this.captchas.length : 0; - this.nodes.input.placeholder = (function() { - switch (count) { - case 0: - return 'Verification (Shift + Enter to cache)'; - case 1: - return 'Verification (1 cached captcha)'; - default: - return "Verification (" + count + " cached captchas)"; - } - })(); - return this.nodes.input.alt = count; + return this.nodes.input.value = null; }, reload: function(focus) { $.globalEval('Recaptcha.reload("t")'); @@ -9742,8 +9652,6 @@ keydown: function(e) { if (e.keyCode === 8 && !this.nodes.input.value) { this.reload(); - } else if (e.keyCode === 13 && e.shiftKey) { - this.save(); } else { return; } @@ -10069,6 +9977,9 @@ node.disabled = lock; } } + if (QR.captcha.isEnabled) { + QR.captcha.nodes.input.disabled = lock; + } this.nodes.rm.style.visibility = lock ? 'hidden' : ''; (lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput); this.nodes.spoiler.disabled = lock; @@ -16154,18 +16065,18 @@ if (this.isClone) { return; } - return this.nodes.date.textContent = Time.funk(Time, this.info.date); + return this.nodes.date.textContent = Time.funk(this.info.date); }, createFunc: function(format) { var code; code = format.replace(/%([A-Za-z])/g, function(s, c) { if (c in Time.formatters) { - return "' + Time.formatters." + c + ".call(date) + '"; + return "' + this.formatters." + c + ".call(date) + '"; } else { return s; } }); - return Function('Time', 'date', "return '" + code + "'"); + return Function('date', "return '" + code + "'"); }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], diff --git a/src/Linkification/Linkify.coffee b/src/Linkification/Linkify.coffee index 5ab95ad63..b57a16adc 100755 --- a/src/Linkification/Linkify.coffee +++ b/src/Linkification/Linkify.coffee @@ -5,6 +5,17 @@ Linkify = if Conf['Comment Expansion'] ExpandComment.callbacks.push @node + if Conf['Embedding'] or Conf['Link Title'] + @embedProcess = Function 'link', + "var data = this.services(link); + if (data) { + #{ + (if Conf['Embedding'] then 'this.embed(data);\n' else '') + + if Conf['Title Link'] then 'this.title(data);' else '' + } + } + " + Post.callbacks.push name: 'Linkify' cb: @node @@ -63,10 +74,7 @@ Linkify = Linkify.embedProcess Linkify.makeLink link, @ return - embedProcess: (link) -> - if data = Linkify.services link - Linkify.embed data if Conf['Embedding'] - Linkify.title data if Conf['Link Title'] + embedProcess: -> return regString: ///( # http, magnet, ftp, etc diff --git a/src/Miscellaneous/Time.coffee b/src/Miscellaneous/Time.coffee index 0d290cc63..0f08befa2 100755 --- a/src/Miscellaneous/Time.coffee +++ b/src/Miscellaneous/Time.coffee @@ -8,14 +8,14 @@ Time = cb: @node node: -> return if @isClone - @nodes.date.textContent = Time.funk Time, @info.date + @nodes.date.textContent = Time.funk @info.date createFunc: (format) -> code = format.replace /%([A-Za-z])/g, (s, c) -> if c of Time.formatters - "' + Time.formatters.#{c}.call(date) + '" + "' + this.formatters.#{c}.call(date) + '" else s - Function 'Time', 'date', "return '#{code}'" + Function 'date', "return '#{code}'" day: [ 'Sunday' 'Monday' diff --git a/src/Posting/QR.captcha.coffee b/src/Posting/QR.captcha.coffee index d175a84c1..42173511d 100644 --- a/src/Posting/QR.captcha.coffee +++ b/src/Posting/QR.captcha.coffee @@ -1,19 +1,16 @@ QR.captcha = init: -> return if d.cookie.indexOf('pass_enabled=1') >= 0 - container = $.id 'captchaContainer' - return unless @isEnabled = !!container + return unless @isEnabled = !!$.id 'captchaContainer' imgContainer = $.el 'div', className: 'captcha-img' title: 'Reload reCAPTCHA' innerHTML: '
' - hidden: true input = $.el 'input', className: 'captcha-input field' title: 'Verification' - placeholder: 'Focus to load reCAPTCHA' autocomplete: 'off' spellcheck: false tabIndex: 45 @@ -22,20 +19,25 @@ QR.captcha = img: imgContainer.firstChild.firstChild input: input - $.on input, 'focus', @setup - $.on input, 'blur', QR.focusout $.on input, 'focus', QR.focusin $.addClass QR.nodes.el, 'has-captcha' $.after QR.nodes.com.parentNode, [imgContainer, input] - @setupObserver = new MutationObserver @afterSetup - @setupObserver.observe container, childList: true + @beforeSetup() - @setup() if Conf['Auto-load captcha'] @afterSetup() # reCAPTCHA might have loaded before the QR. + beforeSetup: -> + {img, input} = @nodes + img.parentNode.parentNode.hidden = true + input.value = '' + input.placeholder = 'Focus to load reCAPTCHA' + $.on input, 'focus', @setup + @setupObserver = new MutationObserver @afterSetup + @setupObserver.observe $.id('captchaContainer'), childList: true + setup: -> $.globalEval 'loadRecaptcha()' @@ -44,21 +46,13 @@ QR.captcha = QR.captcha.setupObserver.disconnect() delete QR.captcha.setupObserver - setLifetime = (e) -> QR.captcha.lifetime = e.detail - $.on window, 'captcha:timeout', setLifetime - $.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))' - $.off window, 'captcha:timeout', setLifetime - {img, input} = QR.captcha.nodes img.parentNode.parentNode.hidden = false - $.off input, 'focus', QR.captcha.setup + input.placeholder = 'Verification' + $.off input, 'focus', QR.captcha.setup $.on input, 'keydown', QR.captcha.keydown.bind QR.captcha $.on img.parentNode, 'click', QR.captcha.reload.bind QR.captcha - $.get 'captchas', [], ({captchas}) -> - QR.captcha.sync captchas - $.sync 'captchas', QR.captcha.sync - QR.captcha.nodes.challenge = challenge new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge, childList: true @@ -66,68 +60,27 @@ QR.captcha = attributes: true QR.captcha.load() - sync: (captchas) -> - QR.captcha.captchas = captchas - QR.captcha.count() + destroy: -> + $.globalEval 'Recaptcha.destroy()' + @beforeSetup() getOne: -> - @clear() - if captcha = @captchas.shift() - {challenge, response} = captcha - @count() - $.set 'captchas', @captchas - else - challenge = @nodes.img.alt - if response = @nodes.input.value then @reload() - if response - response = response.trim() + challenge = @nodes.img.alt + response = @nodes.input.value.trim() + if response and !/\s/.test response # one-word-captcha: # If there's only one word, duplicate it. - response += " #{response}" unless /\s/.test response + response = "#{response} #{response}" {challenge, response} - save: -> - return unless response = @nodes.input.value.trim() - @captchas.push - challenge: @nodes.img.alt - response: response - timeout: @timeout - @count() - @reload() - $.set 'captchas', @captchas - - clear: -> - return unless @captchas # not loaded yet. - - now = Date.now() - for captcha, i in @captchas - break if captcha.timeout > now - return unless i - @captchas = @captchas[i..] - @count() - $.set 'captchas', @captchas - load: -> return unless @nodes.challenge.firstChild # -1 minute to give upload some time. - @timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE challenge = @nodes.challenge.firstChild.value @nodes.img.alt = challenge @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.input.value = null - @clear() - count: -> - count = if @captchas then @captchas.length else 0 - @nodes.input.placeholder = switch count - when 0 - 'Verification (Shift + Enter to cache)' - when 1 - 'Verification (1 cached captcha)' - else - "Verification (#{count} cached captchas)" - - @nodes.input.alt = count reload: (focus) -> # the 't' argument prevents the input from being focused @@ -138,8 +91,6 @@ QR.captcha = keydown: (e) -> if e.keyCode is 8 and not @nodes.input.value @reload() - else if e.keyCode is 13 and e.shiftKey - @save() else return e.preventDefault() diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 34dd1f8b7..b33904c7d 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -701,6 +701,9 @@ QR = onerror: -> # Connection error, or www.4chan.org/banned delete QR.req + if QR.captcha.isEnabled + QR.captcha.destroy() + QR.captcha.setup() post.unlock() QR.cooldown.auto = false QR.status() @@ -734,6 +737,7 @@ QR = {req} = QR delete QR.req + QR.captcha.destroy() if QR.captcha.isEnabled post = QR.posts[0] post.unlock() @@ -763,25 +767,12 @@ QR = # Remove the obnoxious 4chan Pass ad. if /mistyped/i.test err.textContent err = 'You seem to have mistyped the CAPTCHA.' - else if /expired/i.test err.textContent - err = 'This CAPTCHA is no longer valid because it has expired.' - # Enable auto-post if we have some cached captchas. - QR.cooldown.auto = if QR.captcha.isEnabled - !!QR.captcha.captchas.length - else if err is 'Connection error with sys.4chan.org.' - true - else - # Something must've gone terribly wrong if you get captcha errors without captchas. - # Don't auto-post indefinitely in that case. - false + QR.cooldown.auto = false # Too many frequent mistyped captchas will auto-ban you! # On connection error, the post most likely didn't go through. QR.cooldown.set delay: 2 else if err.textContent and m = err.textContent.match /wait\s+(\d+)\s+second/i - QR.cooldown.auto = if QR.captcha.isEnabled - !!QR.captcha.captchas.length - else - true + QR.cooldown.auto = !QR.captcha.isEnabled QR.cooldown.set delay: m[1] else # stop auto-posting QR.cooldown.auto = false @@ -821,18 +812,6 @@ QR = # Enable auto-posting if we have stuff left to post, disable it otherwise. postsCount = QR.posts.length - 1 QR.cooldown.auto = postsCount and isReply - if QR.cooldown.auto and QR.captcha.isEnabled and (captchasCount = QR.captcha.captchas.length) < 3 and captchasCount < postsCount - notif = new Notification 'Quick reply warning', - body: "You are running low on cached captchas. Cache count: #{captchasCount}." - icon: Favicon.logo - notif.onclick = -> - QR.open() - QR.captcha.nodes.input.focus() - window.focus() - notif.onshow = -> - setTimeout -> - notif.close() - , 7 * $.SECOND unless Conf['Persistent QR'] or QR.cooldown.auto QR.close() diff --git a/src/Posting/QR.post.coffee b/src/Posting/QR.post.coffee index b3c79a379..089503b9e 100644 --- a/src/Posting/QR.post.coffee +++ b/src/Posting/QR.post.coffee @@ -91,6 +91,8 @@ QR.post = class return unless @ is QR.selected for name in ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'] when node = QR.nodes[name] node.disabled = lock + if QR.captcha.isEnabled + QR.captcha.nodes.input.disabled = lock @nodes.rm.style.visibility = if lock then 'hidden' else '' (if lock then $.off else $.on) QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput @nodes.spoiler.disabled = lock