mirror of
https://github.com/LalleSX/4chan-XZ.git
synced 2026-01-30 09:48:12 +01:00
513 lines
13 KiB
JavaScript
513 lines
13 KiB
JavaScript
import $ from '../platform/$'
|
|
import CaptchaReplace from './Captcha.replace'
|
|
import CaptchaT from './Captcha.t'
|
|
import meta from '../../package.json'
|
|
import Main from '../main/Main'
|
|
import Keybinds from '../Miscellaneous/Keybinds'
|
|
import $$ from '../platform/$$'
|
|
import QR from './QR'
|
|
import { Conf, d } from '../globals/globals'
|
|
import { MINUTE, SECOND } from '../platform/helpers'
|
|
|
|
const Captcha = {
|
|
Cache: {
|
|
init() {
|
|
$.on(d, 'SaveCaptcha', (e) => {
|
|
return this.saveAPI(e.detail)
|
|
})
|
|
return $.on(d, 'NoCaptcha', (e) => {
|
|
return this.noCaptcha(e.detail)
|
|
})
|
|
},
|
|
|
|
captchas: [],
|
|
|
|
getCount() {
|
|
return this.captchas.length
|
|
},
|
|
|
|
neededRaw() {
|
|
return (
|
|
!(
|
|
this.haveCookie() ||
|
|
this.captchas.length ||
|
|
QR.req ||
|
|
this.submitCB
|
|
) &&
|
|
(QR.posts.length > 1 ||
|
|
Conf['Auto-load captcha'] ||
|
|
!QR.posts[0].isOnlyQuotes() ||
|
|
QR.posts[0].file)
|
|
)
|
|
},
|
|
|
|
needed() {
|
|
return this.neededRaw() && $.event('LoadCaptcha')
|
|
},
|
|
|
|
prerequest() {
|
|
if (!Conf['Prerequest Captcha']) {
|
|
return
|
|
}
|
|
// Post count temporarily off by 1 when called from QR.post.rm, QR.close, or QR.submit
|
|
return $.queueTask(() => {
|
|
if (
|
|
!this.prerequested &&
|
|
this.neededRaw() &&
|
|
!$.event('LoadCaptcha') &&
|
|
!QR.captcha.occupied() &&
|
|
QR.cooldown.seconds <= 60 &&
|
|
QR.selected === QR.posts[QR.posts.length - 1] &&
|
|
!QR.selected.isOnlyQuotes()
|
|
) {
|
|
const isReply = QR.selected.thread !== 'new'
|
|
if (!$.event('RequestCaptcha', { isReply })) {
|
|
this.prerequested = true
|
|
this.submitCB = (captcha) => {
|
|
if (captcha) {
|
|
return this.save(captcha)
|
|
}
|
|
}
|
|
return this.updateCount()
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
haveCookie() {
|
|
return /\b_ct=/.test(d.cookie) && QR.posts[0].thread !== 'new'
|
|
},
|
|
|
|
getOne() {
|
|
let captcha
|
|
delete this.prerequested
|
|
this.clear()
|
|
if ((captcha = this.captchas.shift())) {
|
|
this.count()
|
|
return captcha
|
|
} else {
|
|
return null
|
|
}
|
|
},
|
|
|
|
request(isReply) {
|
|
if (!this.submitCB) {
|
|
if ($.event('RequestCaptcha', { isReply })) {
|
|
return
|
|
}
|
|
}
|
|
return (cb) => {
|
|
this.submitCB = cb
|
|
return this.updateCount()
|
|
}
|
|
},
|
|
|
|
abort() {
|
|
if (this.submitCB) {
|
|
delete this.submitCB
|
|
$.event('AbortCaptcha')
|
|
return this.updateCount()
|
|
}
|
|
},
|
|
|
|
saveAPI(captcha) {
|
|
let cb
|
|
if ((cb = this.submitCB)) {
|
|
delete this.submitCB
|
|
cb(captcha)
|
|
return this.updateCount()
|
|
} else {
|
|
return this.save(captcha)
|
|
}
|
|
},
|
|
|
|
noCaptcha(detail) {
|
|
let cb
|
|
if ((cb = this.submitCB)) {
|
|
if (!this.haveCookie() || detail?.error) {
|
|
QR.error(detail?.error || 'Failed to retrieve captcha.')
|
|
QR.captcha.setup(d.activeElement === QR.nodes.status)
|
|
}
|
|
delete this.submitCB
|
|
cb()
|
|
return this.updateCount()
|
|
}
|
|
},
|
|
|
|
save(captcha) {
|
|
let cb
|
|
if ((cb = this.submitCB)) {
|
|
this.abort()
|
|
cb(captcha)
|
|
return
|
|
}
|
|
this.captchas.push(captcha)
|
|
this.captchas.sort((a, b) => a.timeout - b.timeout)
|
|
return this.count()
|
|
},
|
|
|
|
clear() {
|
|
if (this.captchas.length) {
|
|
let i
|
|
const now = Date.now()
|
|
for (i = 0; i < this.captchas.length; i++) {
|
|
var captcha = this.captchas[i]
|
|
if (captcha.timeout > now) {
|
|
break
|
|
}
|
|
}
|
|
if (i) {
|
|
this.captchas = this.captchas.slice(i)
|
|
return this.count()
|
|
}
|
|
}
|
|
},
|
|
|
|
count() {
|
|
clearTimeout(this.timer)
|
|
if (this.captchas.length) {
|
|
this.timer = setTimeout(
|
|
this.clear.bind(this),
|
|
this.captchas[0].timeout - Date.now(),
|
|
)
|
|
}
|
|
return this.updateCount()
|
|
},
|
|
|
|
updateCount() {
|
|
return $.event('CaptchaCount', this.captchas.length)
|
|
},
|
|
},
|
|
Replace: CaptchaReplace,
|
|
t: CaptchaT,
|
|
v2: {
|
|
lifetime: 2 * MINUTE,
|
|
|
|
init() {
|
|
if (d.cookie.indexOf('pass_enabled=1') >= 0) {
|
|
return
|
|
}
|
|
if (
|
|
!(this.isEnabled =
|
|
!!$('#g-recaptcha, #captcha-forced-noscript') || !$.id('postForm'))
|
|
) {
|
|
return
|
|
}
|
|
|
|
if ((this.noscript = Conf['Force Noscript Captcha'] || !Main.jsEnabled)) {
|
|
$.addClass(QR.nodes.el, 'noscript-captcha')
|
|
}
|
|
|
|
Captcha.cache.init()
|
|
$.on(d, 'CaptchaCount', this.count.bind(this))
|
|
|
|
const root = $.el('div', { className: 'captcha-root' })
|
|
$.extend(root, {
|
|
innerHTML:
|
|
'<div class="captcha-counter"><a href="javascript:;"></a></div>',
|
|
})
|
|
const counter = $('.captcha-counter > a', root)
|
|
this.nodes = { root, counter }
|
|
this.count()
|
|
$.addClass(QR.nodes.el, 'has-captcha', 'captcha-v2')
|
|
$.after(QR.nodes.com.parentNode, root)
|
|
|
|
$.on(counter, 'click', this.toggle.bind(this))
|
|
$.on(counter, 'keydown', (e) => {
|
|
if (Keybinds.keyCode(e) !== 'Space') {
|
|
return
|
|
}
|
|
this.toggle()
|
|
e.preventDefault()
|
|
return e.stopPropagation()
|
|
})
|
|
return $.on(window, 'captcha:success', () => {
|
|
// XXX Greasemonkey 1.x workaround to gain access to GM_* functions.
|
|
return $.queueTask(() => this.save(false))
|
|
})
|
|
},
|
|
|
|
timeouts: {},
|
|
prevNeeded: 0,
|
|
|
|
noscriptURL() {
|
|
let lang
|
|
let url = `https://www.google.com/recaptcha/api/fallback?k=${meta.recaptchaKey}`
|
|
if ((lang = Conf['captchaLanguage'].trim())) {
|
|
url += `&hl=${encodeURIComponent(lang)}`
|
|
}
|
|
return url
|
|
},
|
|
|
|
moreNeeded() {
|
|
// Post count temporarily off by 1 when called from QR.post.rm, QR.close, or QR.submit
|
|
return $.queueTask(() => {
|
|
const needed = Captcha.cache.needed()
|
|
if (needed && !this.prevNeeded) {
|
|
this.setup(QR.cooldown.auto && d.activeElement === QR.nodes.status)
|
|
}
|
|
return (this.prevNeeded = needed)
|
|
})
|
|
},
|
|
|
|
toggle() {
|
|
if (this.nodes.container && !this.timeouts.destroy) {
|
|
return this.destroy()
|
|
} else {
|
|
return this.setup(true, true)
|
|
}
|
|
},
|
|
|
|
setup(focus, force) {
|
|
if (!this.isEnabled || (!Captcha.cache.needed() && !force)) {
|
|
return
|
|
}
|
|
|
|
if (focus) {
|
|
$.addClass(QR.nodes.el, 'focus')
|
|
this.nodes.counter.focus()
|
|
}
|
|
|
|
if (this.timeouts.destroy) {
|
|
clearTimeout(this.timeouts.destroy)
|
|
delete this.timeouts.destroy
|
|
return this.reload()
|
|
}
|
|
|
|
if (this.nodes.container) {
|
|
// XXX https://bugzilla.mozilla.org/show_bug.cgi?id=1226835
|
|
$.queueTask(() => {
|
|
let iframe
|
|
if (
|
|
this.nodes.container &&
|
|
d.activeElement === this.nodes.counter &&
|
|
(iframe = $(
|
|
'iframe[src^="https://www.google.com/recaptcha/"]',
|
|
this.nodes.container,
|
|
))
|
|
) {
|
|
iframe.focus()
|
|
return QR.focus()
|
|
}
|
|
}) // Event handler not fired in Firefox
|
|
return
|
|
}
|
|
|
|
this.nodes.container = $.el('div', { className: 'captcha-container' })
|
|
$.prepend(this.nodes.root, this.nodes.container)
|
|
new MutationObserver(this.afterSetup.bind(this)).observe(
|
|
this.nodes.container,
|
|
{
|
|
childList: true,
|
|
subtree: true,
|
|
},
|
|
)
|
|
|
|
if (this.noscript) {
|
|
return this.setupNoscript()
|
|
} else {
|
|
return this.setupJS()
|
|
}
|
|
},
|
|
|
|
setupNoscript() {
|
|
const iframe = $.el('iframe', {
|
|
id: 'qr-captcha-iframe',
|
|
scrolling: 'no',
|
|
src: this.noscriptURL(),
|
|
})
|
|
const div = $.el('div')
|
|
const textarea = $.el('textarea')
|
|
$.add(div, textarea)
|
|
return $.add(this.nodes.container, [iframe, div])
|
|
},
|
|
|
|
setupJS() {
|
|
return $.global(function () {
|
|
const render = function () {
|
|
const { classList } = document.documentElement
|
|
const container = document.querySelector('#qr .captcha-container')
|
|
return (container.dataset.widgetID = window.grecaptcha.render(
|
|
container,
|
|
{
|
|
sitekey: meta.recaptchaKey,
|
|
theme:
|
|
classList.contains('tomorrow') ||
|
|
classList.contains('spooky') ||
|
|
classList.contains('dark-captcha')
|
|
? 'dark'
|
|
: 'light',
|
|
callback(response) {
|
|
return window.dispatchEvent(
|
|
new CustomEvent('captcha:success', { detail: response }),
|
|
)
|
|
},
|
|
},
|
|
))
|
|
}
|
|
if (window.grecaptcha) {
|
|
return render()
|
|
} else {
|
|
const cbNative = window.onRecaptchaLoaded
|
|
window.onRecaptchaLoaded = function () {
|
|
render()
|
|
return cbNative()
|
|
}
|
|
if (
|
|
!document.head.querySelector(
|
|
'script[src^="https://www.google.com/recaptcha/api.js"]',
|
|
)
|
|
) {
|
|
const script = document.createElement('script')
|
|
script.src =
|
|
'https://www.google.com/recaptcha/api.js?onload=onRecaptchaLoaded&render=explicit'
|
|
return document.head.appendChild(script)
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
afterSetup(mutations) {
|
|
for (var mutation of mutations) {
|
|
for (var node of mutation.addedNodes) {
|
|
var iframe, textarea
|
|
if (
|
|
(iframe = $.x(
|
|
'./descendant-or-self::iframe[starts-with(@src, "https://www.google.com/recaptcha/")]',
|
|
node,
|
|
))
|
|
) {
|
|
this.setupIFrame(iframe)
|
|
}
|
|
if ((textarea = $.x('./descendant-or-self::textarea', node))) {
|
|
this.setupTextArea(textarea)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
setupIFrame(iframe) {
|
|
let needle
|
|
if (!doc.contains(iframe)) {
|
|
return
|
|
}
|
|
Captcha.replace.iframe(iframe)
|
|
$.addClass(QR.nodes.el, 'captcha-open')
|
|
this.fixQRPosition()
|
|
$.on(iframe, 'load', this.fixQRPosition)
|
|
if (d.activeElement === this.nodes.counter) {
|
|
iframe.focus()
|
|
}
|
|
// XXX Make sure scroll on space prevention (see src/css/style.css) doesn't cause scrolling of div
|
|
if (
|
|
['blink', 'edge'].includes($.engine) &&
|
|
((needle = iframe.parentNode),
|
|
$$('#qr .captcha-container > div > div:first-of-type').includes(needle))
|
|
) {
|
|
return $.on(iframe.parentNode, 'scroll', function () {
|
|
return (this.scrollTop = 0)
|
|
})
|
|
}
|
|
},
|
|
|
|
fixQRPosition() {
|
|
if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) {
|
|
QR.nodes.el.style.top = ''
|
|
return (QR.nodes.el.style.bottom = '0px')
|
|
}
|
|
},
|
|
|
|
setupTextArea(textarea) {
|
|
return $.one(textarea, 'input', () => this.save(true))
|
|
},
|
|
|
|
destroy() {
|
|
if (!this.isEnabled) {
|
|
return
|
|
}
|
|
delete this.timeouts.destroy
|
|
$.rmClass(QR.nodes.el, 'captcha-open')
|
|
if (this.nodes.container) {
|
|
$.global(function () {
|
|
const container = document.querySelector('#qr .captcha-container')
|
|
return window.grecaptcha.reset(container.dataset.widgetID)
|
|
})
|
|
$.rm(this.nodes.container)
|
|
return delete this.nodes.container
|
|
}
|
|
},
|
|
|
|
getOne(isReply) {
|
|
return Captcha.cache.getOne(isReply)
|
|
},
|
|
|
|
save(pasted, token) {
|
|
Captcha.cache.save({
|
|
response: token || $('textarea', this.nodes.container).value,
|
|
timeout: Date.now() + this.lifetime,
|
|
})
|
|
|
|
const focus =
|
|
d.activeElement?.nodeName === 'IFRAME' &&
|
|
/https?:\/\/www\.google\.com\/recaptcha\//.test(d.activeElement.src)
|
|
if (Captcha.cache.needed()) {
|
|
if (focus) {
|
|
if (QR.cooldown.auto || Conf['Post on Captcha Completion']) {
|
|
this.nodes.counter.focus()
|
|
} else {
|
|
QR.nodes.status.focus()
|
|
}
|
|
}
|
|
this.reload()
|
|
} else {
|
|
if (pasted) {
|
|
this.destroy()
|
|
} else {
|
|
if (this.timeouts.destroy == null) {
|
|
this.timeouts.destroy = setTimeout(
|
|
this.destroy.bind(this),
|
|
3 * SECOND,
|
|
)
|
|
}
|
|
}
|
|
if (focus) {
|
|
QR.nodes.status.focus()
|
|
}
|
|
}
|
|
|
|
if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) {
|
|
return QR.submit()
|
|
}
|
|
},
|
|
|
|
count() {
|
|
const count = Captcha.cache.getCount()
|
|
const loading = Captcha.cache.submitCB ? '...' : ''
|
|
this.nodes.counter.textContent = `Captchas: ${count}${loading}`
|
|
return this.moreNeeded()
|
|
},
|
|
|
|
reload() {
|
|
if (
|
|
$(
|
|
'iframe[src^="https://www.google.com/recaptcha/api/fallback?"]',
|
|
this.nodes.container,
|
|
)
|
|
) {
|
|
this.destroy()
|
|
return this.setup(false, true)
|
|
} else {
|
|
return $.global(function () {
|
|
const container = document.querySelector('#qr .captcha-container')
|
|
return window.grecaptcha.reset(container.dataset.widgetID)
|
|
})
|
|
}
|
|
},
|
|
|
|
occupied() {
|
|
return !!this.nodes.container && !this.timeouts.destroy
|
|
},
|
|
},
|
|
}
|
|
export default Captcha
|