diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b7bf8d74..9190c64ab 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+- 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.
+
### v1.4.1
*2014-03-01*
diff --git a/src/Posting/QR.captcha.coffee b/src/Posting/QR.captcha.coffee
index 029620f1f..31c48f008 100644
--- a/src/Posting/QR.captcha.coffee
+++ b/src/Posting/QR.captcha.coffee
@@ -1,8 +1,7 @@
QR.captcha =
init: ->
return if d.cookie.indexOf('pass_enabled=1') >= 0
- container = $.id 'captchaContainer'
- return unless @isEnabled = !!container
+ return unless @isEnabled = !!$.id 'captchaContainer'
$.globalEval 'loadRecaptcha()' if Conf['Auto-load captcha']
@@ -10,11 +9,9 @@ QR.captcha =
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,17 +19,22 @@ QR.captcha =
img: imgContainer.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()
@afterSetup() # reCAPTCHA might have loaded before the QR.
+ beforeSetup: ->
+ {img, input} = @nodes
+ img.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()'
afterSetup: ->
@@ -40,89 +42,37 @@ 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.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', [], ({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
subtree: true
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} #{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.length
- 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 # For XTRM RICE.
-
reload: (focus) ->
# the 't' argument prevents the input from being focused
$.globalEval 'Recaptcha.reload("t")'
@@ -132,8 +82,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 72563de1a..de07b690c 100644
--- a/src/Posting/QR.coffee
+++ b/src/Posting/QR.coffee
@@ -663,6 +663,9 @@ QR =
onerror: (err, url, line) ->
# 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()
@@ -699,6 +702,7 @@ QR =
{req} = QR
delete QR.req
+ QR.captcha.destroy() if QR.captcha.isEnabled
post = QR.posts[0]
post.unlock()
@@ -730,23 +734,12 @@ QR =
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
@@ -786,18 +779,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 a6239e57f..c11bab53e 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