added devcontainer and made a custom cookie workaround for a specific problem

This commit is contained in:
Legonois 2025-03-11 22:15:59 +00:00
parent 98ad968606
commit 5bf86f1daa
12 changed files with 124 additions and 41 deletions

View File

@ -6,6 +6,13 @@
"image": "mcr.microsoft.com/devcontainers/universal:2-linux", "image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": { "features": {
"ghcr.io/devcontainers/features/node:1": {} "ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"svelte.svelte-vscode"
]
}
} }
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.

1
.gitignore vendored
View File

@ -4,6 +4,7 @@ desktop.ini
# node # node
node_modules node_modules
.pnpm-store
# static build # static build
build build

View File

@ -154,41 +154,43 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
}); });
app.post('/', (req, res, next) => { app.post('/', (req, res, next) => {
if (!env.sessionEnabled || req.rateLimitKey) { // if (!env.sessionEnabled || req.rateLimitKey) {
return next(); // return next();
} // }
try { // try {
const authorization = req.header("Authorization"); // const authorization = req.header("Authorization");
if (!authorization) { // if (!authorization) {
return fail(res, "error.api.auth.jwt.missing"); // return fail(res, "error.api.auth.jwt.missing");
} // }
if (authorization.length >= 256) { // if (authorization.length >= 256) {
return fail(res, "error.api.auth.jwt.invalid"); // return fail(res, "error.api.auth.jwt.invalid");
} // }
const [ type, token, ...rest ] = authorization.split(" "); // const [ type, token, ...rest ] = authorization.split(" ");
if (!token || type.toLowerCase() !== 'bearer' || rest.length) { // if (!token || type.toLowerCase() !== 'bearer' || rest.length) {
return fail(res, "error.api.auth.jwt.invalid"); // return fail(res, "error.api.auth.jwt.invalid");
} // }
if (!jwt.verify(token)) { // if (!jwt.verify(token)) {
return fail(res, "error.api.auth.jwt.invalid"); // return fail(res, "error.api.auth.jwt.invalid");
} // }
req.rateLimitKey = hashHmac(token, 'rate'); // req.rateLimitKey = hashHmac(token, 'rate');
} catch { // } catch {
return fail(res, "error.api.generic"); // return fail(res, "error.api.generic");
} // }
next(); next();
}); });
app.post('/', apiLimiter); app.post('/', apiLimiter);
app.use('/', express.json({ limit: 1024 })); app.use('/', express.json({ limit: 8192 }));
app.use('/', (err, _, res, next) => { app.use('/', (err, req, res, next) => {
if (err) { if (err) {
// console.error('Failed to normalize request:', req);
console.error('error:', err);
const { status, body } = createResponse("error", { const { status, body } = createResponse("error", {
code: "error.api.invalid_body", code: "error.api.invalid_body",
}); });
@ -234,6 +236,8 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
const { success, data: normalizedRequest } = await normalizeRequest(request); const { success, data: normalizedRequest } = await normalizeRequest(request);
if (!success) { if (!success) {
console.error('Failed to normalize request:', request);
console.log('data:', normalizedRequest);
return fail(res, "error.api.invalid_body"); return fail(res, "error.api.invalid_body");
} }
@ -250,15 +254,20 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
return fail(res, `error.api.${parsed.error}`, context); return fail(res, `error.api.${parsed.error}`, context);
} }
if (!request.cookies)
request.cookies = {"X-nocookies" : "included"};
try { try {
const result = await match({ const result = await match({
host: parsed.host, host: parsed.host,
patternMatch: parsed.patternMatch, patternMatch: parsed.patternMatch,
params: normalizedRequest, params: normalizedRequest,
inputCookies: request.cookies,
}); });
res.status(result.status).json(result.body); res.status(result.status).json(result.body);
} catch { } catch (e) {
console.error("Failed to match request", request, e);
fail(res, "error.api.generic"); fail(res, "error.api.generic");
} }
}) })

View File

