DUNGEONS-30: add access gateway with Telegram and Discord approvals

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
2026-09-05 17:09:53 +05:00
commit b87a8ef70e
30 changed files with 1838 additions and 0 deletions

73
public/app.js Normal file
View File

@@ -0,0 +1,73 @@
const form = document.querySelector('#access-form');
const input = document.querySelector('#access-number');
const button = document.querySelector('#enter-button');
const label = document.querySelector('#button-label');
const status = document.querySelector('#form-status');
const codeLength = Number(form.dataset.codeLength);
let waiting = false;
let cooldown;
function announce(message, success = false) {
status.textContent = message;
status.classList.toggle('success', success);
status.hidden = false;
}
input.addEventListener('input', () => {
input.removeAttribute('aria-invalid');
if (!waiting) status.hidden = true;
});
form.addEventListener('submit', async event => {
event.preventDefault();
if (waiting) return;
const code = input.value.replace(/\s/g, '');
if (!new RegExp(`^\\d{${codeLength}}$`).test(code)) {
input.setAttribute('aria-invalid', 'true');
announce(`Enter the ${codeLength}-digit number from your bot DM.`);
input.focus();
return;
}
waiting = true;
button.disabled = true;
form.setAttribute('aria-busy', 'true');
label.textContent = 'CHECKING YOUR NUMBER…';
status.hidden = true;
let retryAfter = 0;
let approved = false;
try {
const params = new URLSearchParams(location.search);
const next = location.pathname.startsWith('/_access') ? params.get('next') || '/' : location.pathname + location.search + location.hash;
const response = await fetch('/_access/login', {
method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ code, next }), signal: AbortSignal.timeout(15000),
});
const data = await response.json();
if (!response.ok) {
retryAfter = response.status === 429 ? Number(data.retryAfter) || 60 : 0;
if (response.status === 401) input.setAttribute('aria-invalid', 'true');
throw new Error(data.error || 'We could not check your number. Please try again.');
}
approved = true;
input.value = '';
announce('Access granted. Your next chapter is opening…', true);
label.textContent = 'OPENING THE GROUND…';
location.assign(data.next);
} catch (error) {
announce(error.name === 'TimeoutError' || error instanceof TypeError ? 'Connection interrupted. Check your connection and try again.' : error.message);
status.focus();
} finally {
form.removeAttribute('aria-busy');
if (retryAfter > 0) {
const deadline = Date.now() + retryAfter * 1000;
const tick = () => {
const seconds = Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
label.textContent = `TRY AGAIN IN ${seconds}s`;
if (seconds === 0) { clearInterval(cooldown); waiting = false; button.disabled = false; label.textContent = 'ENTER THE ALPHA'; }
};
tick(); cooldown = setInterval(tick, 1000);
} else if (!approved) {
waiting = false; button.disabled = false; label.textContent = 'ENTER THE ALPHA';
}
}
});