Compare commits
18 Commits
main
...
api-client
Author | SHA1 | Date | |
---|---|---|---|
|
c68ebbe949 | ||
|
1cd57fc7ff | ||
|
95f0fbdb5e | ||
|
5d7cd861f3 | ||
|
6d4477a962 | ||
|
64ac458941 | ||
|
d3e278660d | ||
|
d201deb60a | ||
|
00d5754ea8 | ||
|
ff57a6a448 | ||
|
1d30ac0139 | ||
|
a80c7b7a5a | ||
|
afe9917169 | ||
|
4755787b69 | ||
|
c8ccb32421 | ||
|
6cc1227288 | ||
|
ab8650030b | ||
|
abb6a26ee0 |
1
packages/api-client/.gitignore
vendored
Normal file
1
packages/api-client/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist
|
43
packages/api-client/i18n/error.json
Normal file
43
packages/api-client/i18n/error.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"api.auth.jwt.missing": "couldn't confirm whether you're not a robot because the processing server didn't receive the human access token. try again in a few seconds or reload the page!",
|
||||||
|
"api.auth.jwt.invalid": "couldn't confirm whether you're not a robot because your human access token expired and wasn't renewed. try again in a few seconds or reload the page!",
|
||||||
|
"api.auth.turnstile.missing": "couldn't confirm whether you're not a robot because the processing server didn't receive the human access token. try again in a few seconds or reload the page!",
|
||||||
|
"api.auth.turnstile.invalid": "couldn't confirm whether you're not a robot because your human access token expired and wasn't renewed. try again in a few seconds or reload the page!",
|
||||||
|
|
||||||
|
"api.unreachable": "couldn't connect to the processing server. check your internet connection and try again.",
|
||||||
|
"api.timed_out": "the processing server took way too long to respond. it may be overwhelmed at the moment, try again in a few seconds!",
|
||||||
|
"api.rate_exceeded": "you're making way too many requests. try again in {{ limit }} seconds!",
|
||||||
|
"api.capacity": "cobalt is at capacity and can't process your request at the moment. try again in a few seconds. if it still doesn't work, let us know and we'll try to help!",
|
||||||
|
|
||||||
|
"api.generic": "something went wrong and i couldn't get anything for you. try again in a few seconds, but if issue sticks, let us know and we'll try to help!",
|
||||||
|
"api.unknown_response": "couldn't parse the response from the server. this could be caused by a version mismatch. are you sure you're on the latest version of cobalt?",
|
||||||
|
|
||||||
|
"api.service.unsupported": "this service is not supported yet. have you pasted the right link?",
|
||||||
|
"api.service.disabled": "this service is supported by cobalt, but it's disabled on this instance. try a link from another service!",
|
||||||
|
|
||||||
|
"api.link.invalid": "your link is invalid or this service is not supported yet. have you pasted the right link?",
|
||||||
|
"api.link.unsupported": "{{ service }} is supported, but i couldn't recognize your link. have you pasted the right one?",
|
||||||
|
|
||||||
|
"api.fetch.fail": "something went wrong when fetching info from {{ service }} and i couldn't find anything for you. are you sure your link works? if it does and you still see this error, let us know and we'll try to help!",
|
||||||
|
"api.fetch.critical": "the {{ service }} module returned an error that i don't recognize. try again in a few seconds, but if issue sticks, let us know!",
|
||||||
|
"api.fetch.empty": "couldn't find any media that i could download for you. are you sure you pasted the right link?",
|
||||||
|
"api.fetch.rate": "the cobalt processing server got rate limited by the {{ service }} api. try again in a few seconds!",
|
||||||
|
"api.fetch.short_link": "couldn't get link info from the short link. are you sure it works? if it does and you still get this error, let us know, and we'll try to help!",
|
||||||
|
|
||||||
|
"api.content.too_long": "the media you requested is too long. current duration limit is {{ limit }} minutes. try something shorter instead!",
|
||||||
|
|
||||||
|
"api.content.video.unavailable": "i can't access this video. it may be restricted on {{ service }}'s side. have you pasted the right link?",
|
||||||
|
"api.content.video.live": "this video is currently live, so i can't download it yet. wait for the livestream to finish, and then try again!",
|
||||||
|
"api.content.video.private": "this video is private, so i cannot access it. change its visibility or try another one!",
|
||||||
|
"api.content.video.age": "this video is age-restricted, so i can't access it anonymously. try another one!",
|
||||||
|
"api.content.video.region": "this video is region locked, and the processing server is in a different location. try another one!",
|
||||||
|
|
||||||
|
"api.content.post.unavailable": "couldn't find anything about this post. its visibility may be limited or it may not exist at all. make sure your link works and try again in a few seconds!",
|
||||||
|
"api.content.post.private": "this post is from a private account, so i can't access it. have you pasted the right link?",
|
||||||
|
"api.content.post.age": "this post is age-restricted, so i can't access it anonymously. have you pasted the right link?",
|
||||||
|
|
||||||
|
"api.youtube.codec": "youtube didn't return anything with your preferred codec & resolution. try another set of settings!",
|
||||||
|
"api.youtube.decipher": "youtube updated its decipher algorithm and i couldn't extract the info about the video.\n\ntry again in a few seconds, but if issue sticks, contact us for support.",
|
||||||
|
"api.youtube.login": "couldn't get this video because youtube labeled me as a bot. this is potentially caused by the processing instance not having any active account tokens. try again in a few seconds, but if it still doesn't work, tell the instance owner about this error!",
|
||||||
|
"api.youtube.token_expired": "couldn't get this video because the youtube token expired and i couldn't refresh it. try again in a few seconds, but if it still doesn't work, tell the instance owner about this error!"
|
||||||
|
}
|
@ -3,13 +3,34 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {},
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch"
|
||||||
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "imput <meow@imput.net>",
|
"author": "imput <meow@imput.net>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"tsup": "^8.2.4",
|
"tsup": "^8.2.4",
|
||||||
"typescript": "^5.4.5"
|
"turnstile-types": "^1.2.2",
|
||||||
|
"typescript": "^5.4.5",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tsup": {
|
||||||
|
"dts": true,
|
||||||
|
"bundle": false,
|
||||||
|
"treeshake": true,
|
||||||
|
"target": "node18",
|
||||||
|
"format": ["esm", "cjs"],
|
||||||
|
"entry": ["src/**/*.ts"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
packages/api-client/src/index.ts
Normal file
3
packages/api-client/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./turnstile-api";
|
||||||
|
export * from "./unauthenticated-api";
|
||||||
|
export * from "./types/interface";
|
53
packages/api-client/src/internal/base-api.ts
Normal file
53
packages/api-client/src/internal/base-api.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { CobaltResponseType, type CobaltResponse } from "../types/response";
|
||||||
|
import { CobaltReachabilityError } from "../types/errors";
|
||||||
|
import type { CobaltRequest } from "../types/request";
|
||||||
|
import { CobaltAPIClient } from "../types/interface";
|
||||||
|
|
||||||
|
export default class CobaltAPI implements CobaltAPIClient {
|
||||||
|
#baseURL: string | undefined;
|
||||||
|
|
||||||
|
getBaseURL() {
|
||||||
|
return this.#baseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBaseURL(baseURL: string) {
|
||||||
|
const url = new URL(baseURL);
|
||||||
|
|
||||||
|
if (baseURL !== url.origin && baseURL !== `${url.origin}/`) {
|
||||||
|
throw new Error('Invalid cobalt instance URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#baseURL = url.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(data: CobaltRequest, headers?: Record<string, string>) {
|
||||||
|
const baseURL = this.getBaseURL();
|
||||||
|
if (!baseURL) throw "baseURL is undefined";
|
||||||
|
|
||||||
|
const response: CobaltResponse = await fetch(baseURL, {
|
||||||
|
method: 'POST',
|
||||||
|
redirect: 'manual',
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...headers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.catch((e) => {
|
||||||
|
const timedOut = e?.message?.includes("timed out");
|
||||||
|
return {
|
||||||
|
status: CobaltResponseType.Error,
|
||||||
|
error: {
|
||||||
|
code: timedOut
|
||||||
|
? CobaltReachabilityError.TimedOut
|
||||||
|
: CobaltReachabilityError.Unreachable
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
};
|
72
packages/api-client/src/internal/session.ts
Normal file
72
packages/api-client/src/internal/session.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { CobaltSession, CobaltResponseType, CobaltSessionResponse } from "../types/response";
|
||||||
|
import { CobaltReachabilityError } from "../types/errors";
|
||||||
|
import type { TurnstileObject } from "turnstile-types";
|
||||||
|
import { CobaltAPIClient } from "../types/interface";
|
||||||
|
|
||||||
|
const currentTime = () => Math.floor(new Date().getTime() / 1000);
|
||||||
|
const EXPIRY_THRESHOLD_SECONDS = 2;
|
||||||
|
|
||||||
|
export default class CobaltSessionHandler {
|
||||||
|
#client: CobaltAPIClient;
|
||||||
|
#turnstile: TurnstileObject;
|
||||||
|
#session: CobaltSession | undefined;
|
||||||
|
|
||||||
|
constructor(client: CobaltAPIClient, turnstile: TurnstileObject) {
|
||||||
|
this.#client = client;
|
||||||
|
this.#turnstile = turnstile;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #requestSession(): Promise<CobaltSessionResponse> {
|
||||||
|
const baseURL = this.#client.getBaseURL();
|
||||||
|
if (!baseURL) throw "baseURL is undefined";
|
||||||
|
|
||||||
|
const endpoint = new URL('/session', baseURL);
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
redirect: 'manual',
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
headers: {
|
||||||
|
'cf-turnstile-response': this.#turnstile.getResponse('#turnstile-widget')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.catch((e) => {
|
||||||
|
const timedOut = e?.message?.includes("timed out");
|
||||||
|
return {
|
||||||
|
status: CobaltResponseType.Error,
|
||||||
|
error: {
|
||||||
|
code: timedOut
|
||||||
|
? CobaltReachabilityError.TimedOut
|
||||||
|
: CobaltReachabilityError.Unreachable
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if ('token' in response && 'exp' in response) {
|
||||||
|
this.#session = {
|
||||||
|
token: response.token,
|
||||||
|
exp: currentTime() + response.exp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#turnstile.reset('#turnstile-widget');
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSession() {
|
||||||
|
return this.#session && this.#session.exp - EXPIRY_THRESHOLD_SECONDS > currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.#session = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSession(): Promise<CobaltSessionResponse> {
|
||||||
|
if (this.hasSession()) {
|
||||||
|
return this.#session!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#requestSession();
|
||||||
|
}
|
||||||
|
};
|
47
packages/api-client/src/turnstile-api.ts
Normal file
47
packages/api-client/src/turnstile-api.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import CobaltSessionHandler from "./internal/session";
|
||||||
|
import CobaltAPI from "./internal/base-api";
|
||||||
|
import { CobaltRequest } from "./types/request";
|
||||||
|
import { CobaltAuthError } from "./types/errors";
|
||||||
|
import type { TurnstileObject } from "turnstile-types";
|
||||||
|
|
||||||
|
export class TurnstileCobaltAPI extends CobaltAPI {
|
||||||
|
#session: CobaltSessionHandler;
|
||||||
|
#instanceHasTurnstile = true;
|
||||||
|
|
||||||
|
constructor(turnstile: TurnstileObject) {
|
||||||
|
super();
|
||||||
|
this.#session = new CobaltSessionHandler(this, turnstile);
|
||||||
|
}
|
||||||
|
|
||||||
|
needsSession() {
|
||||||
|
return this.#instanceHasTurnstile && !this.#session.hasSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
setBaseURL(baseURL: string): string {
|
||||||
|
if (this.#session && super.getBaseURL() !== super.setBaseURL(baseURL)) {
|
||||||
|
this.#session.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getBaseURL()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(data: CobaltRequest) {
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (this.#instanceHasTurnstile) {
|
||||||
|
const sessionOrError = await this.#session.getSession();
|
||||||
|
|
||||||
|
if ("error" in sessionOrError) {
|
||||||
|
if (sessionOrError.error.code !== CobaltAuthError.NotConfigured) {
|
||||||
|
return sessionOrError;
|
||||||
|
} else {
|
||||||
|
this.#instanceHasTurnstile = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers['Authorization'] = `Bearer ${sessionOrError.token}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.request(data, headers);
|
||||||
|
}
|
||||||
|
}
|
67
packages/api-client/src/types/errors.ts
Normal file
67
packages/api-client/src/types/errors.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
export enum CobaltAuthError {
|
||||||
|
NotConfigured = 'error.api.auth.not_configured',
|
||||||
|
JWTMissing = 'error.api.auth.jwt.missing',
|
||||||
|
JWTInvalid = 'error.api.auth.jwt.invalid',
|
||||||
|
TurnstileMissing = 'error.api.auth.turnstile.missing',
|
||||||
|
TurnstileInvalid = 'api.auth.turnstile.invalid'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltReachabilityError {
|
||||||
|
Unreachable = 'error.api.unreachable',
|
||||||
|
TimedOut = 'error.api.timed_out',
|
||||||
|
RateExceeded = 'error.api.rate_exceeded',
|
||||||
|
AtCapacity = 'error.api.capacity'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltGenericError {
|
||||||
|
Generic = 'error.api.generic',
|
||||||
|
UnknownResponse = 'error.api.unknown_response'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltServiceError {
|
||||||
|
Unsupported = 'error.api.service.unsupported',
|
||||||
|
Disabled = 'error.api.service.disabled'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltLinkError {
|
||||||
|
Invalid = 'error.api.link.invalid',
|
||||||
|
FormatUnsupported = 'error.api.link.unsupported'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltProcessingError {
|
||||||
|
Fail = 'error.api.fetch.fail',
|
||||||
|
Critical = 'error.api.fetch.critical',
|
||||||
|
Empty = 'error.api.fetch.empty',
|
||||||
|
RateLimited = 'error.api.fetch.rate',
|
||||||
|
ShortLink = 'error.api.fetch.short_link'
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltContentError {
|
||||||
|
TooLong = 'error.api.content.too_long',
|
||||||
|
|
||||||
|
VideoUnavailable = 'error.api.content.video.unavailable',
|
||||||
|
VideoIsLive = 'error.api.content.video.live',
|
||||||
|
VideoIsPrivate = 'error.api.content.video.private',
|
||||||
|
VideoIsAgeRestricted = 'error.api.content.video.age',
|
||||||
|
VideoIsRegionRestricted = 'error.api.content.video.region',
|
||||||
|
|
||||||
|
PostUnavailable = 'error.api.content.post.unavailable',
|
||||||
|
PostIsPrivate = 'error.api.content.post.private',
|
||||||
|
PostIsAgeRestricted = 'error.api.content.post.age',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum CobaltYouTubeError {
|
||||||
|
MissingCodec = 'error.api.youtube.codec',
|
||||||
|
CannotDecipher = 'error.api.youtube.decipher',
|
||||||
|
MissingLogin = 'error.api.youtube.login',
|
||||||
|
TokenExpired = 'error.api.youtube.token_expired'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CobaltAPIErrorCode = CobaltAuthError
|
||||||
|
| CobaltReachabilityError
|
||||||
|
| CobaltGenericError
|
||||||
|
| CobaltServiceError
|
||||||
|
| CobaltLinkError
|
||||||
|
| CobaltProcessingError
|
||||||
|
| CobaltContentError
|
||||||
|
| CobaltYouTubeError;
|
8
packages/api-client/src/types/interface.ts
Normal file
8
packages/api-client/src/types/interface.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { CobaltRequest } from "./request";
|
||||||
|
import { CobaltResponse } from "./response";
|
||||||
|
|
||||||
|
export interface CobaltAPIClient {
|
||||||
|
request(data: CobaltRequest): Promise<CobaltResponse>;
|
||||||
|
getBaseURL(): string | undefined;
|
||||||
|
setBaseURL(baseURL: string): string;
|
||||||
|
}
|
46
packages/api-client/src/types/request.ts
Normal file
46
packages/api-client/src/types/request.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// FIXME: this is duplicated from api/src/processing/schema.js
|
||||||
|
// (minus defaults) until the api is converted to TS
|
||||||
|
const apiSchema = z.object({
|
||||||
|
url: z.string()
|
||||||
|
.min(1),
|
||||||
|
|
||||||
|
audioBitrate: z.enum(
|
||||||
|
["320", "256", "128", "96", "64", "8"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
audioFormat: z.enum(
|
||||||
|
["best", "mp3", "ogg", "wav", "opus"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
downloadMode: z.enum(
|
||||||
|
["auto", "audio", "mute"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
filenameStyle: z.enum(
|
||||||
|
["classic", "pretty", "basic", "nerdy"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
youtubeVideoCodec: z.enum(
|
||||||
|
["h264", "av1", "vp9"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
videoQuality: z.enum(
|
||||||
|
["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"]
|
||||||
|
).optional(),
|
||||||
|
|
||||||
|
youtubeDubLang: z.string()
|
||||||
|
.length(2)
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
alwaysProxy: z.boolean().optional(),
|
||||||
|
disableMetadata: z.boolean().optional(),
|
||||||
|
tiktokFullAudio: z.boolean().optional(),
|
||||||
|
tiktokH265: z.boolean().optional(),
|
||||||
|
twitterGif: z.boolean().optional(),
|
||||||
|
youtubeDubBrowserLang: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export type CobaltRequest = z.infer<typeof apiSchema>;
|
71
packages/api-client/src/types/response.ts
Normal file
71
packages/api-client/src/types/response.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { CobaltAPIErrorCode } from "./errors";
|
||||||
|
|
||||||
|
export enum CobaltResponseType {
|
||||||
|
Error = 'error',
|
||||||
|
Picker = 'picker',
|
||||||
|
Redirect = 'redirect',
|
||||||
|
Tunnel = 'tunnel',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CobaltErrorResponse = {
|
||||||
|
status: CobaltResponseType.Error,
|
||||||
|
error: {
|
||||||
|
code: CobaltAPIErrorCode,
|
||||||
|
context?: {
|
||||||
|
service?: string,
|
||||||
|
limit?: number,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type CobaltPartialURLResponse = {
|
||||||
|
url: string,
|
||||||
|
filename: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type CobaltPickerResponse = {
|
||||||
|
status: CobaltResponseType.Picker
|
||||||
|
picker: {
|
||||||
|
type: 'photo' | 'video' | 'gif',
|
||||||
|
url: string,
|
||||||
|
thumb?: string,
|
||||||
|
}[];
|
||||||
|
audio?: string,
|
||||||
|
audioFilename?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
type CobaltRedirectResponse = {
|
||||||
|
status: CobaltResponseType.Redirect,
|
||||||
|
} & CobaltPartialURLResponse;
|
||||||
|
|
||||||
|
type CobaltTunnelResponse = {
|
||||||
|
status: CobaltResponseType.Tunnel,
|
||||||
|
} & CobaltPartialURLResponse;
|
||||||
|
|
||||||
|
export type CobaltSession = {
|
||||||
|
token: string,
|
||||||
|
exp: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CobaltServerInfo = {
|
||||||
|
cobalt: {
|
||||||
|
version: string,
|
||||||
|
url: string,
|
||||||
|
startTime: string,
|
||||||
|
durationLimit: number,
|
||||||
|
services: string[]
|
||||||
|
},
|
||||||
|
git: {
|
||||||
|
branch: string,
|
||||||
|
commit: string,
|
||||||
|
remote: string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CobaltSessionResponse = CobaltSession | CobaltErrorResponse;
|
||||||
|
export type CobaltServerInfoResponse = CobaltServerInfo | CobaltErrorResponse;
|
||||||
|
|
||||||
|
export type CobaltResponse = CobaltErrorResponse
|
||||||
|
| CobaltPickerResponse
|
||||||
|
| CobaltRedirectResponse
|
||||||
|
| CobaltTunnelResponse;
|
9
packages/api-client/src/unauthenticated-api.ts
Normal file
9
packages/api-client/src/unauthenticated-api.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import CobaltAPI from "./internal/base-api";
|
||||||
|
import { CobaltRequest } from "./types/request";
|
||||||
|
import { CobaltAPIClient } from "./types/interface";
|
||||||
|
|
||||||
|
export class UnauthenticatedCobaltAPI extends CobaltAPI implements CobaltAPIClient {
|
||||||
|
async request(data: CobaltRequest) {
|
||||||
|
return super.request(data, {});
|
||||||
|
}
|
||||||
|
}
|
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@ -77,9 +77,15 @@ importers:
|
|||||||
tsup:
|
tsup:
|
||||||
specifier: ^8.2.4
|
specifier: ^8.2.4
|
||||||
version: 8.2.4(postcss@8.4.40)(typescript@5.5.4)
|
version: 8.2.4(postcss@8.4.40)(typescript@5.5.4)
|
||||||
|
turnstile-types:
|
||||||
|
specifier: ^1.2.2
|
||||||
|
version: 1.2.2
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.4.5
|
specifier: ^5.4.5
|
||||||
version: 5.5.4
|
version: 5.5.4
|
||||||
|
zod:
|
||||||
|
specifier: ^3.23.8
|
||||||
|
version: 3.23.8
|
||||||
|
|
||||||
packages/version-info: {}
|
packages/version-info: {}
|
||||||
|
|
||||||
@ -119,6 +125,9 @@ importers:
|
|||||||
'@fontsource/redaction-10':
|
'@fontsource/redaction-10':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
|
'@imput/cobalt-client':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../packages/api-client
|
||||||
'@sveltejs/adapter-static':
|
'@sveltejs/adapter-static':
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
version: 3.0.2(@sveltejs/kit@2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14)))(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14)))
|
version: 3.0.2(@sveltejs/kit@2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14)))(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14)))
|
||||||
|
@ -6,47 +6,5 @@
|
|||||||
"remux.corrupted": "couldn't read the metadata from this file, it may be corrupted.",
|
"remux.corrupted": "couldn't read the metadata from this file, it may be corrupted.",
|
||||||
"remux.out_of_resources": "cobalt ran out of resources and can't continue with on-device processing. this is related to limitations on your browser's side. try refreshing or reopening the app and trying again. some devices can only process tiny files.",
|
"remux.out_of_resources": "cobalt ran out of resources and can't continue with on-device processing. this is related to limitations on your browser's side. try refreshing or reopening the app and trying again. some devices can only process tiny files.",
|
||||||
|
|
||||||
"tunnel.probe": "couldn't verify whether you can download this file. try again in a few seconds!",
|
"tunnel.probe": "couldn't verify whether you can download this file. try again in a few seconds!"
|
||||||
|
|
||||||
"api.auth.jwt.missing": "couldn't confirm whether you're not a robot because the processing server didn't receive the human access token. try again in a few seconds or reload the page!",
|
|
||||||
"api.auth.jwt.invalid": "couldn't confirm whether you're not a robot because your human access token expired and wasn't renewed. try again in a few seconds or reload the page!",
|
|
||||||
"api.auth.turnstile.missing": "couldn't confirm whether you're not a robot because the processing server didn't receive the human access token. try again in a few seconds or reload the page!",
|
|
||||||
"api.auth.turnstile.invalid": "couldn't confirm whether you're not a robot because your human access token expired and wasn't renewed. try again in a few seconds or reload the page!",
|
|
||||||
|
|
||||||
"api.unreachable": "couldn't connect to the processing server. check your internet connection and try again.",
|
|
||||||
"api.timed_out": "the processing server took way too long to respond. it may be overwhelmed at the moment, try again in a few seconds!",
|
|
||||||
"api.rate_exceeded": "you're making way too many requests. try again in {{ limit }} seconds!",
|
|
||||||
"api.capacity": "cobalt is at capacity and can't process your request at the moment. try again in a few seconds. if it still doesn't work, let us know and we'll try to help!",
|
|
||||||
|
|
||||||
"api.generic": "something went wrong and i couldn't get anything for you. try again in a few seconds, but if issue sticks, let us know and we'll try to help!",
|
|
||||||
"api.unknown_response": "couldn't parse the response from the server. this could be caused by a version mismatch. are you sure you're on the latest version of cobalt?",
|
|
||||||
|
|
||||||
"api.service.unsupported": "this service is not supported yet. have you pasted the right link?",
|
|
||||||
"api.service.disabled": "this service is supported by cobalt, but it's disabled on this instance. try a link from another service!",
|
|
||||||
|
|
||||||
"api.link.invalid": "your link is invalid or this service is not supported yet. have you pasted the right link?",
|
|
||||||
"api.link.unsupported": "{{ service }} is supported, but i couldn't recognize your link. have you pasted the right one?",
|
|
||||||
|
|
||||||
"api.fetch.fail": "something went wrong when fetching info from {{ service }} and i couldn't find anything for you. are you sure your link works? if it does and you still see this error, let us know and we'll try to help!",
|
|
||||||
"api.fetch.critical": "the {{ service }} module returned an error that i don't recognize. try again in a few seconds, but if issue sticks, let us know!",
|
|
||||||
"api.fetch.empty": "couldn't find any media that i could download for you. are you sure you pasted the right link?",
|
|
||||||
"api.fetch.rate": "the cobalt processing server got rate limited by the {{ service }} api. try again in a few seconds!",
|
|
||||||
"api.fetch.short_link": "couldn't get link info from the short link. are you sure it works? if it does and you still get this error, let us know, and we'll try to help!",
|
|
||||||
|
|
||||||
"api.content.too_long": "the media you requested is too long. current duration limit is {{ limit }} minutes. try something shorter instead!",
|
|
||||||
|
|
||||||
"api.content.video.unavailable": "i can't access this video. it may be restricted on {{ service }}'s side. have you pasted the right link?",
|
|
||||||
"api.content.video.live": "this video is currently live, so i can't download it yet. wait for the livestream to finish, and then try again!",
|
|
||||||
"api.content.video.private": "this video is private, so i cannot access it. change its visibility or try another one!",
|
|
||||||
"api.content.video.age": "this video is age-restricted, so i can't access it anonymously. try another one!",
|
|
||||||
"api.content.video.region": "this video is region locked, and the processing server is in a different location. try another one!",
|
|
||||||
|
|
||||||
"api.content.post.unavailable": "couldn't find anything about this post. its visibility may be limited or it may not exist at all. make sure your link works and try again in a few seconds!",
|
|
||||||
"api.content.post.private": "this post is from a private account, so i can't access it. have you pasted the right link?",
|
|
||||||
"api.content.post.age": "this post is age-restricted, so i can't access it anonymously. have you pasted the right link?",
|
|
||||||
|
|
||||||
"api.youtube.codec": "youtube didn't return anything with your preferred codec & resolution. try another set of settings!",
|
|
||||||
"api.youtube.decipher": "youtube updated its decipher algorithm and i couldn't extract the info about the video.\n\ntry again in a few seconds, but if issue sticks, contact us for support.",
|
|
||||||
"api.youtube.login": "couldn't get this video because youtube labeled me as a bot. this is potentially caused by the processing instance not having any active account tokens. try again in a few seconds, but if it still doesn't work, tell the instance owner about this error!",
|
|
||||||
"api.youtube.token_expired": "couldn't get this video because the youtube token expired and i couldn't refresh it. try again in a few seconds, but if it still doesn't work, tell the instance owner about this error!"
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.5.0",
|
"@eslint/js": "^9.5.0",
|
||||||
"@fontsource/redaction-10": "^5.0.2",
|
"@fontsource/redaction-10": "^5.0.2",
|
||||||
|
"@imput/cobalt-client": "workspace:^",
|
||||||
"@sveltejs/adapter-static": "^3.0.2",
|
"@sveltejs/adapter-static": "^3.0.2",
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
import { link } from "$lib/state/omnibox";
|
import { link } from "$lib/state/omnibox";
|
||||||
import { updateSetting } from "$lib/state/settings";
|
import { updateSetting } from "$lib/state/settings";
|
||||||
import { turnstileLoaded } from "$lib/state/turnstile";
|
import { turnstileCreated } from "$lib/state/turnstile";
|
||||||
|
|
||||||
import type { Optional } from "$lib/types/generic";
|
import type { Optional } from "$lib/types/generic";
|
||||||
import type { DownloadModeOption } from "$lib/types/settings";
|
import type { DownloadModeOption } from "$lib/types/settings";
|
||||||
@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: if (env.TURNSTILE_KEY) {
|
$: if (env.TURNSTILE_KEY) {
|
||||||
if ($turnstileLoaded) {
|
if ($turnstileCreated) {
|
||||||
isDisabled = false;
|
isDisabled = false;
|
||||||
} else {
|
} else {
|
||||||
isDisabled = true;
|
isDisabled = true;
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import "@fontsource-variable/noto-sans-mono";
|
import "@fontsource-variable/noto-sans-mono";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
import settings from "$lib/state/settings";
|
||||||
import API from "$lib/api/api";
|
import API from "$lib/api/api";
|
||||||
|
import APIUrl from "$lib/state/api-url";
|
||||||
|
import lazySettingGetter from "$lib/settings/lazy-get";
|
||||||
|
import { apiOverrideWarning } from "$lib/api/safety-warning";
|
||||||
|
|
||||||
|
import env from "$lib/env";
|
||||||
import { t } from "$lib/i18n/translations";
|
import { t } from "$lib/i18n/translations";
|
||||||
import { createDialog } from "$lib/dialogs";
|
import { createDialog } from "$lib/dialogs";
|
||||||
import { downloadFile } from "$lib/download";
|
|
||||||
|
|
||||||
import type { DialogInfo } from "$lib/types/dialog";
|
import type { DialogInfo } from "$lib/types/dialog";
|
||||||
|
import { downloadFile } from "$lib/download";
|
||||||
|
import {
|
||||||
|
UnauthenticatedCobaltAPI,
|
||||||
|
TurnstileCobaltAPI,
|
||||||
|
type CobaltAPIClient
|
||||||
|
} from "@imput/cobalt-client";
|
||||||
|
|
||||||
export let url: string;
|
export let url: string;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
@ -58,10 +69,47 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let client: CobaltAPIClient;
|
||||||
|
|
||||||
|
$: client?.setBaseURL($APIUrl);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (env.TURNSTILE_KEY) {
|
||||||
|
client = new TurnstileCobaltAPI(window.turnstile);
|
||||||
|
} else {
|
||||||
|
client = new UnauthenticatedCobaltAPI();
|
||||||
|
}
|
||||||
|
client.setBaseURL($APIUrl);
|
||||||
|
});
|
||||||
|
|
||||||
export const download = async (link: string) => {
|
export const download = async (link: string) => {
|
||||||
changeDownloadButton("think");
|
changeDownloadButton("think");
|
||||||
|
|
||||||
const response = await API.request(link);
|
const getSetting = lazySettingGetter($settings);
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
url: link,
|
||||||
|
|
||||||
|
downloadMode: getSetting("save", "downloadMode"),
|
||||||
|
audioBitrate: getSetting("save", "audioBitrate"),
|
||||||
|
audioFormat: getSetting("save", "audioFormat"),
|
||||||
|
tiktokFullAudio: getSetting("save", "tiktokFullAudio"),
|
||||||
|
youtubeDubBrowserLang: getSetting("save", "youtubeDubBrowserLang"),
|
||||||
|
|
||||||
|
youtubeVideoCodec: getSetting("save", "youtubeVideoCodec"),
|
||||||
|
videoQuality: getSetting("save", "videoQuality"),
|
||||||
|
|
||||||
|
filenameStyle: getSetting("save", "filenameStyle"),
|
||||||
|
disableMetadata: getSetting("save", "disableMetadata"),
|
||||||
|
|
||||||
|
twitterGif: getSetting("save", "twitterGif"),
|
||||||
|
tiktokH265: getSetting("save", "tiktokH265"),
|
||||||
|
|
||||||
|
alwaysProxy: getSetting("privacy", "alwaysProxy"),
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiOverrideWarning();
|
||||||
|
const response = await client.request(request);
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
changeDownloadButton("error");
|
changeDownloadButton("error");
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import { get } from "svelte/store";
|
|
||||||
|
|
||||||
import env, { apiURL } from "$lib/env";
|
|
||||||
import settings from "$lib/state/settings";
|
|
||||||
|
|
||||||
export const currentApiURL = () => {
|
|
||||||
const processingSettings = get(settings).processing;
|
|
||||||
const customInstanceURL = processingSettings.customInstanceURL;
|
|
||||||
|
|
||||||
if (processingSettings.enableCustomInstances && customInstanceURL.length > 0) {
|
|
||||||
return new URL(customInstanceURL).origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (env.DEFAULT_API && processingSettings.allowDefaultOverride) {
|
|
||||||
return new URL(env.DEFAULT_API).origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new URL(apiURL).origin;
|
|
||||||
}
|
|
@ -1,85 +1,3 @@
|
|||||||
import { get } from "svelte/store";
|
|
||||||
|
|
||||||
import settings from "$lib/state/settings";
|
|
||||||
import { getSession } from "$lib/api/session";
|
|
||||||
import { currentApiURL } from "$lib/api/api-url";
|
|
||||||
import { apiOverrideWarning } from "$lib/api/safety-warning";
|
|
||||||
import type { Optional } from "$lib/types/generic";
|
|
||||||
import type { CobaltAPIResponse, CobaltErrorResponse } from "$lib/types/api";
|
|
||||||
import lazySettingGetter from "$lib/settings/lazy-get";
|
|
||||||
|
|
||||||
const request = async (url: string) => {
|
|
||||||
const getSetting = lazySettingGetter(get(settings));
|
|
||||||
|
|
||||||
const request = {
|
|
||||||
url,
|
|
||||||
|
|
||||||
downloadMode: getSetting("save", "downloadMode"),
|
|
||||||
audioBitrate: getSetting("save", "audioBitrate"),
|
|
||||||
audioFormat: getSetting("save", "audioFormat"),
|
|
||||||
tiktokFullAudio: getSetting("save", "tiktokFullAudio"),
|
|
||||||
youtubeDubBrowserLang: getSetting("save", "youtubeDubBrowserLang"),
|
|
||||||
|
|
||||||
youtubeVideoCodec: getSetting("save", "youtubeVideoCodec"),
|
|
||||||
videoQuality: getSetting("save", "videoQuality"),
|
|
||||||
|
|
||||||
filenameStyle: getSetting("save", "filenameStyle"),
|
|
||||||
disableMetadata: getSetting("save", "disableMetadata"),
|
|
||||||
|
|
||||||
twitterGif: getSetting("save", "twitterGif"),
|
|
||||||
tiktokH265: getSetting("save", "tiktokH265"),
|
|
||||||
|
|
||||||
alwaysProxy: getSetting("privacy", "alwaysProxy"),
|
|
||||||
}
|
|
||||||
|
|
||||||
await apiOverrideWarning();
|
|
||||||
|
|
||||||
const usingCustomInstance = getSetting("processing", "enableCustomInstances")
|
|
||||||
&& getSetting("processing", "customInstanceURL");
|
|
||||||
const api = currentApiURL();
|
|
||||||
// FIXME: rewrite this to allow custom instances to specify their own turnstile tokens
|
|
||||||
const session = usingCustomInstance ? undefined : await getSession();
|
|
||||||
|
|
||||||
let extraHeaders = {}
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
if ("error" in session) {
|
|
||||||
if (session.error.code !== "error.api.auth.not_configured") {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
extraHeaders = {
|
|
||||||
"Authorization": `Bearer ${session.token}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: Optional<CobaltAPIResponse> = await fetch(api, {
|
|
||||||
method: "POST",
|
|
||||||
redirect: "manual",
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...extraHeaders,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.catch((e) => {
|
|
||||||
if (e?.message?.includes("timed out")) {
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
code: "error.api.timed_out"
|
|
||||||
}
|
|
||||||
} as CobaltErrorResponse;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
const probeCobaltTunnel = async (url: string) => {
|
const probeCobaltTunnel = async (url: string) => {
|
||||||
const request = await fetch(`${url}&p=1`).catch(() => {});
|
const request = await fetch(`${url}&p=1`).catch(() => {});
|
||||||
if (request?.status === 200) {
|
if (request?.status === 200) {
|
||||||
@ -89,6 +7,5 @@ const probeCobaltTunnel = async (url: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
request,
|
|
||||||
probeCobaltTunnel,
|
probeCobaltTunnel,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
import { currentApiURL } from "$lib/api/api-url";
|
import APIUrl from "$lib/state/api-url";
|
||||||
|
|
||||||
import type { CobaltServerInfoResponse, CobaltErrorResponse, CobaltServerInfo } from "$lib/types/api";
|
import type { CobaltServerInfoResponse, CobaltErrorResponse, CobaltServerInfo } from "$lib/types/api";
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export type CobaltServerInfoCache = {
|
|||||||
export const cachedInfo = writable<CobaltServerInfoCache | undefined>();
|
export const cachedInfo = writable<CobaltServerInfoCache | undefined>();
|
||||||
|
|
||||||
const request = async () => {
|
const request = async () => {
|
||||||
const apiEndpoint = `${currentApiURL()}/`;
|
const apiEndpoint = `${get(APIUrl)}/`;
|
||||||
|
|
||||||
const response: CobaltServerInfoResponse = await fetch(apiEndpoint, {
|
const response: CobaltServerInfoResponse = await fetch(apiEndpoint, {
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
@ -35,7 +35,7 @@ const request = async () => {
|
|||||||
export const getServerInfo = async () => {
|
export const getServerInfo = async () => {
|
||||||
const cache = get(cachedInfo);
|
const cache = get(cachedInfo);
|
||||||
|
|
||||||
if (cache && cache.origin === currentApiURL()) {
|
if (cache && cache.origin === get(APIUrl)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export const getServerInfo = async () => {
|
|||||||
if (!("status" in freshInfo)) {
|
if (!("status" in freshInfo)) {
|
||||||
cachedInfo.set({
|
cachedInfo.set({
|
||||||
info: freshInfo,
|
info: freshInfo,
|
||||||
origin: currentApiURL(),
|
origin: get(APIUrl),
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import turnstile from "$lib/api/turnstile";
|
|
||||||
import { writable, get } from "svelte/store";
|
|
||||||
import { currentApiURL } from "$lib/api/api-url";
|
|
||||||
|
|
||||||
import type { CobaltSession, CobaltErrorResponse, CobaltSessionResponse } from "$lib/types/api";
|
|
||||||
|
|
||||||
const cachedSession = writable<CobaltSession | undefined>();
|
|
||||||
|
|
||||||
export const requestSession = async() => {
|
|
||||||
const apiEndpoint = `${currentApiURL()}/session`;
|
|
||||||
|
|
||||||
let requestHeaders = {};
|
|
||||||
|
|
||||||
const turnstileResponse = turnstile.getResponse();
|
|
||||||
if (turnstileResponse) {
|
|
||||||
requestHeaders = {
|
|
||||||
"cf-turnstile-response": turnstileResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: CobaltSessionResponse = await fetch(apiEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
redirect: "manual",
|
|
||||||
signal: AbortSignal.timeout(10000),
|
|
||||||
headers: requestHeaders,
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.catch((e) => {
|
|
||||||
if (e?.message?.includes("timed out")) {
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
code: "error.api.timed_out"
|
|
||||||
}
|
|
||||||
} as CobaltErrorResponse
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
turnstile.update();
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getSession = async () => {
|
|
||||||
const currentTime = () => Math.floor(new Date().getTime() / 1000);
|
|
||||||
const cache = get(cachedSession);
|
|
||||||
|
|
||||||
if (cache?.token && cache?.exp - 2 > currentTime()) {
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSession = await requestSession();
|
|
||||||
|
|
||||||
if (!newSession) return {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
code: "error.api.unreachable"
|
|
||||||
}
|
|
||||||
} as CobaltErrorResponse
|
|
||||||
|
|
||||||
if (!("status" in newSession)) {
|
|
||||||
newSession.exp = currentTime() + newSession.exp;
|
|
||||||
cachedSession.set(newSession);
|
|
||||||
}
|
|
||||||
return newSession;
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
const getResponse = () => {
|
|
||||||
const turnstileElement = document.getElementById("turnstile-widget");
|
|
||||||
|
|
||||||
if (turnstileElement) {
|
|
||||||
return window?.turnstile?.getResponse(turnstileElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = () => {
|
|
||||||
const turnstileElement = document.getElementById("turnstile-widget");
|
|
||||||
|
|
||||||
if (turnstileElement) {
|
|
||||||
return window?.turnstile?.reset(turnstileElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getResponse,
|
|
||||||
update,
|
|
||||||
}
|
|
18
web/src/lib/state/api-url.ts
Normal file
18
web/src/lib/state/api-url.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { derived } from "svelte/store";
|
||||||
|
|
||||||
|
import env, { apiURL } from "$lib/env";
|
||||||
|
import settings from "$lib/state/settings";
|
||||||
|
|
||||||
|
export default derived(
|
||||||
|
settings,
|
||||||
|
$settings => {
|
||||||
|
const { processing } = $settings;
|
||||||
|
|
||||||
|
if (processing.enableCustomInstances && processing.customInstanceURL)
|
||||||
|
return new URL(processing.customInstanceURL).origin;
|
||||||
|
else if (env.DEFAULT_API && processing.allowDefaultOverride)
|
||||||
|
return new URL(env.DEFAULT_API).origin;
|
||||||
|
else
|
||||||
|
return new URL(apiURL).origin;
|
||||||
|
}
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user