@ -29,14 +29,24 @@ import loom from "./services/loom.js";
import facebook from "./services/facebook.js"; import facebook from "./services/facebook.js";
import bluesky from "./services/bluesky.js"; import bluesky from "./services/bluesky.js";
import xiaohongshu from "./services/xiaohongshu.js"; import xiaohongshu from "./services/xiaohongshu.js";
import Cookie from "./cookie/cookie.js";
let freebind; let freebind;
export default async function({ host, patternMatch, params }) { export default async function({ host, patternMatch, params, inputCookies }) {
const { url } = params; const { url } = params;
assert(url instanceof URL); assert(url instanceof URL);
let dispatcher, requestIP; let dispatcher, requestIP;
console.log('Calling with cookies:', inputCookies);
let cookies = new Cookie({});
for (const [k, v] of Object.entries(inputCookies)) {
cookies.set(k, v);
}
console.log('Calling with cookies:', cookies);
if (env.freebindCIDR) { if (env.freebindCIDR) {
if (!freebind) { if (!freebind) {
freebind = await import('freebind'); freebind = await import('freebind');
@ -133,6 +143,7 @@ export default async function({ host, patternMatch, params }) {
isAudioOnly, isAudioOnly,
h265: params.tiktokH265, h265: params.tiktokH265,
alwaysProxy: params.alwaysProxy, alwaysProxy: params.alwaysProxy,
cookies: cookies,
}); });
break; break;

View File

@ -77,9 +77,10 @@ export function createResponse(responseType, responseData) {
} }
export function normalizeRequest(request) { export function normalizeRequest(request) {
return apiSchema.safeParseAsync(request).catch(() => ( return apiSchema.safeParseAsync(request).catch((err) => {
{ success: false } console.log('Zod validation error:', JSON.stringify(err, null, 2));
)); return { success: false };
});
} }
export function getIP(req) { export function getIP(req) {

View File

@ -47,5 +47,7 @@ export const apiSchema = z.object({
twitterGif: z.boolean().default(true), twitterGif: z.boolean().default(true),
youtubeHLS: z.boolean().default(false), youtubeHLS: z.boolean().default(false),
cookies: z.record(z.string()).default({}),
}) })
.strict(); .strict();

View File

@ -8,9 +8,12 @@ import { createStream } from "../../stream/manage.js";
const shortDomain = "https://vt.tiktok.com/"; const shortDomain = "https://vt.tiktok.com/";
export default async function(obj) { export default async function(obj) {
const cookie = new Cookie({}); // const cookie = new Cookie({});
const cookie = obj.cookies;
let postId = obj.postId; let postId = obj.postId;
console.log("With TikTok using cookies", cookie);
if (!postId) { if (!postId) {
let html = await fetch(`${shortDomain}${obj.shortLink}`, { let html = await fetch(`${shortDomain}${obj.shortLink}`, {
redirect: "manual", redirect: "manual",

View File

@ -1,5 +1,6 @@
{ {
"gotit": "got it", "gotit": "got it",
"signin": "sign into service",
"cancel": "cancel", "cancel": "cancel",
"reset": "reset", "reset": "reset",
"done": "done", "done": "done",

View File

@ -59,7 +59,7 @@
"api.content.post.unavailable": "couldn't find anything about this post. its visibility may be limited or it may not exist. make sure your link works and try again in a few seconds!", "api.content.post.unavailable": "couldn't find anything about this post. its visibility may be limited or it may not exist. make sure your link works and try again in a few seconds!",
"api.content.post.private": "couldn't get anything about this post because it's from a private account. try a different link!", "api.content.post.private": "couldn't get anything about this post because it's from a private account. try a different link!",
"api.content.post.age": "this post is age-restricted and isn't available without logging in. try a different link!", "api.content.post.age": "this post is age-restricted and isn't available without logging in. Sign in to your account.",
"api.youtube.no_matching_format": "youtube didn't return a valid video + audio format combo, either video or audio is missing. formats for this video may be re-encoding on youtube's side or something went wrong when parsing them. try enabling the hls option in video settings!", "api.youtube.no_matching_format": "youtube didn't return a valid video + audio format combo, either video or audio is missing. formats for this video may be re-encoding on youtube's side or something went wrong when parsing them. try enabling the hls option in video settings!",
"api.youtube.decipher": "youtube updated its decipher algorithm and i couldn't extract the info about the video. try again in a few seconds, but if this issue sticks, please report it!", "api.youtube.decipher": "youtube updated its decipher algorithm and i couldn't extract the info about the video. try again in a few seconds, but if this issue sticks, please report it!",

View File

@ -38,7 +38,8 @@
let isDisabled = false; let isDisabled = false;
let isLoading = false; let isLoading = false;
$: isBotCheckOngoing = $turnstileEnabled && !$turnstileSolved; // $: isBotCheckOngoing = $turnstileEnabled && !$turnstileSolved;
$: isBotCheckOngoing = false;
const validLink = (url: string) => { const validLink = (url: string) => {
try { try {
@ -132,7 +133,8 @@
--> -->
{#if env.DEFAULT_API || (!$page.url.host.endsWith(".cobalt.tools") && $page.url.host !== "cobalt.tools")} {#if env.DEFAULT_API || (!$page.url.host.endsWith(".cobalt.tools") && $page.url.host !== "cobalt.tools")}
<div id="instance-label"> <div id="instance-label">
{$t("save.label.community_instance")} KDDResearch Instance
<!-- {$t("save.label.community_instance")} -->
</div> </div>
{/if} {/if}
@ -155,13 +157,14 @@
autocapitalize="off" autocapitalize="off"
maxlength="512" maxlength="512"
placeholder={$t("save.input.placeholder")} placeholder={$t("save.input.placeholder")}
aria-label={isBotCheckOngoing
? $t("a11y.save.link_area.turnstile")
: $t("a11y.save.link_area")}
data-form-type="other" data-form-type="other"
disabled={isDisabled} disabled={isDisabled}
/> />
<!-- aria-label={isBotCheckOngoing
? $t("a11y.save.link_area.turnstile")
: $t("a11y.save.link_area")} -->
{#if $link && !isLoading} {#if $link && !isLoading}
<ClearButton click={() => ($link = "")} /> <ClearButton click={() => ($link = "")} />
{/if} {/if}

View File

@ -77,6 +77,50 @@
if (response.status === "error") { if (response.status === "error") {
changeDownloadButton("error"); changeDownloadButton("error");
console.log(response.error.code);
if (response.error.code == "error.api.content.post.age") {
return createDialog({
id: "save-error",
type: "small",
meowbalt: "error",
buttons: [
{
text: $t("button.signin"),
main: true,
action: () => {
// opens a new tab for the user and redirects them to the login page
const website = URL.parse(link);
if (!website)
throw new Error("Invalid URL");
console.log(website);
switch (website.hostname) {
case "www.tiktok.com":
website.pathname = "/login/qrcode";
break;
default:
website.pathname = "/login";
break;
}
window.open(website.href, "_blank");
},
},
{
text: $t("button.cancel"),
main: false,
action: () => {},
},
],
bodyText: $t(response.error.code, response?.error?.context),
});
}
return createDialog({ return createDialog({
...defaultErrorPopup, ...defaultErrorPopup,
bodyText: $t(response.error.code, response?.error?.context), bodyText: $t(response.error.code, response?.error?.context),

View File

@ -8,10 +8,11 @@ export const turnstileCreated = writable(false);
export const turnstileEnabled = derived( export const turnstileEnabled = derived(
[settings, cachedInfo], [settings, cachedInfo],
([$settings, $cachedInfo]) => { ([$settings, $cachedInfo]) => {
return !!$cachedInfo?.info?.cobalt?.turnstileSitekey && // return !!$cachedInfo?.info?.cobalt?.turnstileSitekey &&
!( // !(
$settings.processing.enableCustomApiKey && // $settings.processing.enableCustomApiKey &&
$settings.processing.customApiKey.length > 0 // $settings.processing.customApiKey.length > 0
) // )
return false;
} }
) )