684 lines
17 KiB
JavaScript
684 lines
17 KiB
JavaScript
var TCaptcha = {
|
|
node: null,
|
|
|
|
frameNode: null,
|
|
imgCntNode: null,
|
|
bgNode: null,
|
|
fgNode: null,
|
|
msgNode: null,
|
|
sliderNode: null,
|
|
respNode: null,
|
|
reloadNode: null,
|
|
helpNode: null,
|
|
challengeNode: null,
|
|
|
|
ticketCaptchaNode: null,
|
|
|
|
challenge: null,
|
|
|
|
reloadTs: null,
|
|
reloadTimeout: null,
|
|
expireTimeout: null,
|
|
frameTimeout: null,
|
|
|
|
pcdBypassable: false,
|
|
|
|
errorCb: null,
|
|
|
|
path: '/captcha',
|
|
|
|
ticketKey: '4chan-tc-ticket',
|
|
|
|
domain: '4chan.org',
|
|
|
|
failCd: 60,
|
|
|
|
tabindex: null,
|
|
|
|
hCaptchaSiteKey: '49d294fa-f15c-41fc-80ba-c2544c52ec2a',
|
|
|
|
init: function(el, board, thread_id, tabindex) {
|
|
if (this.node) {
|
|
this.destroy();
|
|
}
|
|
|
|
if (tabindex) {
|
|
this.tabindex = tabindex;
|
|
}
|
|
|
|
this.node = el;
|
|
|
|
el.style.position = 'relative';
|
|
el.style.width = '300px';
|
|
|
|
this.frameNode = null;
|
|
this.imgCntNode = this.buildImgCntNode();
|
|
this.bgNode = this.buildImgNode('bg');
|
|
this.fgNode = this.buildImgNode('fg');
|
|
this.sliderNode = this.buildSliderNode();
|
|
|
|
this.respNode = this.buildRespField();
|
|
this.reloadNode = this.buildReloadNode(board, thread_id);
|
|
this.helpNode = this.buildHelpNode();
|
|
this.msgNode = this.buildMsgNode();
|
|
this.challengeNode = this.buildChallengeNode();
|
|
|
|
el.appendChild(this.reloadNode);
|
|
el.appendChild(this.respNode);
|
|
el.appendChild(this.helpNode);
|
|
|
|
this.imgCntNode.appendChild(this.bgNode);
|
|
this.imgCntNode.appendChild(this.fgNode);
|
|
el.appendChild(this.imgCntNode);
|
|
|
|
el.appendChild(this.sliderNode);
|
|
el.appendChild(this.msgNode);
|
|
el.appendChild(this.challengeNode);
|
|
|
|
window.addEventListener('message', this.onFrameMessage);
|
|
},
|
|
|
|
destroy: function() {
|
|
let self = TCaptcha;
|
|
|
|
if (!self.node) {
|
|
return;
|
|
}
|
|
|
|
window.removeEventListener('message', self.onFrameMessage);
|
|
|
|
clearTimeout(self.frameTimeout);
|
|
clearTimeout(self.reloadTimeout);
|
|
clearTimeout(self.expireTimeout);
|
|
|
|
self.node.textContent = '';
|
|
|
|
self.node = null;
|
|
self.frameNode = null;
|
|
self.imgCntNode = null;
|
|
self.bgNode = null;
|
|
self.fgNode = null;
|
|
self.msgNode = null;
|
|
self.sliderNode = null;
|
|
self.respNode = null;
|
|
self.reloadNode = null;
|
|
self.helpNode = null;
|
|
self.challengeNode = null;
|
|
|
|
self.ticketCaptchaNode = null;
|
|
|
|
self.challenge = null;
|
|
|
|
self.pcdBypassable = false;
|
|
|
|
self.errorCb = null;
|
|
|
|
self.reloadTs = null;
|
|
|
|
self.onReloadCdDone = null;
|
|
},
|
|
|
|
setErrorCb: function(func) {
|
|
TCaptcha.errorCb = func;
|
|
},
|
|
|
|
toggleImgCntNode: function(flag) {
|
|
TCaptcha.imgCntNode.style.display = flag ? 'block' : 'none';
|
|
},
|
|
|
|
getTicket: function() {
|
|
return localStorage.getItem(TCaptcha.ticketKey);
|
|
},
|
|
|
|
setTicket: function(val) {
|
|
if (val) {
|
|
localStorage.setItem(TCaptcha.ticketKey, val);
|
|
}
|
|
else if (val === false) {
|
|
localStorage.removeItem(TCaptcha.ticketKey);
|
|
}
|
|
},
|
|
|
|
buildFrameNode: function() {
|
|
let el = document.createElement('iframe');
|
|
el.id = 't-frame';
|
|
el.style.border = '0';
|
|
el.style.width = '100%';
|
|
el.style.height = '80px';
|
|
el.style.marginTop = '2px';
|
|
el.style.position = 'relative';
|
|
el.style.display = 'block';
|
|
el.onerror = TCaptcha.onFrameError;
|
|
return el;
|
|
},
|
|
|
|
buildImgCntNode: function() {
|
|
let el = document.createElement('div');
|
|
el.id = 't-cnt';
|
|
el.style.height = '80px';
|
|
el.style.marginTop = '2px';
|
|
el.style.position = 'relative';
|
|
return el;
|
|
},
|
|
|
|
buildImgNode: function(id) {
|
|
let el = document.createElement('div');
|
|
el.id = 't-' + id;
|
|
el.style.width = '100%';
|
|
el.style.height = '100%';
|
|
el.style.position = 'absolute';
|
|
el.style.backgroundRepeat = 'no-repeat';
|
|
el.style.backgroundPosition = 'top left';
|
|
el.style.pointerEvents = 'none';
|
|
return el;
|
|
},
|
|
|
|
buildMsgNode: function() {
|
|
let el = document.createElement('div');
|
|
el.id = 't-msg';
|
|
el.style.width = '100%';
|
|
el.style.height = 'calc(100% - 20px)';
|
|
el.style.position = 'absolute';
|
|
el.style.top = '20px';
|
|
el.style.textAlign = 'center';
|
|
el.style.fontSize = '12px';
|
|
el.style.filter = 'inherit';
|
|
el.style.display = 'none';
|
|
el.style.alignContent = 'center';
|
|
return el;
|
|
},
|
|
|
|
buildRespField: function() {
|
|
let el = document.createElement('input');
|
|
el.id = 't-resp';
|
|
el.name = 't-response';
|
|
el.placeholder = 'Type the CAPTCHA here';
|
|
el.setAttribute('autocomplete', 'off');
|
|
el.type = 'text';
|
|
el.style.width = '160px';
|
|
el.style.boxSizing = 'border-box';
|
|
el.style.textTransform = 'uppercase';
|
|
el.style.fontSize = '11px';
|
|
el.style.height = '18px';
|
|
el.style.margin = '0';
|
|
el.style.padding = '0 2px';
|
|
el.style.fontFamily = 'monospace';
|
|
el.style.verticalAlign = 'middle';
|
|
if (this.tabindex) {
|
|
el.setAttribute('tabindex', this.tabindex + 2);
|
|
}
|
|
return el;
|
|
},
|
|
|
|
buildSliderNode: function() {
|
|
let el = document.createElement('input');
|
|
el.id = 't-slider';
|
|
el.setAttribute('autocomplete', 'off');
|
|
el.type = 'range';
|
|
el.style.width = '100%';
|
|
el.style.boxSizing = 'border-box';
|
|
el.style.visibility = 'hidden';
|
|
el.style.margin = '0';
|
|
el.style.transition = 'box-shadow 15s linear';
|
|
el.style.boxShadow = '0 0 6px 4px #1d8dc4';
|
|
el.style.position = 'relative';
|
|
el.value = 0;
|
|
el.min = 0;
|
|
el.max = 100;
|
|
el.addEventListener('input', this.onSliderInput, false);
|
|
if (this.tabindex) {
|
|
el.setAttribute('tabindex', this.tabindex + 1);
|
|
}
|
|
return el;
|
|
},
|
|
|
|
buildChallengeNode: function() {
|
|
let el = document.createElement('input');
|
|
el.name = 't-challenge';
|
|
el.type = 'hidden';
|
|
return el;
|
|
},
|
|
|
|
buildReloadNode: function(board, thread_id) {
|
|
let el = document.createElement('button');
|
|
el.id = 't-load';
|
|
el.type = 'button';
|
|
el.style.fontSize = '11px';
|
|
el.style.padding = '0';
|
|
el.style.width = '90px';
|
|
el.style.boxSizing = 'border-box';
|
|
el.style.margin = '0 6px 0 0';
|
|
el.style.verticalAlign = 'middle';
|
|
el.style.height = '18px';
|
|
el.textContent = 'Get Captcha';
|
|
el.setAttribute('data-board', board);
|
|
el.setAttribute('data-tid', thread_id);
|
|
el.addEventListener('click', this.onReloadClick, false);
|
|
if (this.tabindex) {
|
|
el.setAttribute('tabindex', this.tabindex);
|
|
}
|
|
return el;
|
|
},
|
|
|
|
buildHelpNode: function() {
|
|
let el = document.createElement('button');
|
|
el.id = 't-help';
|
|
el.type = 'button';
|
|
el.style.fontSize = '11px';
|
|
el.style.padding = '0';
|
|
el.style.width = '20px';
|
|
el.style.boxSizing = 'border-box';
|
|
el.style.margin = '0 0 0 6px';
|
|
el.style.verticalAlign = 'middle';
|
|
el.style.height = '18px';
|
|
el.textContent = '?';
|
|
el.setAttribute('data-tip', 'Help');
|
|
el.tabIndex = -1;
|
|
el.addEventListener('click', this.onHelpClick, false);
|
|
return el;
|
|
},
|
|
|
|
onHelpClick: function() {
|
|
let str = `- Only type letters and numbers displayed in the image.
|
|
- If needed, use the slider to align the image to make it readable.
|
|
- Make sure to not block any cookies set by 4chan.`;
|
|
alert(str);
|
|
},
|
|
|
|
onTicketCaptchaError: function() {
|
|
TCaptcha.toggleMsgOverlay(true, "Couldn't load the captcha.<br><br>Please check your browser's content blocker.");
|
|
},
|
|
|
|
onTicketCaptchaDone: function(resp) {
|
|
TCaptcha.reloadNode.setAttribute('data-ticket-resp', resp);
|
|
TCaptcha.destroyTicketCaptcha();
|
|
TCaptcha.onReloadClick();
|
|
},
|
|
|
|
loadTicketCaptcha: function() {
|
|
window.pcd_c_loaded = TCaptcha.buildTicketCaptcha;
|
|
window.pcd_c_done = TCaptcha.onTicketCaptchaDone;
|
|
TCaptcha.toggleMsgOverlay(true, 'Loading…');
|
|
let s = document.createElement('script');
|
|
s.src = 'https://js.hcaptcha.com/1/api.js?onload=pcd_c_loaded&render=explicit&recaptchacompat=off';
|
|
s.onerror = TCaptcha.onTicketCaptchaError;
|
|
document.head.appendChild(s);
|
|
},
|
|
|
|
buildTicketCaptcha: function() {
|
|
let self = TCaptcha;
|
|
|
|
self.toggleMsgOverlay(false);
|
|
|
|
if (!window.hcaptcha) {
|
|
self.loadTicketCaptcha();
|
|
return;
|
|
}
|
|
|
|
let el = document.createElement('div');
|
|
el.id = 't-tc-cnt';
|
|
self.imgCntNode.appendChild(el);
|
|
|
|
let wid = window.hcaptcha.render('t-tc-cnt', {
|
|
sitekey: self.hCaptchaSiteKey,
|
|
callback: 'pcd_c_done'
|
|
});
|
|
|
|
el.setAttribute('data-wid', wid);
|
|
|
|
self.ticketCaptchaNode = el;
|
|
},
|
|
|
|
destroyTicketCaptcha: function() {
|
|
let self = TCaptcha;
|
|
|
|
if (!window.hcaptcha || !self.ticketCaptchaNode) {
|
|
return;
|
|
}
|
|
|
|
let wid = self.ticketCaptchaNode.getAttribute('data-wid');
|
|
window.hcaptcha.reset(wid);
|
|
self.imgCntNode.removeChild(self.ticketCaptchaNode);
|
|
self.ticketCaptchaNode = null;
|
|
},
|
|
|
|
onReloadClick: function() {
|
|
let btn = TCaptcha.reloadNode;
|
|
let board = btn.getAttribute('data-board');
|
|
let thread_id = btn.getAttribute('data-tid');
|
|
let ticket_resp = btn.getAttribute('data-ticket-resp');
|
|
btn.removeAttribute('data-ticket-resp');
|
|
TCaptcha.toggleReloadBtn(false, 'Loading');
|
|
TCaptcha.load(board, thread_id, ticket_resp);
|
|
},
|
|
|
|
onFrameMessage: function(e) {
|
|
if (e.origin !== `https://sys.${TCaptcha.domain}`) {
|
|
return;
|
|
}
|
|
|
|
if (e.data && e.data.twister) {
|
|
TCaptcha.destroyFrame();
|
|
TCaptcha.buildFromJson(e.data.twister);
|
|
}
|
|
},
|
|
|
|
onFrameError: function(e) {
|
|
TCaptcha.unlockReloadBtn();
|
|
|
|
console.log(e);
|
|
|
|
if (TCaptcha.errorCb) {
|
|
TCaptcha.errorCb.call(null,
|
|
"Couldn't load the captcha frame. Check your content blocker settings."
|
|
);
|
|
}
|
|
},
|
|
|
|
load: function(board, thread_id, ticket_resp) {
|
|
let self = TCaptcha;
|
|
|
|
clearTimeout(self.frameTimeout);
|
|
clearTimeout(self.reloadTimeout);
|
|
clearTimeout(self.expireTimeout);
|
|
|
|
let params = ['framed=1'];
|
|
|
|
if (board) {
|
|
params.push('board=' + board);
|
|
}
|
|
|
|
if (thread_id > 0) {
|
|
params.push('thread_id=' + thread_id);
|
|
}
|
|
|
|
if (ticket_resp) {
|
|
params.push('ticket_resp=' + encodeURIComponent(ticket_resp));
|
|
}
|
|
|
|
let ticket = self.getTicket();
|
|
|
|
if (ticket) {
|
|
params.push('ticket=' + ticket);
|
|
}
|
|
|
|
if (params.length > 0) {
|
|
params = '?' + params.join('&');
|
|
}
|
|
|
|
let src = 'https://sys.' + self.domain + self.path + params;
|
|
|
|
self.frameNode = self.buildFrameNode();
|
|
self.toggleImgCntNode(false);
|
|
self.node.insertBefore(self.frameNode, self.imgCntNode);
|
|
self.frameTimeout = setTimeout(self.onFrameTimeout, 60000, src);
|
|
self.frameNode.src = src;
|
|
},
|
|
|
|
onFrameTimeout: function(src) {
|
|
let self = TCaptcha;
|
|
|
|
self.destroyFrame();
|
|
|
|
console.log('Captcha frame timeout');
|
|
|
|
if (self.errorCb) {
|
|
self.errorCb.call(null, `Couldn't get the captcha.
|
|
Make sure your browser doesn't block content on 4chan then click
|
|
<a href="${src.replace('framed=1', 'opened=1')}" target="_blank">here</a>.`);
|
|
}
|
|
},
|
|
|
|
destroyFrame: function() {
|
|
let self = TCaptcha;
|
|
|
|
clearTimeout(self.frameTimeout);
|
|
self.frameTimeout = null;
|
|
if (self.frameNode) {
|
|
self.frameNode.remove();
|
|
self.frameNode = null;
|
|
}
|
|
self.toggleImgCntNode(true);
|
|
self.unlockReloadBtn();
|
|
},
|
|
|
|
unlockReloadBtn: function() {
|
|
TCaptcha.reloadTs = null;
|
|
TCaptcha.toggleReloadBtn(true, 'Get Captcha');
|
|
},
|
|
|
|
toggleReloadBtn: function(flag, label) {
|
|
let self = TCaptcha;
|
|
|
|
if (self.reloadNode) {
|
|
self.reloadNode.disabled = !flag;
|
|
|
|
if (label !== undefined) {
|
|
self.reloadNode.textContent = label;
|
|
}
|
|
}
|
|
},
|
|
|
|
onCaptchaFailed: function() {
|
|
let self = TCaptcha;
|
|
|
|
let cd = self.failCd * 1000;
|
|
|
|
if (self.reloadTs && self.reloadTs < cd) {
|
|
self.setReloadCd(cd, true);
|
|
}
|
|
},
|
|
|
|
setReloadCd: function(cd, visible, onDone) {
|
|
let self = TCaptcha;
|
|
|
|
if (!self.node) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(self.reloadTimeout);
|
|
|
|
self.onReloadCdDone = onDone;
|
|
|
|
self.pcdBypassable = visible === -1;
|
|
|
|
if (cd) {
|
|
self.toggleReloadBtn(false);
|
|
if (visible) {
|
|
self.reloadTs = Date.now() + cd;
|
|
self.onReloadCdTick();
|
|
}
|
|
else {
|
|
self.reloadTimeout = setTimeout(self.stopReloadCd, cd);
|
|
}
|
|
}
|
|
else {
|
|
self.stopReloadCd();
|
|
}
|
|
},
|
|
|
|
stopReloadCd: function() {
|
|
let self = TCaptcha;
|
|
self.unlockReloadBtn();
|
|
if (self.onReloadCdDone) {
|
|
self.onReloadCdDone.call(self);
|
|
}
|
|
},
|
|
|
|
onReloadCdTick: function() {
|
|
let self = TCaptcha;
|
|
|
|
if (!self.reloadNode || !self.reloadTs) {
|
|
return;
|
|
}
|
|
|
|
let cd = self.reloadTs - Date.now();
|
|
|
|
if (self.pcdBypassable) {
|
|
if (document.cookie.indexOf('_ev1=') !== -1) {
|
|
cd = 0;
|
|
}
|
|
}
|
|
|
|
if (cd > 0) {
|
|
self.reloadNode.textContent = Math.ceil(cd / 1000);
|
|
self.reloadTimeout = setTimeout(self.onReloadCdTick, Math.min(cd, 1000));
|
|
}
|
|
else {
|
|
self.stopReloadCd();
|
|
}
|
|
},
|
|
|
|
clearChallenge: function() {
|
|
let self = TCaptcha;
|
|
|
|
if (self.node) {
|
|
self.challengeNode.value = '';
|
|
self.respNode.value = '';
|
|
self.fgNode.style.backgroundImage = '';
|
|
self.bgNode.style.backgroundImage = '';
|
|
self.toggleSlider(false);
|
|
self.toggleMsgOverlay(false);
|
|
}
|
|
},
|
|
|
|
toggleSlider: function(flag) {
|
|
TCaptcha.sliderNode.style.visibility = flag ? '' : 'hidden';
|
|
TCaptcha.sliderNode.style.boxShadow = flag ? '' : '0 0 4px 2px #1d8dc4';
|
|
},
|
|
|
|
toggleMsgOverlay: function(flag, txt) {
|
|
if (txt !== undefined) {
|
|
TCaptcha.msgNode.innerHTML = `<div>${txt}</div>`;
|
|
}
|
|
TCaptcha.msgNode.style.display = flag ? 'grid' : 'none';
|
|
},
|
|
|
|
onSliderInput: function() {
|
|
var m = -Math.floor((+this.value) / 100 * this.twisterDelta);
|
|
TCaptcha.bgNode.style.backgroundPositionX = m + 'px';
|
|
},
|
|
|
|
onTicketPcdTick: function() {
|
|
let self = TCaptcha;
|
|
|
|
let el = document.getElementById('t-pcd');
|
|
|
|
if (!el) {
|
|
return;
|
|
}
|
|
|
|
let pcd = +el.getAttribute('data-pcd');
|
|
|
|
pcd = pcd - (0 | (Date.now() / 1000));
|
|
|
|
if (pcd <= 0) {
|
|
self.onTicketPcdEnd();
|
|
return;
|
|
}
|
|
|
|
el.textContent = pcd;
|
|
|
|
setTimeout(self.updateTicketPcd, 1000);
|
|
},
|
|
|
|
clearTicketOverlay: function() {
|
|
TCaptcha.toggleMsgOverlay(false);
|
|
},
|
|
|
|
buildFromJson: function(data) {
|
|
let self = TCaptcha;
|
|
|
|
if (!self.node) {
|
|
return;
|
|
}
|
|
|
|
self.unlockReloadBtn();
|
|
self.toggleSlider(false);
|
|
self.toggleMsgOverlay(false);
|
|
|
|
self.setTicket(data.ticket);
|
|
|
|
if (TCaptcha.errorCb) {
|
|
TCaptcha.errorCb.call(null, '');
|
|
}
|
|
|
|
if (data.cd) {
|
|
self.setReloadCd(data.cd * 1000, !data.challenge);
|
|
}
|
|
|
|
if (data.mpcd) {
|
|
self.clearChallenge();
|
|
self.destroyTicketCaptcha();
|
|
self.buildTicketCaptcha();
|
|
return;
|
|
}
|
|
|
|
if (data.pcd) {
|
|
self.buildTicket(data);
|
|
return;
|
|
}
|
|
|
|
if (data.error) {
|
|
console.log(data.error);
|
|
|
|
if (TCaptcha.errorCb) {
|
|
TCaptcha.errorCb.call(null, data.error);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
self.imgCntNode.style.width = data.img_width + 'px';
|
|
self.imgCntNode.style.height = data.img_height + 'px';
|
|
|
|
self.challengeNode.value = data.challenge;
|
|
|
|
self.expireTimeout = setTimeout(self.clearChallenge, data.ttl * 1000 - 3000);
|
|
|
|
if (data.bg_width) {
|
|
self.buildTwister(data);
|
|
}
|
|
else if (data.img) {
|
|
self.buildStatic(data);
|
|
}
|
|
else {
|
|
self.buildNoop(data);
|
|
}
|
|
},
|
|
|
|
buildTwister: function(data) {
|
|
let self = TCaptcha;
|
|
|
|
self.fgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.img + ')';
|
|
self.bgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.bg + ')';
|
|
|
|
self.bgNode.style.backgroundPositionX = '0px';
|
|
|
|
self.toggleSlider(true);
|
|
self.sliderNode.value = 0;
|
|
self.sliderNode.twisterDelta = data.bg_width - data.img_width;
|
|
self.sliderNode.focus();
|
|
},
|
|
|
|
buildStatic: function(data) {
|
|
let self = TCaptcha;
|
|
self.fgNode.style.backgroundImage = 'url(data:image/png;base64,' + data.img + ')';
|
|
self.bgNode.style.backgroundImage = '';
|
|
},
|
|
|
|
buildTicket: function(data) {
|
|
let self = TCaptcha;
|
|
self.toggleMsgOverlay(true, data.pcd_msg || 'Please wait a while.');
|
|
self.fgNode.style.backgroundImage = '';
|
|
self.bgNode.style.backgroundImage = '';
|
|
self.setReloadCd(data.pcd * 1000, data.bpcd ? -1 : true, self.clearTicketOverlay);
|
|
},
|
|
|
|
buildNoop: function(data) {
|
|
let self = TCaptcha;
|
|
self.toggleMsgOverlay(true, 'Verification not required.');
|
|
self.fgNode.style.backgroundImage = '';
|
|
self.bgNode.style.backgroundImage = '';
|
|
}
|
|
};
|