sigmawall/index.mjs
2026-10-14 12:12:59 +05:00

400 lines
11 KiB
JavaScript

import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { stream, streamText, streamSSE } from 'hono/streaming'
import {
deleteCookie,
getCookie,
setCookie,
} from 'hono/cookie'
import * as fs from 'node:fs'
import * as fsPromises from 'node:fs/promises'
var background;
var dirname = import.meta.dirname ?? new URL('.', import.meta.url).pathname;
var auths = new Map();
var auths_dirty = false;
try {
for (let [k,v] of Object.entries(JSON.parse(fs.readFileSync(dirname+'/cookies.json')))) {
auths.set(k, v);
}
auths_dirty = true;
} catch {}
async function saveCache() {
if (!auths_dirty) return;
glog("Saving cookies");
await fsPromises.writeFile(dirname+'/cookies.json', JSON.stringify(Object.fromEntries(
[...auths.entries()].filter(([_,v]) => !v.prepping)
)));
auths_dirty = false;
}
glog('sigmawall', dirname)
for (let [key, mime] of [
['background.png', 'image/png'],
['background.webp', 'image/webp'],
['background.jpeg', 'image/jpeg'],
['background.jpg', 'image/jpeg'],
['background.gif', 'image/gif'],
]) {
try {
var path = dirname + '/' + key;
var mtime = Math.floor(fs.statSync(dirname + '/' + key).mtimeMs)
var data = fs.readFileSync(path);
var route = `/_sigmawall/${key}`;
var url = `${route}?t=${mtime}`;
background = {
data, route, url, mime
};
glog("Background image loaded", background);
break;
} catch {}
}
function randstr() {
return [...crypto.getRandomValues(new Uint8Array(32))].map(e=>e.toString(16).padStart(2,0)).join('');
}
var app = Object.assign(new Hono(), {
port: 15345
});
function glog(...msg) {
console.log(`[${(new Date()).toGMTString()}]`, ...msg);
}
function log(key, ...msg) {
glog(`[${(key??'?'.repeat(64)).substr(0,8)}]`, ...msg)
}
app.all("*", async (c, next) => {
var key;
keyValid(getCookie(c, '_sigmawall_token'));
log(getCookie(c, '_sigmawall_token'), 'Checking req', auths.get(getCookie(c, '_sigmawall_token')));
if ((key = auths.get(getCookie(c, '_sigmawall_token'))) && key.status) {
keyValid(getCookie(c, '_sigmawall_token'), 10000);
c.status(421);
return c.text("Success");
}
log(getCookie(c, '_sigmawall_token'), 'Verification required');
if (c.req.method != "GET") {
log(getCookie(c, '_sigmawall_token'), 'Non-GET denied');
c.status(403);
return c.text("Unauthorized");
}
return next();
});
if (background) {
app.get(background.route, async (c) => {
c.header('Content-Type', background.mime);
c.header('Cache-Control', 'public, max-age=604800, immutable');
c.header('Expires', (new Date(Date.now()+604800000).toGMTString()));
return c.body(background.data);
});
}
var templ = str => `<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width" />
<style>
html {
display:flex;
align-items: center;
justify-content: center;
min-height:100%;
color: #0f0;
background-color: #000;
text-shadow: #000 0.07em 0.07em 0px;
font-size: min(2.8vw, 2em);${background?`
background-image: url("${background.url}");
background-position: center;
background-size: cover;`:''}
}
a {
color: #0ff;
cursor: pointer;
text-decoration: underline;
}
label:has(:checked) > a {
color: #f0f;
}
@media (width > 0) { .what-the-sigma {
display:block!important;
}}
h1 {
margin:0
}
.proc { color:#888; }
.thing:has(.kill) { display:none; }
</style>
</head>
<span style="color: red">
<iframe style="position: absolute; opacity: 0; width:0; height:0;" name=out id=out></iframe>
<h1>SIGMAWALL</h1>
<small>anti-scraper protection</small></span>
${str}`;
var funcs = new Map();
app.get("/favicon.ico", async (c) => {
c.status(404);
return c.text('no favicon');
});
app.get("/_sigmawall/:key", async (c) => {
let fn = funcs.get(c.req.param('key'));
if (fn) {
try {
await fn(c);
} catch(e) {
console.error(e);
}
c.status(200);
return c.text('if you see this, Go Back');
}
c.status(404);
return c.text("wack");
});
function newfunc(fn) {
var key = randstr();
var obj;
funcs.set(key, (...args) => obj.fn(...args));
return obj = {
fn,
remove() {
funcs.delete(key);
},
key
};
}
function keyValid(key, extend) {
var auth;
if (!(auth = auths.get(key)))
return false;
if (Date.now() >= auth.expire) {
if (!auth.prepping) {
auths_dirty = true;
}
expsig(key);
auths.delete(key);
return false;
}
if (extend != null) {
if (auth.real_expire == null && Date.now()+extend >= auth.expire) {
auth.real_expire = auth.expire;
auth.expire = Date.now()+extend;
}
}
return true;
}
function on_expire(key) {
var auth;
if (!(auth = auths.get(key)))
return false;
auth.expsigs = auth.expsigs ?? new Set();
var fn;
return Object.assign(new Promise(succ => auth.expsigs.add(fn = succ)), {
remove() {
auth.expsigs.delete(fn);
}
});
}
function expsig(key) {
var auth;
if (!(auth = auths.get(key)))
return false;
for (let fn of (auth.expsigs ?? new Set())) {
fn(); auth.expsigs.delete(fn);
}
return true;
}
var conns = 0;
app.get("*", async (c) => {
return streamText(c, async(stream) => { conns++; try {
c.header('X-Accel-Buffering', 'no');
c.header('Content-Type', 'text/html; charset=utf-8');
var closed = false;
var banned = false;
var pass;
var key;
var auth = auths.get(key = getCookie(c, '_sigmawall_token'));
function boop() {
var oauth = auths.get(key);
if (oauth && !oauth.prepping)
throw new Error("can't boop non-prep");
auths.set(key, auth = Object.assign(oauth ?? {}, {
prepping: true, expire: Date.now() + 1000*60*5
}));
}
var finp, finf;
function nfinp() {
finp = new Promise((succ) => finf = succ);
}
if (auth && (!auth.prepping)) {
log(key, 'Rejected due to ban');
await stream.write(templ(`<p>Sorry but you pressed the get banned button return in ${(auth.expire - Date.now())/1000} seconds.`));
return;
} else if (!auth) {
key = randstr()
log(getCookie(c, '_sigmawall_token'), 'Setting cookie', key);
setCookie(c, '_sigmawall_token', key);
c.req.raw.signal.addEventListener('abort', () => {
running = false;
closed = true;
stream.abort();
});
boop();
(o => (o.users = 0, o.expire = Date.now() + 30*1000))(auths.get(key));
if (!closed)
await stream.write(templ(
`<meta http-equiv="refresh" content="0">`+
`<div><p>Page should refresh automatically, if not refresh manually.` +
`<p>Please note that sigmawall requires HTML streaming support to function, which is known to be present in the following browsers: <ul>` +
`<li>Firefox</li>`+
`<li>Chromium</li>`+
`<li>Safari/WebKit</li>`+
`<li>links</li>`+
`</ul><p>The following browsers are known not to support HTML streaming:<ul>`+
`<li>lynx</li>`+
`<li>w3m</li>`+
`<li>eww (emacs web wowser)</li>`+
`</ul><p>If you are using one of those browsers, consider links instead.</div>`
));
log(key, 'Cookie set complete');
return;
}
pass = -2;
if ((o => o.users)(auths.get(key))) {
await stream.write(templ(`<p>Verification ongoing in another window. Waiting...<meta http-equiv="refresh" content="5">`));
return;
} else {
(o => o.users = (o.users ?? 0) + 1)(auths.get(key));
}
boop();
nfinp();
c.req.raw.signal.addEventListener('abort', () => {
log(key, 'Aborted');
closed = true;
finf();
if (keyValid(key) && (o => o.prepping && !--o.users)(auths.get(key))) {
log(key, 'Deleted');
auths.delete(key);
}
stream.abort();
});
var banner = newfunc(function() {
banned = true;
finf();
log(key, 'Foolish user banned');
auths_dirty = true;
expsig(key);
auths.set(key, { expire: Date.now()+1000*30, status: false });
if (verb)
verb.remove();
this.remove();
banner = null;
});
await stream.write(templ(`
<div class="thing"><p><a href="/_sigmawall/${banner.key}" target=out>click here if you want to get banned</a> for 30 seconds.
<p>otherwise, do as instructed. Trust the process<br><div class="lastonly">
`));
await stream.write(`<div style="display:none; position:absolute; font-size: 10000em; color: #00000000; text-shadow: unset; transform: scale(0,0)" aria-hidden="true" class="what-the-sigma" disabled><br>Please ignore the following text: { ${`<span class="wtf">dummy content for webkit </span>`.repeat(64)} }<br></div>`);
var lastonly_i = 0;
async function lastonly_b() {
await stream.write(`<style>.lastonly-e-${lastonly_i++} { display:none; }</style>`);
}
await lastonly_b();
async function dumpflush(i) {
await stream.write(`<span style="display:none" class="lastonly-e-${lastonly_i}">${[...Array(i??2048)].map(e=>' ').join('')}</span>`);
}
await dumpflush();
async function fakebusy() {
await lastonly_b();
await stream.write(`<div class="proc lastonly-e-${lastonly_i}">Processing`);
for (let i=0; i<10; i++) {
await stream.sleep(50);
await stream.write('.');
}
await stream.write(`</div>`);
}
await fakebusy();
function fincookie() {
auths.set(key, { expire: Date.now()+1000*60*60*24*3, status: true }); // 3 day
auths_dirty = true;
}
var verb = newfunc(function () {
pass=3;
finf();
fincookie();
log(key, 'Checks passed');
this.remove();
verb = null;
});
var msg = 'Challenge failed. Refresh page.';
var imgl = false;
var imgls = false;
var imge = Date.now() + 1500;
while ((!closed) && keyValid(key) && auths.get(key).prepping) {
var exp;
await Promise.race([
new Promise(succ => setTimeout(succ, Math.min(Math.max(auths.get(key).expire - Date.now(), 50), 5000))),
...((!imgl) ? [(new Promise(succ => setTimeout(succ, Math.max(imge - Date.now(), 0)))).then(_ => imgl=true)] : []),
finp.then(nfinp),
exp = on_expire(key)
]);
if ((imgl && !imgls) && !closed) {
imgls = true;
log(key, 'Serving enter button');
await lastonly_b();
await stream.write(`<center class="lastonly-e-${lastonly_i}"><a href=/_sigmawall/${verb.key} target=out>--&gt; Enter site &lt;--</a></center>`);
}
exp.remove();
if (!closed)
await stream.write(' ');
if (banned) {
msg = 'You are now banned for 30 seconds';
break;
}
if (pass == 3) {
log(key, auths.get(key));
msg = 'You are in. Page should refresh if not do it yourself <meta http-equiv="refresh" content="1">';
break;
}
}
if (banner)
banner.remove();
if (verb)
verb.remove()
if (!closed)
await stream.write('</div><div class=kill></div></div><div><p>'+msg);
} finally { conns--; } });
});
setInterval(async function() {
for (let [k,v] of auths.entries()) {
keyValid(k);
}
await saveCache();
}, 5000)
var lastconns=-1;
setInterval(function() {
let prepauths = new Map([...auths.entries()].filter(e=>e[1].prepping)).size;
let connsc = conns | prepauths | funcs.size;
if (!(connsc == 0 && lastconns == 0))
glog(`${conns} connections.. ${prepauths} prepauths.. ${funcs.size} funcs`);
lastconns = connsc;
}, 5000);
serve(app);