From 2fd502039fc1c6ce44dc368a901747e40d2ea3e3 Mon Sep 17 00:00:00 2001 From: localhost Date: Wed, 29 Apr 2026 11:36:21 +0200 Subject: [PATCH] bypass captcha keys --- bun.lockb | Bin 54028 -> 55054 bytes docker-compose.yml | 1 + package.json | 2 ++ src/router/websocket.ts | 64 +++++++++++++++++++++++----------------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/bun.lockb b/bun.lockb index 7e8efb2896e886adf2f2c9f4c9e7665e7df406d6..e2bb602d36ccd9024f0692efed7c283fa11c2ae3 100755 GIT binary patch delta 9132 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 JrjpA${s{_=IIREx delta 8481 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 `save:${videoId}` const DEFAULT_STORAGE_LIMIT_MESSAGE = 'Daily storage limit reached. Is this an urgent archive? Please email me: admin@preservetube.com' const NEW_VISITOR_STORAGE_LIMIT_MESSAGE = 'You are a new visitor, so your storage limit is lower for the first few hours. Please come back later.
Is this an urgent archive? Please email me: admin@preservetube.com' +const bKeys = yaml.load(await Bun.file('keys.yaml').text()) as { keys: string[] } const checkMbLimit = async (subjects: string[], mb?: number): Promise<{ isLimited: boolean, isNewVisitorLimited: boolean }> => { const keys = subjects.map(subject => `save-mb:${Bun.hash(subject).toString()}`) @@ -114,7 +116,8 @@ const getRateLimitKey = (ip: string): string => { app.ws('/save', { query: t.Object({ url: t.String(), - rlid: t.Optional(t.String()) + rlid: t.Optional(t.String()), + bKey: t.Optional(t.String()) }), body: t.String(), open: async (ws) => { @@ -140,16 +143,19 @@ app.ws('/save', { ws.send(`DONE - ${process.env.FRONTEND}/watch?v=${videoId}`) ws.close() } else { - const subjects = getRateLimitSubjects( - getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), - ws.data.query.rlid - ) - const limitStatus = await checkMbLimit(subjects) - if (limitStatus.isLimited) { - return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE); + if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) { + const subjects = getRateLimitSubjects( + getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), + ws.data.query.rlid + ) + const limitStatus = await checkMbLimit(subjects) + if (limitStatus.isLimited) { + return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE); + } + + console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${JSON.stringify(ws.data.query)}`) } - console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${JSON.stringify(ws.data.query)}`) ws.send('DATA - This process is automatic. Your video will start archiving shortly.') ws.send('CAPTCHA - Solving a cryptographic challenge before downloading.') videoIds[ws.id] = videoId @@ -164,13 +170,15 @@ app.ws('/save', { if (await redis.get(saveKey(videoId)) !== 'downloading') { await redis.set(saveKey(videoId), 'downloading', 'EX', 300) - const captchaCheck = await checkCaptcha(message, ws.data.headers['cf-connecting-ip'] || '0.0.0.0') - if (!captchaCheck.success) { - await cleanup(ws, videoId); - console.log(`captcha failed for ${videoId} - ${JSON.stringify(captchaCheck)}`) - return sendError(ws, 'Captcha validation failed.'); - } else { - ws.send('DATA - Captcha validated. Starting download...'); + if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) { + const captchaCheck = await checkCaptcha(message, ws.data.headers['cf-connecting-ip'] || '0.0.0.0') + if (!captchaCheck.success) { + await cleanup(ws, videoId); + console.log(`captcha failed for ${videoId} - ${JSON.stringify(captchaCheck)}`) + return sendError(ws, 'Captcha validation failed.'); + } else { + ws.send('DATA - Captcha validated. Starting download...'); + } } const data = await getVideo(videoId) @@ -194,17 +202,19 @@ app.ws('/save', { return sendError(ws, downloadResult.message); } - const mbsUsed = Math.ceil(downloadResult.size / (1024 * 1024)) - const subjects = getRateLimitSubjects( - getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), - ws.data.query.rlid - ) - const limitStatus = await checkMbLimit(subjects, mbsUsed) - if (limitStatus.isLimited) { - const file = fs.readdirSync('./videos/').find(f => f.includes(`${videoId}.`)) - if (file) fs.unlinkSync('./videos/' + file) - await cleanup(ws, videoId); - return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE); + if (!(ws.data.query.bKey && bKeys.keys.includes(ws.data.query.bKey))) { + const mbsUsed = Math.ceil(downloadResult.size / (1024 * 1024)) + const subjects = getRateLimitSubjects( + getRateLimitKey(ws.data.headers['cf-connecting-ip'] || '0.0.0.0'), + ws.data.query.rlid + ) + const limitStatus = await checkMbLimit(subjects, mbsUsed) + if (limitStatus.isLimited) { + const file = fs.readdirSync('./videos/').find(f => f.includes(`${videoId}.`)) + if (file) fs.unlinkSync('./videos/' + file) + await cleanup(ws, videoId); + return sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE); + } } const uploadSuccess = await handleUpload(ws, videoId);