347 lines
9.1 KiB
JavaScript
347 lines
9.1 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 port = parseInt(process.argv[2]) || 15345;
|
|
var prefix = process.argv[3] || "/84/_verify";
|
|
var cookie_name = process.argv[4] || "_cgwall_token";
|
|
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)
|
|
|
|
function randstr() {
|
|
return [...crypto.getRandomValues(new Uint8Array(32))].map(e=>e.toString(16).padStart(2,0)).join('');
|
|
}
|
|
|
|
var app = Object.assign(new Hono(), {
|
|
port
|
|
});
|
|
|
|
function glog(...msg) {
|
|
console.log(`[${(new Date()).toGMTString()}]`, ...msg);
|
|
}
|
|
|
|
function log(key, ...msg) {
|
|
glog(`[${(key??'?'.repeat(64)).substr(0,8)}]`, ...msg)
|
|
}
|
|
|
|
app.get(`${prefix}/reset`, async (c) => {
|
|
var key, auth;
|
|
if ((auth = auths.get(key = getCookie(c, cookie_name)))) {
|
|
auth.expire = Date.now();
|
|
keyValid(key);
|
|
}
|
|
return c.text("meow");
|
|
});
|
|
|
|
app.all("*", async (c, next) => {
|
|
var key;
|
|
keyValid(getCookie(c, cookie_name));
|
|
log(getCookie(c, cookie_name), 'Checking req', auths.get(getCookie(c, cookie_name)));
|
|
if ((key = auths.get(getCookie(c, cookie_name))) && key.status) {
|
|
keyValid(getCookie(c, cookie_name), 10000);
|
|
c.status(421);
|
|
return c.text("Success");
|
|
}
|
|
log(getCookie(c, cookie_name), 'Verification required');
|
|
if (c.req.method != "GET") {
|
|
log(getCookie(c, cookie_name), 'Non-GET denied');
|
|
c.status(403);
|
|
return c.text("Unauthorized");
|
|
}
|
|
return next();
|
|
});
|
|
|
|
var templ = str => `<!DOCTYPE html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width" />
|
|
<link rel="stylesheet" href="/main.css">
|
|
<style>
|
|
label:has(:checked) > a {
|
|
color: #f0f;
|
|
}
|
|
@media (width > 0) { .what-the-sigma {
|
|
display:block!important;
|
|
}}
|
|
.proc { color:#888; }
|
|
.thing:has(.kill) { display:none; }
|
|
</style>
|
|
</head>
|
|
<iframe style="position: absolute; opacity: 0; width:0; height:0;" name=out id=out></iframe>
|
|
<h2>anti-scraper protection</h2>
|
|
${str}`;
|
|
|
|
var funcs = new Map();
|
|
|
|
app.get(`${prefix}/: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');
|
|
if ((!(c.req.header('User-Agent')||''+'').match(/^Links /))
|
|
&&(!((c.req.header('Accept')||'')+'').match(/^text\/html/))) {
|
|
return;
|
|
}
|
|
var closed = false;
|
|
var banned = false;
|
|
var pass;
|
|
var key;
|
|
var auth = auths.get(key = getCookie(c, cookie_name));
|
|
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, cookie_name), 'Setting cookie', key);
|
|
setCookie(c, cookie_name, 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>redirecting...</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="${prefix}/${banner.key}" target=out>click here if you want to get banned</a> for 30 seconds.
|
|
<p>otherwise, click the Verify button when it appears.<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(20)} }<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 dumpflush();
|
|
async function fakebusy(time = 500) {
|
|
await lastonly_b();
|
|
await stream.write(`<div class="proc lastonly-e-${lastonly_i}">Processing`);
|
|
for (let i=0; i<10; i++) {
|
|
await stream.sleep(time / 10);
|
|
await stream.write('.');
|
|
}
|
|
await stream.write(`</div>`);
|
|
}
|
|
await fakebusy(1000);
|
|
function fincookie() {
|
|
auths.set(key, { expire: Date.now()+1000*60*60, status: true }); // 1 hour
|
|
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() + 50;
|
|
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(`<div class="lastonly-e-${lastonly_i}"><a href=${prefix}/${verb.key} target=out>--> Verify <--</a></div>`);
|
|
}
|
|
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. Redirecting... <meta http-equiv="refresh" content="0">';
|
|
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);
|