diff --git a/.gitignore b/.gitignore
index b408b65c..ae56830a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@ build
.env.*
!.env.example
cookies.json
+keys.json
# docker
docker-compose.yml
diff --git a/api/package.json b/api/package.json
index 8ebe4c7e..339d383f 100644
--- a/api/package.json
+++ b/api/package.json
@@ -1,7 +1,7 @@
{
"name": "@imput/cobalt-api",
"description": "save what you love",
- "version": "10.0.0",
+ "version": "10.1.0",
"author": "imput",
"exports": "./src/cobalt.js",
"type": "module",
@@ -29,18 +29,18 @@
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"esbuild": "^0.14.51",
- "express": "^4.18.1",
+ "express": "^4.21.0",
"express-rate-limit": "^6.3.0",
"ffmpeg-static": "^5.1.0",
"hls-parser": "^0.10.7",
- "ipaddr.js": "2.1.0",
+ "ipaddr.js": "2.2.0",
"nanoid": "^4.0.2",
"node-cache": "^5.1.2",
"psl": "1.9.0",
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",
- "youtubei.js": "^10.3.0",
+ "youtubei.js": "^10.5.0",
"zod": "^3.23.8"
},
"optionalDependencies": {
diff --git a/api/src/config.js b/api/src/config.js
index fc1d5d29..3a28d7ce 100644
--- a/api/src/config.js
+++ b/api/src/config.js
@@ -26,7 +26,7 @@ const env = {
rateLimitMax: (process.env.RATELIMIT_MAX && parseInt(process.env.RATELIMIT_MAX)) || 20,
durationLimit: (process.env.DURATION_LIMIT && parseInt(process.env.DURATION_LIMIT)) || 10800,
- streamLifespan: 90,
+ streamLifespan: (process.env.TUNNEL_LIFESPAN && parseInt(process.env.TUNNEL_LIFESPAN)) || 90,
processingPriority: process.platform !== 'win32'
&& process.env.PROCESSING_PRIORITY
@@ -34,10 +34,20 @@ const env = {
externalProxy: process.env.API_EXTERNAL_PROXY,
+ turnstileSitekey: process.env.TURNSTILE_SITEKEY,
turnstileSecret: process.env.TURNSTILE_SECRET,
jwtSecret: process.env.JWT_SECRET,
jwtLifetime: process.env.JWT_EXPIRY || 120,
+ sessionEnabled: process.env.TURNSTILE_SITEKEY
+ && process.env.TURNSTILE_SECRET
+ && process.env.JWT_SECRET,
+
+ apiKeyURL: process.env.API_KEY_URL && new URL(process.env.API_KEY_URL),
+ authRequired: process.env.API_AUTH_REQUIRED === '1',
+
+ keyReloadInterval: 900,
+
enabledServices,
}
diff --git a/api/src/core/api.js b/api/src/core/api.js
index fdd13ce1..b11d689a 100644
--- a/api/src/core/api.js
+++ b/api/src/core/api.js
@@ -17,6 +17,7 @@ import { verifyTurnstileToken } from "../security/turnstile.js";
import { friendlyServiceName } from "../processing/service-alias.js";
import { verifyStream, getInternalStream } from "../stream/manage.js";
import { createResponse, normalizeRequest, getIP } from "../processing/request.js";
+import * as APIKeys from "../security/api-keys.js";
const git = {
branch: await getBranch(),
@@ -49,6 +50,7 @@ export const runAPI = (express, app, __dirname) => {
url: env.apiURL,
startTime: `${startTimestamp}`,
durationLimit: env.durationLimit,
+ turnstileSitekey: env.sessionEnabled ? env.turnstileSitekey : undefined,
services: [...env.enabledServices].map(e => {
return friendlyServiceName(e);
}),
@@ -56,34 +58,40 @@ export const runAPI = (express, app, __dirname) => {
git,
})
- const apiLimiter = rateLimit({
- windowMs: env.rateLimitWindow * 1000,
- max: env.rateLimitMax,
- standardHeaders: true,
- legacyHeaders: false,
- keyGenerator: req => {
- if (req.authorized) {
- return generateHmac(req.header("Authorization"), ipSalt);
+ const handleRateExceeded = (_, res) => {
+ const { status, body } = createResponse("error", {
+ code: "error.api.rate_exceeded",
+ context: {
+ limit: env.rateLimitWindow
}
- return generateHmac(getIP(req), ipSalt);
- },
- handler: (req, res) => {
- const { status, body } = createResponse("error", {
- code: "error.api.rate_exceeded",
- context: {
- limit: env.rateLimitWindow
- }
- });
- return res.status(status).json(body);
- }
- })
+ });
+ return res.status(status).json(body);
+ };
- const apiLimiterStream = rateLimit({
- windowMs: env.rateLimitWindow * 1000,
- max: env.rateLimitMax,
+ const sessionLimiter = rateLimit({
+ windowMs: 60000,
+ max: 10,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: req => generateHmac(getIP(req), ipSalt),
+ handler: handleRateExceeded
+ });
+
+ const apiLimiter = rateLimit({
+ windowMs: env.rateLimitWindow * 1000,
+ max: (req) => req.rateLimitMax || env.rateLimitMax,
+ standardHeaders: true,
+ legacyHeaders: false,
+ keyGenerator: req => req.rateLimitKey || generateHmac(getIP(req), ipSalt),
+ handler: handleRateExceeded
+ })
+
+ const apiTunnelLimiter = rateLimit({
+ windowMs: env.rateLimitWindow * 1000,
+ max: (req) => req.rateLimitMax || env.rateLimitMax,
+ standardHeaders: true,
+ legacyHeaders: false,
+ keyGenerator: req => req.rateLimitKey || generateHmac(getIP(req), ipSalt),
handler: (req, res) => {
return res.sendStatus(429)
}
@@ -102,23 +110,45 @@ export const runAPI = (express, app, __dirname) => {
...corsConfig,
}));
- app.post('/', apiLimiter);
- app.use('/tunnel', apiLimiterStream);
-
app.post('/', (req, res, next) => {
if (!acceptRegex.test(req.header('Accept'))) {
return fail(res, "error.api.header.accept");
}
-
if (!acceptRegex.test(req.header('Content-Type'))) {
return fail(res, "error.api.header.content_type");
}
-
next();
});
app.post('/', (req, res, next) => {
- if (!env.turnstileSecret || !env.jwtSecret) {
+ if (!env.apiKeyURL) {
+ return next();
+ }
+
+ const { success, error } = APIKeys.validateAuthorization(req);
+ if (!success) {
+ // We call next() here if either if:
+ // a) we have user sessions enabled, meaning the request
+ // will still need a Bearer token to not be rejected, or
+ // b) we do not require the user to be authenticated, and
+ // so they can just make the request with the regular
+ // rate limit configuration;
+ // otherwise, we reject the request.
+ if (
+ (env.sessionEnabled || !env.authRequired)
+ && ['missing', 'not_api_key'].includes(error)
+ ) {
+ return next();
+ }
+
+ return fail(res, `error.api.auth.key.${error}`);
+ }
+
+ return next();
+ });
+
+ app.post('/', (req, res, next) => {
+ if (!env.sessionEnabled || req.rateLimitKey) {
return next();
}
@@ -140,14 +170,16 @@ export const runAPI = (express, app, __dirname) => {
return fail(res, "error.api.auth.jwt.invalid");
}
- req.authorized = true;
+ req.rateLimitKey = generateHmac(req.header("Authorization"), ipSalt);
} catch {
return fail(res, "error.api.generic");
}
next();
});
+ app.post('/', apiLimiter);
app.use('/', express.json({ limit: 1024 }));
+
app.use('/', (err, _, res, next) => {
if (err) {
const { status, body } = createResponse("error", {
@@ -159,8 +191,8 @@ export const runAPI = (express, app, __dirname) => {
next();
});
- app.post("/session", async (req, res) => {
- if (!env.turnstileSecret || !env.jwtSecret) {
+ app.post("/session", sessionLimiter, async (req, res) => {
+ if (!env.sessionEnabled) {
return fail(res, "error.api.auth.not_configured")
}
@@ -229,7 +261,7 @@ export const runAPI = (express, app, __dirname) => {
}
})
- app.get('/tunnel', (req, res) => {
+ app.get('/tunnel', apiTunnelLimiter, (req, res) => {
const id = String(req.query.id);
const exp = String(req.query.exp);
const sig = String(req.query.sig);
@@ -311,6 +343,10 @@ export const runAPI = (express, app, __dirname) => {
setGlobalDispatcher(new ProxyAgent(env.externalProxy))
}
+ if (env.apiKeyURL) {
+ APIKeys.setup(env.apiKeyURL);
+ }
+
app.listen(env.apiPort, env.listenAddress, () => {
console.log(`\n` +
Bright(Cyan("cobalt ")) + Bright("API ^ω^") + "\n" +
diff --git a/api/src/misc/console-text.js b/api/src/misc/console-text.js
index 014584ae..6ce747d7 100644
--- a/api/src/misc/console-text.js
+++ b/api/src/misc/console-text.js
@@ -5,12 +5,19 @@ function t(color, tt) {
export function Bright(tt) {
return t("\x1b[1m", tt)
}
+
export function Red(tt) {
return t("\x1b[31m", tt)
}
+
export function Green(tt) {
return t("\x1b[32m", tt)
}
+
export function Cyan(tt) {
return t("\x1b[36m", tt)
}
+
+export function Yellow(tt) {
+ return t("\x1b[93m", tt)
+}
diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js
index 4fdb24f6..31d12e7f 100644
--- a/api/src/processing/match-action.js
+++ b/api/src/processing/match-action.js
@@ -137,6 +137,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
}
break;
+ case "ok":
case "vk":
case "tiktok":
params = { type: "proxy" };
diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js
index f091d448..8d8bf4ac 100644
--- a/api/src/processing/service-config.js
+++ b/api/src/processing/service-config.js
@@ -137,7 +137,8 @@ export const services = {
":user/status/:id/video/:index",
":user/status/:id/photo/:index",
":user/status/:id/mediaviewer",
- ":user/status/:id/mediaViewer"
+ ":user/status/:id/mediaViewer",
+ "i/bookmarks?post_id=:id"
],
subdomains: ["mobile"],
altDomains: ["x.com", "vxtwitter.com", "fixvx.com"],
diff --git a/api/src/processing/services/youtube.js b/api/src/processing/services/youtube.js
index cf83fbd0..46f72a5b 100644
--- a/api/src/processing/services/youtube.js
+++ b/api/src/processing/services/youtube.js
@@ -243,11 +243,13 @@ export default async function(o) {
}
if (basicInfo?.short_description?.startsWith("Provided to YouTube by")) {
- let descItems = basicInfo.short_description.split("\n\n");
- fileMetadata.album = descItems[2];
- fileMetadata.copyright = descItems[3];
- if (descItems[4].startsWith("Released on:")) {
- fileMetadata.date = descItems[4].replace("Released on: ", '').trim()
+ let descItems = basicInfo.short_description.split("\n\n", 5);
+ if (descItems.length === 5) {
+ fileMetadata.album = descItems[2];
+ fileMetadata.copyright = descItems[3];
+ if (descItems[4].startsWith("Released on:")) {
+ fileMetadata.date = descItems[4].replace("Released on: ", '').trim();
+ }
}
}
diff --git a/api/src/processing/url.js b/api/src/processing/url.js
index a8e69937..034a5d73 100644
--- a/api/src/processing/url.js
+++ b/api/src/processing/url.js
@@ -120,6 +120,11 @@ function cleanURL(url) {
limitQuery('p')
}
break;
+ case "twitter":
+ if (url.searchParams.get('post_id')) {
+ limitQuery('post_id')
+ }
+ break;
}
if (stripQuery) {
diff --git a/api/src/security/api-keys.js b/api/src/security/api-keys.js
new file mode 100644
index 00000000..eee48da3
--- /dev/null
+++ b/api/src/security/api-keys.js
@@ -0,0 +1,205 @@
+import { env } from "../config.js";
+import { readFile } from "node:fs/promises";
+import { Yellow } from "../misc/console-text.js";
+import ip from "ipaddr.js";
+
+// this function is a modified variation of code
+// from https://stackoverflow.com/a/32402438/14855621
+const generateWildcardRegex = rule => {
+ var escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
+ return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$");
+}
+
+const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
+
+let keys = {};
+
+const ALLOWED_KEYS = new Set(['name', 'ips', 'userAgents', 'limit']);
+
+/* Expected format pseudotype:
+** type KeyFileContents = Record<
+** UUIDv4String,
+** {
+** name?: string,
+** limit?: number | "unlimited",
+** ips?: CIDRString[],
+** userAgents?: string[]
+** }
+** >;
+*/
+
+const validateKeys = (input) => {
+ if (typeof input !== 'object' || input === null) {
+ throw "input is not an object";
+ }
+
+ if (Object.keys(input).some(x => !UUID_REGEX.test(x))) {
+ throw "key file contains invalid key(s)";
+ }
+
+ Object.values(input).forEach(details => {
+ if (typeof details !== 'object' || details === null) {
+ throw "some key(s) are incorrectly configured";
+ }
+
+ const unexpected_key = Object.keys(details).find(k => !ALLOWED_KEYS.has(k));
+ if (unexpected_key) {
+ throw "detail object contains unexpected key: " + unexpected_key;
+ }
+
+ if (details.limit && details.limit !== 'unlimited') {
+ if (typeof details.limit !== 'number')
+ throw "detail object contains invalid limit (not a number)";
+ else if (details.limit < 1)
+ throw "detail object contains invalid limit (not a positive number)";
+ }
+
+ if (details.ips) {
+ if (!Array.isArray(details.ips))
+ throw "details object contains value for `ips` which is not an array";
+
+ const invalid_ip = details.ips.find(
+ addr => typeof addr !== 'string' || (!ip.isValidCIDR(addr) && !ip.isValid(addr))
+ );
+
+ if (invalid_ip) {
+ throw "`ips` in details contains an invalid IP or CIDR range: " + invalid_ip;
+ }
+ }
+
+ if (details.userAgents) {
+ if (!Array.isArray(details.userAgents))
+ throw "details object contains value for `userAgents` which is not an array";
+
+ const invalid_ua = details.userAgents.find(ua => typeof ua !== 'string');
+ if (invalid_ua) {
+ throw "`userAgents` in details contains an invalid user agent: " + invalid_ua;
+ }
+ }
+ });
+}
+
+const formatKeys = (keyData) => {
+ const formatted = {};
+
+ for (let key in keyData) {
+ const data = keyData[key];
+ key = key.toLowerCase();
+
+ formatted[key] = {};
+
+ if (data.limit) {
+ if (data.limit === "unlimited") {
+ data.limit = Infinity;
+ }
+
+ formatted[key].limit = data.limit;
+ }
+
+ if (data.ips) {
+ formatted[key].ips = data.ips.map(addr => {
+ if (ip.isValid(addr)) {
+ return [ ip.parse(addr), 32 ];
+ }
+
+ return ip.parseCIDR(addr);
+ });
+ }
+
+ if (data.userAgents) {
+ formatted[key].userAgents = data.userAgents.map(generateWildcardRegex);
+ }
+ }
+
+ return formatted;
+}
+
+const loadKeys = async (source) => {
+ let updated;
+ if (source.protocol === 'file:') {
+ const pathname = source.pathname === '/' ? '' : source.pathname;
+ updated = JSON.parse(
+ await readFile(
+ decodeURIComponent(source.host + pathname),
+ 'utf8'
+ )
+ );
+ } else {
+ updated = await fetch(source).then(a => a.json());
+ }
+
+ validateKeys(updated);
+ keys = formatKeys(updated);
+}
+
+const wrapLoad = (url) => {
+ loadKeys(url)
+ .then(() => {})
+ .catch((e) => {
+ console.error(`${Yellow('[!]')} Failed loading API keys at ${new Date().toISOString()}.`);
+ console.error('Error:', e);
+ })
+}
+
+const err = (reason) => ({ success: false, error: reason });
+
+export const validateAuthorization = (req) => {
+ const authHeader = req.get('Authorization');
+
+ if (typeof authHeader !== 'string') {
+ return err("missing");
+ }
+
+ const [ authType, keyString ] = authHeader.split(' ', 2);
+ if (authType.toLowerCase() !== 'api-key') {
+ return err("not_api_key");
+ }
+
+ if (!UUID_REGEX.test(keyString) || `${authType} ${keyString}` !== authHeader) {
+ return err("invalid");
+ }
+
+ const matchingKey = keys[keyString.toLowerCase()];
+ if (!matchingKey) {
+ return err("not_found");
+ }
+
+ if (matchingKey.ips) {
+ let addr;
+ try {
+ addr = ip.parse(req.ip);
+ } catch {
+ return err("invalid_ip");
+ }
+
+ const ip_allowed = matchingKey.ips.some(
+ ([ allowed, size ]) => {
+ return addr.kind() === allowed.kind()
+ && addr.match(allowed, size);
+ }
+ );
+
+ if (!ip_allowed) {
+ return err("ip_not_allowed");
+ }
+ }
+
+ if (matchingKey.userAgents) {
+ const userAgent = req.get('User-Agent');
+ if (!matchingKey.userAgents.some(regex => regex.test(userAgent))) {
+ return err("ua_not_allowed");
+ }
+ }
+
+ req.rateLimitKey = keyString.toLowerCase();
+ req.rateLimitMax = matchingKey.limit;
+
+ return { success: true };
+}
+
+export const setup = (url) => {
+ wrapLoad(url);
+ if (env.keyReloadInterval > 0) {
+ setInterval(() => wrapLoad(url), env.keyReloadInterval * 1000);
+ }
+}
diff --git a/api/src/util/setup.js b/api/src/util/setup.js
index e9d1beae..34b870cb 100644
--- a/api/src/util/setup.js
+++ b/api/src/util/setup.js
@@ -19,7 +19,7 @@ let final = () => {
}
console.log(Bright("\nAwesome! I've created a fresh .env file for you."));
console.log(`${Bright("Now I'll run")} ${Cyan("npm install")} ${Bright("to install all dependencies. It shouldn't take long.\n\n")}`);
- execSync('npm install', { stdio: [0, 1, 2] });
+ execSync('pnpm install', { stdio: [0, 1, 2] });
console.log(`\n\n${Cyan("All done!\n")}`);
console.log(Bright("You can re-run this script at any time to update the configuration."));
console.log(Bright("\nYou're now ready to start cobalt. Simply run ") + Cyan("npm start") + Bright('!\nHave fun :)'));
diff --git a/api/src/util/tests.json b/api/src/util/tests.json
index 17952595..94005c0a 100644
--- a/api/src/util/tests.json
+++ b/api/src/util/tests.json
@@ -192,6 +192,24 @@
"code": 400,
"status": "error"
}
+ },
+ {
+ "name": "bookmarked video",
+ "url": "https://twitter.com/i/bookmarks?post_id=1828099210220294314",
+ "params": {},
+ "expected": {
+ "code": 200,
+ "status": "redirect"
+ }
+ },
+ {
+ "name": "bookmarked photo",
+ "url": "https://twitter.com/i/bookmarks?post_id=1837430141179289876",
+ "params": {},
+ "expected": {
+ "code": 200,
+ "status": "redirect"
+ }
}
],
"soundcloud": [
diff --git a/docs/run-an-instance.md b/docs/run-an-instance.md
index 8144c037..08654d9f 100644
--- a/docs/run-an-instance.md
+++ b/docs/run-an-instance.md
@@ -71,6 +71,9 @@ sudo service nscd start
| `RATELIMIT_WINDOW` | `60` | `120` | rate limit time window in **seconds**. |
| `RATELIMIT_MAX` | `20` | `30` | max requests per time window. requests above this amount will be blocked for the rate limit window duration. |
| `DURATION_LIMIT` | `10800` | `18000` | max allowed video duration in **seconds**. |
+| `TUNNEL_LIFESPAN` | `90` | `120` | the duration for which tunnel info is stored in ram, **in seconds**. |
+| `API_KEY_URL` | ➖ | `file://keys.json` | the location of the api key database. for loading API keys, cobalt supports HTTP(S) urls, or local files by specifying a local path using the `file://` protocol. see the "api key file format" below for more details. |
+| `API_AUTH_REQUIRED` | ➖ | `1` | when set to `1`, the user always needs to be authenticated in some way before they can access the API (either via an api key or via turnstile, if enabled). |
\* the higher the nice value, the lower the priority. [read more here](https://en.wikipedia.org/wiki/Nice_(Unix)).
@@ -79,3 +82,55 @@ setting a `FREEBIND_CIDR` allows cobalt to pick a random IP for every download a
requests it makes for that particular download. to use freebind in cobalt, you need to follow its [setup instructions](https://github.com/imputnet/freebind.js?tab=readme-ov-file#setup) first. if you configure this option while running cobalt
in a docker container, you also need to set the `API_LISTEN_ADDRESS` env to `127.0.0.1`, and set
`network_mode` for the container to `host`.
+
+#### api key file format
+the file is a JSON-serialized object with the following structure:
+```typescript
+
+type KeyFileContents = Record<
+ UUIDv4String,
+ {
+ name?: string,
+ limit?: number | "unlimited",
+ ips?: (CIDRString | IPString)[],
+ userAgents?: string[]
+ }
+>;
+```
+
+where *`UUIDv4String`* is a stringified version of a UUIDv4 identifier.
+- **name** is a field for your own reference, it is not used by cobalt anywhere.
+
+- **`limit`** specifies how many requests the API key can make during the window specified in the `RATELIMIT_WINDOW` env.
+ - when omitted, the limit specified in `RATELIMIT_MAX` will be used.
+ - it can be also set to `"unlimited"`, in which case the API key bypasses all rate limits.
+
+- **`ips`** contains an array of allowlisted IP ranges, which can be specified both as individual ips or CIDR ranges (e.g. *`["192.168.42.69", "2001:db8::48", "10.0.0.0/8", "fe80::/10"]`*).
+ - when specified, only requests from these ip ranges can use the specified api key.
+ - when omitted, any IP can be used to make requests with that API key.
+
+- **`userAgents`** contains an array of allowed user agents, with support for wildcards (e.g. *`["cobaltbot/1.0", "Mozilla/5.0 * Chrome/*"]`*).
+ - when specified, requests with a `user-agent` that does not appear in this array will be rejected.
+ - when omitted, any user agent can be specified to make requests with that API key.
+
+- if both `ips` and `userAgents` are set, the tokens will be limited by both parameters.
+- if cobalt detects any problem with your key file, it will be ignored and a warning will be printed to the console.
+
+an example key file could look like this:
+```json
+{
+ "b5c7160a-b655-4c7a-b500-de839f094550": {
+ "limit": 10,
+ "ips": ["10.0.0.0/8", "192.168.42.42"],
+ "userAgents": ["*Chrome*"]
+ },
+ "b00b1234-a3e5-99b1-c6d1-dba4512ae190": {
+ "limit": "unlimited",
+ "ips": ["192.168.1.2"],
+ "userAgents": ["cobaltbot/1.0"]
+ }
+}
+```
+
+if you are configuring a key file, **do not use the UUID from the example** but instead generate your own. you can do this by running the following command if you have node.js installed:
+`node -e "console.log(crypto.randomUUID())"`
diff --git a/packages/api-client/package.json b/packages/api-client/package.json
index 676d22bb..560d1af9 100644
--- a/packages/api-client/package.json
+++ b/packages/api-client/package.json
@@ -9,7 +9,7 @@
"license": "MIT",
"devDependencies": {
"prettier": "3.3.3",
- "tsup": "^8.2.4",
+ "tsup": "^8.3.0",
"typescript": "^5.4.5"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9d316277..71456e11 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,11 +26,11 @@ importers:
specifier: ^0.14.51
version: 0.14.54
express:
- specifier: ^4.18.1
- version: 4.19.2
+ specifier: ^4.21.0
+ version: 4.21.0
express-rate-limit:
specifier: ^6.3.0
- version: 6.11.2(express@4.19.2)
+ version: 6.11.2(express@4.21.0)
ffmpeg-static:
specifier: ^5.1.0
version: 5.2.0
@@ -38,8 +38,8 @@ importers:
specifier: ^0.10.7
version: 0.10.9
ipaddr.js:
- specifier: 2.1.0
- version: 2.1.0
+ specifier: 2.2.0
+ version: 2.2.0
nanoid:
specifier: ^4.0.2
version: 4.0.2
@@ -59,8 +59,8 @@ importers:
specifier: 1.0.3
version: 1.0.3
youtubei.js:
- specifier: ^10.3.0
- version: 10.3.0
+ specifier: ^10.5.0
+ version: 10.5.0
zod:
specifier: ^3.23.8
version: 3.23.8
@@ -75,8 +75,8 @@ importers:
specifier: 3.3.3
version: 3.3.3
tsup:
- specifier: ^8.2.4
- version: 8.2.4(postcss@8.4.40)(typescript@5.5.4)
+ specifier: ^8.3.0
+ version: 8.3.0(postcss@8.4.47)(typescript@5.5.4)
typescript:
specifier: ^5.4.5
version: 5.5.4
@@ -84,50 +84,37 @@ importers:
packages/version-info: {}
web:
- dependencies:
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.5.0
+ version: 9.8.0
'@fontsource-variable/noto-sans-mono':
specifier: ^5.0.20
version: 5.0.20
'@fontsource/ibm-plex-mono':
specifier: ^5.0.13
version: 5.0.13
+ '@fontsource/redaction-10':
+ specifier: ^5.0.2
+ version: 5.0.2
'@imput/libav.js-remux-cli':
specifier: ^5.5.6
version: 5.5.6
'@imput/version-info':
specifier: workspace:^
version: link:../packages/version-info
- '@tabler/icons-svelte':
- specifier: 3.6.0
- version: 3.6.0(svelte@4.2.18)
- '@vitejs/plugin-basic-ssl':
- specifier: ^1.1.0
- version: 1.1.0(vite@5.3.5(@types/node@20.14.14))
- mime:
- specifier: ^4.0.4
- version: 4.0.4
- sveltekit-i18n:
- specifier: ^2.4.2
- version: 2.4.2(svelte@4.2.18)
- ts-deepmerge:
- specifier: ^7.0.0
- version: 7.0.1
- devDependencies:
- '@eslint/js':
- specifier: ^9.5.0
- version: 9.8.0
- '@fontsource/redaction-10':
- specifier: ^5.0.2
- version: 5.0.2
'@sveltejs/adapter-static':
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.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))
'@sveltejs/kit':
specifier: ^2.0.0
- version: 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: 2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
'@sveltejs/vite-plugin-svelte':
specifier: ^3.0.0
- version: 3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14))
+ version: 3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
+ '@tabler/icons-svelte':
+ specifier: 3.6.0
+ version: 3.6.0(svelte@4.2.19)
'@types/eslint__js':
specifier: ^8.42.3
version: 8.42.3
@@ -137,9 +124,15 @@ importers:
'@types/node':
specifier: ^20.14.10
version: 20.14.14
+ '@vitejs/plugin-basic-ssl':
+ specifier: ^1.1.0
+ version: 1.1.0(vite@5.4.8(@types/node@20.14.14))
compare-versions:
specifier: ^6.1.0
version: 6.1.1
+ dotenv:
+ specifier: ^16.0.1
+ version: 16.4.5
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -148,16 +141,25 @@ importers:
version: 10.4.5
mdsvex:
specifier: ^0.11.2
- version: 0.11.2(svelte@4.2.18)
+ version: 0.11.2(svelte@4.2.19)
+ mime:
+ specifier: ^4.0.4
+ version: 4.0.4
svelte:
- specifier: ^4.2.7
- version: 4.2.18
+ specifier: ^4.2.19
+ version: 4.2.19
svelte-check:
specifier: ^3.6.0
- version: 3.8.5(postcss@8.4.40)(svelte@4.2.18)
+ version: 3.8.5(postcss@8.4.47)(svelte@4.2.19)
svelte-preprocess:
specifier: ^6.0.2
- version: 6.0.2(postcss@8.4.40)(svelte@4.2.18)(typescript@5.5.4)
+ version: 6.0.2(postcss@8.4.47)(svelte@4.2.19)(typescript@5.5.4)
+ sveltekit-i18n:
+ specifier: ^2.4.2
+ version: 2.4.2(svelte@4.2.19)
+ ts-deepmerge:
+ specifier: ^7.0.1
+ version: 7.0.1
tslib:
specifier: ^2.4.1
version: 2.6.3
@@ -168,11 +170,11 @@ importers:
specifier: ^5.4.5
version: 5.5.4
typescript-eslint:
- specifier: ^7.13.1
- version: 7.18.0(eslint@8.57.0)(typescript@5.5.4)
+ specifier: ^8.8.0
+ version: 8.8.0(eslint@8.57.0)(typescript@5.5.4)
vite:
- specifier: ^5.0.3
- version: 5.3.5(@types/node@20.14.14)
+ specifier: ^5.3.6
+ version: 5.4.8(@types/node@20.14.14)
packages:
@@ -180,6 +182,9 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@bufbuild/protobuf@2.1.0':
+ resolution: {integrity: sha512-+2Mx67Y3skJ4NCD/qNSdBJNWtu6x6Qr53jeNg+QcwiL6mt0wK+3jwHH2x1p7xaYH6Ve2JKOVn0OxU35WsmqI9A==}
+
'@derhuerst/http-basic@8.2.4':
resolution: {integrity: sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==}
engines: {node: '>=6.0.0'}
@@ -564,83 +569,83 @@ packages:
'@polka/url@1.0.0-next.25':
resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==}
- '@rollup/rollup-android-arm-eabi@4.19.2':
- resolution: {integrity: sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==}
+ '@rollup/rollup-android-arm-eabi@4.24.0':
+ resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==}
cpu: [arm]
os: [android]
- '@rollup/rollup-android-arm64@4.19.2':
- resolution: {integrity: sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==}
+ '@rollup/rollup-android-arm64@4.24.0':
+ resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==}
cpu: [arm64]
os: [android]
- '@rollup/rollup-darwin-arm64@4.19.2':
- resolution: {integrity: sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==}
+ '@rollup/rollup-darwin-arm64@4.24.0':
+ resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==}
cpu: [arm64]
os: [darwin]
- '@rollup/rollup-darwin-x64@4.19.2':
- resolution: {integrity: sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==}
+ '@rollup/rollup-darwin-x64@4.24.0':
+ resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==}
cpu: [x64]
os: [darwin]
- '@rollup/rollup-linux-arm-gnueabihf@4.19.2':
- resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==}
+ '@rollup/rollup-linux-arm-gnueabihf@4.24.0':
+ resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm-musleabihf@4.19.2':
- resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==}
+ '@rollup/rollup-linux-arm-musleabihf@4.24.0':
+ resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm]
os: [linux]
- '@rollup/rollup-linux-arm64-gnu@4.19.2':
- resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==}
+ '@rollup/rollup-linux-arm64-gnu@4.24.0':
+ resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-arm64-musl@4.19.2':
- resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==}
+ '@rollup/rollup-linux-arm64-musl@4.24.0':
+ resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64]
os: [linux]
- '@rollup/rollup-linux-powerpc64le-gnu@4.19.2':
- resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==}
+ '@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
+ resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64]
os: [linux]
- '@rollup/rollup-linux-riscv64-gnu@4.19.2':
- resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==}
+ '@rollup/rollup-linux-riscv64-gnu@4.24.0':
+ resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64]
os: [linux]
- '@rollup/rollup-linux-s390x-gnu@4.19.2':
- resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==}
+ '@rollup/rollup-linux-s390x-gnu@4.24.0':
+ resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x]
os: [linux]
- '@rollup/rollup-linux-x64-gnu@4.19.2':
- resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==}
+ '@rollup/rollup-linux-x64-gnu@4.24.0':
+ resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-linux-x64-musl@4.19.2':
- resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==}
+ '@rollup/rollup-linux-x64-musl@4.24.0':
+ resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64]
os: [linux]
- '@rollup/rollup-win32-arm64-msvc@4.19.2':
- resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==}
+ '@rollup/rollup-win32-arm64-msvc@4.24.0':
+ resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
cpu: [arm64]
os: [win32]
- '@rollup/rollup-win32-ia32-msvc@4.19.2':
- resolution: {integrity: sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==}
+ '@rollup/rollup-win32-ia32-msvc@4.24.0':
+ resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==}
cpu: [ia32]
os: [win32]
- '@rollup/rollup-win32-x64-msvc@4.19.2':
- resolution: {integrity: sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==}
+ '@rollup/rollup-win32-x64-msvc@4.24.0':
+ resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==}
cpu: [x64]
os: [win32]
@@ -701,6 +706,9 @@ packages:
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+ '@types/estree@1.0.6':
+ resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
'@types/fluent-ffmpeg@2.1.25':
resolution: {integrity: sha512-a9/Jtv/RVaCG4lUwWIcuClWE5eXJFoFS/oHOecOv/RS8n+lQdJzcJVmDlxA8Xbk4B82YpO88Dijcoljb6sYTcA==}
@@ -719,63 +727,62 @@ packages:
'@types/unist@2.0.10':
resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==}
- '@typescript-eslint/eslint-plugin@7.18.0':
- resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/eslint-plugin@8.8.0':
+ resolution: {integrity: sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^7.0.0
- eslint: ^8.56.0
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
- '@typescript-eslint/parser@7.18.0':
- resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/parser@8.8.0':
+ resolution: {integrity: sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
+ eslint: ^8.57.0 || ^9.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
- '@typescript-eslint/scope-manager@7.18.0':
- resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/scope-manager@8.8.0':
+ resolution: {integrity: sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/type-utils@7.18.0':
- resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==}
- engines: {node: ^18.18.0 || >=20.0.0}
- peerDependencies:
- eslint: ^8.56.0
- typescript: '*'
- peerDependenciesMeta:
- typescript:
- optional: true
-
- '@typescript-eslint/types@7.18.0':
- resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==}
- engines: {node: ^18.18.0 || >=20.0.0}
-
- '@typescript-eslint/typescript-estree@7.18.0':
- resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/type-utils@8.8.0':
+ resolution: {integrity: sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
- '@typescript-eslint/utils@7.18.0':
- resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==}
- engines: {node: ^18.18.0 || >=20.0.0}
- peerDependencies:
- eslint: ^8.56.0
+ '@typescript-eslint/types@8.8.0':
+ resolution: {integrity: sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/visitor-keys@7.18.0':
- resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ '@typescript-eslint/typescript-estree@8.8.0':
+ resolution: {integrity: sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@typescript-eslint/utils@8.8.0':
+ resolution: {integrity: sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+
+ '@typescript-eslint/visitor-keys@8.8.0':
+ resolution: {integrity: sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.2.0':
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
@@ -839,10 +846,6 @@ packages:
array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
- array-union@2.1.0:
- resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
- engines: {node: '>=8'}
-
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
engines: {node: '>= 0.4'}
@@ -854,8 +857,8 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
- body-parser@1.20.2:
- resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
+ body-parser@1.20.3:
+ resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
brace-expansion@1.1.11:
@@ -1017,10 +1020,6 @@ packages:
devalue@5.0.0:
resolution: {integrity: sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==}
- dir-glob@3.0.1:
- resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
- engines: {node: '>=8'}
-
doctrine@3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
@@ -1045,6 +1044,10 @@ packages:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -1255,8 +1258,8 @@ packages:
peerDependencies:
express: ^4 || ^5
- express@4.19.2:
- resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
+ express@4.21.0:
+ resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
engines: {node: '>= 0.10.0'}
fast-deep-equal@3.1.3:
@@ -1275,6 +1278,14 @@ packages:
fastq@1.17.1:
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+ fdir@6.4.0:
+ resolution: {integrity: sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
ffmpeg-static@5.2.0:
resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==}
engines: {node: '>=16'}
@@ -1287,8 +1298,8 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
- finalhandler@1.2.0:
- resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+ finalhandler@1.3.1:
+ resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
engines: {node: '>= 0.8'}
find-up@5.0.0:
@@ -1359,10 +1370,6 @@ packages:
globalyzer@0.1.0:
resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==}
- globby@11.1.0:
- resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
- engines: {node: '>=10'}
-
globrex@0.1.2:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
@@ -1446,6 +1453,10 @@ packages:
resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==}
engines: {node: '>= 10'}
+ ipaddr.js@2.2.0:
+ resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
+ engines: {node: '>= 10'}
+
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -1556,8 +1567,8 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
- merge-descriptors@1.0.1:
- resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+ merge-descriptors@1.0.3:
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -1728,12 +1739,8 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'}
- path-to-regexp@0.1.7:
- resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
-
- path-type@4.0.0:
- resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
- engines: {node: '>=8'}
+ path-to-regexp@0.1.10:
+ resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
periscopic@3.1.0:
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
@@ -1741,10 +1748,17 @@ packages:
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
+ picocolors@1.1.0:
+ resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
+ picomatch@4.0.2:
+ resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+ engines: {node: '>=12'}
+
pirates@4.0.6:
resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
engines: {node: '>= 6'}
@@ -1767,8 +1781,8 @@ packages:
yaml:
optional: true
- postcss@8.4.40:
- resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
+ postcss@8.4.47:
+ resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -1802,8 +1816,8 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- qs@6.11.0:
- resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+ qs@6.13.0:
+ resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
queue-microtask@1.2.3:
@@ -1847,8 +1861,8 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
- rollup@4.19.2:
- resolution: {integrity: sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==}
+ rollup@4.24.0:
+ resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
@@ -1873,12 +1887,12 @@ packages:
engines: {node: '>=10'}
hasBin: true
- send@0.18.0:
- resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+ send@0.19.0:
+ resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
- serve-static@1.15.0:
- resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+ serve-static@1.16.2:
+ resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
set-cookie-parser@2.6.0:
@@ -1914,10 +1928,6 @@ packages:
resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
engines: {node: '>= 10'}
- slash@3.0.0:
- resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
- engines: {node: '>=8'}
-
sorcery@0.11.1:
resolution: {integrity: sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==}
hasBin: true
@@ -1926,6 +1936,10 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
@@ -2060,8 +2074,8 @@ packages:
typescript:
optional: true
- svelte@4.2.18:
- resolution: {integrity: sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==}
+ svelte@4.2.19:
+ resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==}
engines: {node: '>=16'}
sveltekit-i18n@2.4.2:
@@ -2085,6 +2099,10 @@ packages:
tiny-glob@0.2.9:
resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==}
+ tinyglobby@0.2.9:
+ resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==}
+ engines: {node: '>=12.0.0'}
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -2120,8 +2138,8 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
- tsup@8.2.4:
- resolution: {integrity: sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==}
+ tsup@8.3.0:
+ resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
@@ -2157,11 +2175,10 @@ packages:
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
- typescript-eslint@7.18.0:
- resolution: {integrity: sha512-PonBkP603E3tt05lDkbOMyaxJjvKqQrXsnow72sVeOFINDE/qNmnnd+f9b4N+U7W6MXnnYyrhtmF2t08QWwUbA==}
- engines: {node: ^18.18.0 || >=20.0.0}
+ typescript-eslint@8.8.0:
+ resolution: {integrity: sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- eslint: ^8.56.0
typescript: '*'
peerDependenciesMeta:
typescript:
@@ -2207,8 +2224,8 @@ packages:
vfile-message@2.0.4:
resolution: {integrity: sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==}
- vite@5.3.5:
- resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
+ vite@5.4.8:
+ resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
@@ -2216,6 +2233,7 @@ packages:
less: '*'
lightningcss: ^1.21.0
sass: '*'
+ sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
@@ -2228,6 +2246,8 @@ packages:
optional: true
sass:
optional: true
+ sass-embedded:
+ optional: true
stylus:
optional: true
sugarss:
@@ -2273,8 +2293,8 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- youtubei.js@10.3.0:
- resolution: {integrity: sha512-tLmeJCECK2xF2hZZtF2nEqirdKVNLFSDpa0LhTaXY3tngtL7doQXyy7M2CLueramDTlmCnFaW+rctHirTPFaRQ==}
+ youtubei.js@10.5.0:
+ resolution: {integrity: sha512-iyA+VF28c15tCCKH9ExM2RKC3zYiHzA/eixGlJ3vERANkuI+xYKzAZ4vtOhmyqwrAddu88R/DkzEsmpph5NWjg==}
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@@ -2286,6 +2306,8 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
+ '@bufbuild/protobuf@2.1.0': {}
+
'@derhuerst/http-basic@8.2.4':
dependencies:
caseless: 0.12.0
@@ -2527,61 +2549,61 @@ snapshots:
'@polka/url@1.0.0-next.25': {}
- '@rollup/rollup-android-arm-eabi@4.19.2':
+ '@rollup/rollup-android-arm-eabi@4.24.0':
optional: true
- '@rollup/rollup-android-arm64@4.19.2':
+ '@rollup/rollup-android-arm64@4.24.0':
optional: true
- '@rollup/rollup-darwin-arm64@4.19.2':
+ '@rollup/rollup-darwin-arm64@4.24.0':
optional: true
- '@rollup/rollup-darwin-x64@4.19.2':
+ '@rollup/rollup-darwin-x64@4.24.0':
optional: true
- '@rollup/rollup-linux-arm-gnueabihf@4.19.2':
+ '@rollup/rollup-linux-arm-gnueabihf@4.24.0':
optional: true
- '@rollup/rollup-linux-arm-musleabihf@4.19.2':
+ '@rollup/rollup-linux-arm-musleabihf@4.24.0':
optional: true
- '@rollup/rollup-linux-arm64-gnu@4.19.2':
+ '@rollup/rollup-linux-arm64-gnu@4.24.0':
optional: true
- '@rollup/rollup-linux-arm64-musl@4.19.2':
+ '@rollup/rollup-linux-arm64-musl@4.24.0':
optional: true
- '@rollup/rollup-linux-powerpc64le-gnu@4.19.2':
+ '@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
optional: true
- '@rollup/rollup-linux-riscv64-gnu@4.19.2':
+ '@rollup/rollup-linux-riscv64-gnu@4.24.0':
optional: true
- '@rollup/rollup-linux-s390x-gnu@4.19.2':
+ '@rollup/rollup-linux-s390x-gnu@4.24.0':
optional: true
- '@rollup/rollup-linux-x64-gnu@4.19.2':
+ '@rollup/rollup-linux-x64-gnu@4.24.0':
optional: true
- '@rollup/rollup-linux-x64-musl@4.19.2':
+ '@rollup/rollup-linux-x64-musl@4.24.0':
optional: true
- '@rollup/rollup-win32-arm64-msvc@4.19.2':
+ '@rollup/rollup-win32-arm64-msvc@4.24.0':
optional: true
- '@rollup/rollup-win32-ia32-msvc@4.19.2':
+ '@rollup/rollup-win32-ia32-msvc@4.24.0':
optional: true
- '@rollup/rollup-win32-x64-msvc@4.19.2':
+ '@rollup/rollup-win32-x64-msvc@4.24.0':
optional: true
- '@sveltejs/adapter-static@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)))':
+ '@sveltejs/adapter-static@3.0.2(@sveltejs/kit@2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))':
dependencies:
- '@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))
+ '@sveltejs/kit': 2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
- '@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))':
+ '@sveltejs/kit@2.5.19(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))':
dependencies:
- '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14))
+ '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
'@types/cookie': 0.6.0
cookie: 0.6.0
devalue: 5.0.0
@@ -2593,43 +2615,43 @@ snapshots:
sade: 1.8.1
set-cookie-parser: 2.6.0
sirv: 2.0.4
- svelte: 4.2.18
+ svelte: 4.2.19
tiny-glob: 0.2.9
- vite: 5.3.5(@types/node@20.14.14)
+ vite: 5.4.8(@types/node@20.14.14)
- '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@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))':
+ '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))':
dependencies:
- '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14))
+ '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
debug: 4.3.6
- svelte: 4.2.18
- vite: 5.3.5(@types/node@20.14.14)
+ svelte: 4.2.19
+ vite: 5.4.8(@types/node@20.14.14)
transitivePeerDependencies:
- supports-color
- '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.5(@types/node@20.14.14))':
+ '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))':
dependencies:
- '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@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))
+ '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14)))(svelte@4.2.19)(vite@5.4.8(@types/node@20.14.14))
debug: 4.3.6
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.11
- svelte: 4.2.18
- svelte-hmr: 0.16.0(svelte@4.2.18)
- vite: 5.3.5(@types/node@20.14.14)
- vitefu: 0.2.5(vite@5.3.5(@types/node@20.14.14))
+ svelte: 4.2.19
+ svelte-hmr: 0.16.0(svelte@4.2.19)
+ vite: 5.4.8(@types/node@20.14.14)
+ vitefu: 0.2.5(vite@5.4.8(@types/node@20.14.14))
transitivePeerDependencies:
- supports-color
- '@sveltekit-i18n/base@1.3.7(svelte@4.2.18)':
+ '@sveltekit-i18n/base@1.3.7(svelte@4.2.19)':
dependencies:
- svelte: 4.2.18
+ svelte: 4.2.19
'@sveltekit-i18n/parser-default@1.1.1': {}
- '@tabler/icons-svelte@3.6.0(svelte@4.2.18)':
+ '@tabler/icons-svelte@3.6.0(svelte@4.2.19)':
dependencies:
'@tabler/icons': 3.6.0
- svelte: 4.2.18
+ svelte: 4.2.19
'@tabler/icons@3.6.0': {}
@@ -2646,6 +2668,8 @@ snapshots:
'@types/estree@1.0.5': {}
+ '@types/estree@1.0.6': {}
+
'@types/fluent-ffmpeg@2.1.25':
dependencies:
'@types/node': 20.14.14
@@ -2662,14 +2686,14 @@ snapshots:
'@types/unist@2.0.10': {}
- '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)':
+ '@typescript-eslint/eslint-plugin@8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)':
dependencies:
'@eslint-community/regexpp': 4.11.0
- '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/visitor-keys': 7.18.0
+ '@typescript-eslint/parser': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/scope-manager': 8.8.0
+ '@typescript-eslint/type-utils': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/utils': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/visitor-keys': 8.8.0
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
@@ -2680,12 +2704,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4)':
+ '@typescript-eslint/parser@8.8.0(eslint@8.57.0)(typescript@5.5.4)':
dependencies:
- '@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/types': 7.18.0
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
- '@typescript-eslint/visitor-keys': 7.18.0
+ '@typescript-eslint/scope-manager': 8.8.0
+ '@typescript-eslint/types': 8.8.0
+ '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.5.4)
+ '@typescript-eslint/visitor-keys': 8.8.0
debug: 4.3.6
eslint: 8.57.0
optionalDependencies:
@@ -2693,31 +2717,31 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@7.18.0':
+ '@typescript-eslint/scope-manager@8.8.0':
dependencies:
- '@typescript-eslint/types': 7.18.0
- '@typescript-eslint/visitor-keys': 7.18.0
+ '@typescript-eslint/types': 8.8.0
+ '@typescript-eslint/visitor-keys': 8.8.0
- '@typescript-eslint/type-utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)':
+ '@typescript-eslint/type-utils@8.8.0(eslint@8.57.0)(typescript@5.5.4)':
dependencies:
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
- '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.5.4)
+ '@typescript-eslint/utils': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
debug: 4.3.6
- eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.4)
optionalDependencies:
typescript: 5.5.4
transitivePeerDependencies:
+ - eslint
- supports-color
- '@typescript-eslint/types@7.18.0': {}
+ '@typescript-eslint/types@8.8.0': {}
- '@typescript-eslint/typescript-estree@7.18.0(typescript@5.5.4)':
+ '@typescript-eslint/typescript-estree@8.8.0(typescript@5.5.4)':
dependencies:
- '@typescript-eslint/types': 7.18.0
- '@typescript-eslint/visitor-keys': 7.18.0
+ '@typescript-eslint/types': 8.8.0
+ '@typescript-eslint/visitor-keys': 8.8.0
debug: 4.3.6
- globby: 11.1.0
+ fast-glob: 3.3.2
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.3
@@ -2727,27 +2751,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@7.18.0(eslint@8.57.0)(typescript@5.5.4)':
+ '@typescript-eslint/utils@8.8.0(eslint@8.57.0)(typescript@5.5.4)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
- '@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/types': 7.18.0
- '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.5.4)
+ '@typescript-eslint/scope-manager': 8.8.0
+ '@typescript-eslint/types': 8.8.0
+ '@typescript-eslint/typescript-estree': 8.8.0(typescript@5.5.4)
eslint: 8.57.0
transitivePeerDependencies:
- supports-color
- typescript
- '@typescript-eslint/visitor-keys@7.18.0':
+ '@typescript-eslint/visitor-keys@8.8.0':
dependencies:
- '@typescript-eslint/types': 7.18.0
+ '@typescript-eslint/types': 8.8.0
eslint-visitor-keys: 3.4.3
'@ungap/structured-clone@1.2.0': {}
- '@vitejs/plugin-basic-ssl@1.1.0(vite@5.3.5(@types/node@20.14.14))':
+ '@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.8(@types/node@20.14.14))':
dependencies:
- vite: 5.3.5(@types/node@20.14.14)
+ vite: 5.4.8(@types/node@20.14.14)
accepts@1.3.8:
dependencies:
@@ -2798,15 +2822,13 @@ snapshots:
array-flatten@1.1.1: {}
- array-union@2.1.0: {}
-
axobject-query@4.1.0: {}
balanced-match@1.0.2: {}
binary-extensions@2.3.0: {}
- body-parser@1.20.2:
+ body-parser@1.20.3:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
@@ -2816,7 +2838,7 @@ snapshots:
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
- qs: 6.11.0
+ qs: 6.13.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
@@ -2965,10 +2987,6 @@ snapshots:
devalue@5.0.0: {}
- dir-glob@3.0.1:
- dependencies:
- path-type: 4.0.0
-
doctrine@3.0.0:
dependencies:
esutils: 2.0.3
@@ -2985,6 +3003,8 @@ snapshots:
encodeurl@1.0.2: {}
+ encodeurl@2.0.0: {}
+
env-paths@2.2.1: {}
es-define-property@1.0.0:
@@ -3224,38 +3244,38 @@ snapshots:
signal-exit: 3.0.7
strip-final-newline: 2.0.0
- express-rate-limit@6.11.2(express@4.19.2):
+ express-rate-limit@6.11.2(express@4.21.0):
dependencies:
- express: 4.19.2
+ express: 4.21.0
- express@4.19.2:
+ express@4.21.0:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
- body-parser: 1.20.2
+ body-parser: 1.20.3
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.6.0
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
- encodeurl: 1.0.2
+ encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
- finalhandler: 1.2.0
+ finalhandler: 1.3.1
fresh: 0.5.2
http-errors: 2.0.0
- merge-descriptors: 1.0.1
+ merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
- path-to-regexp: 0.1.7
+ path-to-regexp: 0.1.10
proxy-addr: 2.0.7
- qs: 6.11.0
+ qs: 6.13.0
range-parser: 1.2.1
safe-buffer: 5.2.1
- send: 0.18.0
- serve-static: 1.15.0
+ send: 0.19.0
+ serve-static: 1.16.2
setprototypeof: 1.2.0
statuses: 2.0.1
type-is: 1.6.18
@@ -3282,6 +3302,10 @@ snapshots:
dependencies:
reusify: 1.0.4
+ fdir@6.4.0(picomatch@4.0.2):
+ optionalDependencies:
+ picomatch: 4.0.2
+
ffmpeg-static@5.2.0:
dependencies:
'@derhuerst/http-basic': 8.2.4
@@ -3299,10 +3323,10 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
- finalhandler@1.2.0:
+ finalhandler@1.3.1:
dependencies:
debug: 2.6.9
- encodeurl: 1.0.2
+ encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
@@ -3389,15 +3413,6 @@ snapshots:
globalyzer@0.1.0: {}
- globby@11.1.0:
- dependencies:
- array-union: 2.1.0
- dir-glob: 3.0.1
- fast-glob: 3.3.2
- ignore: 5.3.1
- merge2: 1.4.1
- slash: 3.0.0
-
globrex@0.1.2: {}
gopd@1.0.1:
@@ -3469,7 +3484,10 @@ snapshots:
ipaddr.js@1.9.1: {}
- ipaddr.js@2.1.0: {}
+ ipaddr.js@2.1.0:
+ optional: true
+
+ ipaddr.js@2.2.0: {}
is-binary-path@2.1.0:
dependencies:
@@ -3552,17 +3570,17 @@ snapshots:
mdn-data@2.0.30: {}
- mdsvex@0.11.2(svelte@4.2.18):
+ mdsvex@0.11.2(svelte@4.2.19):
dependencies:
'@types/unist': 2.0.10
prism-svelte: 0.4.7
prismjs: 1.29.0
- svelte: 4.2.18
+ svelte: 4.2.19
vfile-message: 2.0.4
media-typer@0.3.0: {}
- merge-descriptors@1.0.1: {}
+ merge-descriptors@1.0.3: {}
merge-stream@2.0.0: {}
@@ -3693,9 +3711,7 @@ snapshots:
lru-cache: 10.4.3
minipass: 7.1.2
- path-to-regexp@0.1.7: {}
-
- path-type@4.0.0: {}
+ path-to-regexp@0.1.10: {}
periscopic@3.1.0:
dependencies:
@@ -3705,21 +3721,25 @@ snapshots:
picocolors@1.0.1: {}
+ picocolors@1.1.0: {}
+
picomatch@2.3.1: {}
+ picomatch@4.0.2: {}
+
pirates@4.0.6: {}
- postcss-load-config@6.0.1(postcss@8.4.40):
+ postcss-load-config@6.0.1(postcss@8.4.47):
dependencies:
lilconfig: 3.1.2
optionalDependencies:
- postcss: 8.4.40
+ postcss: 8.4.47
- postcss@8.4.40:
+ postcss@8.4.47:
dependencies:
nanoid: 3.3.7
- picocolors: 1.0.1
- source-map-js: 1.2.0
+ picocolors: 1.1.0
+ source-map-js: 1.2.1
prelude-ls@1.2.1: {}
@@ -3740,7 +3760,7 @@ snapshots:
punycode@2.3.1: {}
- qs@6.11.0:
+ qs@6.13.0:
dependencies:
side-channel: 1.0.6
@@ -3779,26 +3799,26 @@ snapshots:
dependencies:
glob: 7.2.3
- rollup@4.19.2:
+ rollup@4.24.0:
dependencies:
- '@types/estree': 1.0.5
+ '@types/estree': 1.0.6
optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.19.2
- '@rollup/rollup-android-arm64': 4.19.2
- '@rollup/rollup-darwin-arm64': 4.19.2
- '@rollup/rollup-darwin-x64': 4.19.2
- '@rollup/rollup-linux-arm-gnueabihf': 4.19.2
- '@rollup/rollup-linux-arm-musleabihf': 4.19.2
- '@rollup/rollup-linux-arm64-gnu': 4.19.2
- '@rollup/rollup-linux-arm64-musl': 4.19.2
- '@rollup/rollup-linux-powerpc64le-gnu': 4.19.2
- '@rollup/rollup-linux-riscv64-gnu': 4.19.2
- '@rollup/rollup-linux-s390x-gnu': 4.19.2
- '@rollup/rollup-linux-x64-gnu': 4.19.2
- '@rollup/rollup-linux-x64-musl': 4.19.2
- '@rollup/rollup-win32-arm64-msvc': 4.19.2
- '@rollup/rollup-win32-ia32-msvc': 4.19.2
- '@rollup/rollup-win32-x64-msvc': 4.19.2
+ '@rollup/rollup-android-arm-eabi': 4.24.0
+ '@rollup/rollup-android-arm64': 4.24.0
+ '@rollup/rollup-darwin-arm64': 4.24.0
+ '@rollup/rollup-darwin-x64': 4.24.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.24.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.24.0
+ '@rollup/rollup-linux-arm64-gnu': 4.24.0
+ '@rollup/rollup-linux-arm64-musl': 4.24.0
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.24.0
+ '@rollup/rollup-linux-s390x-gnu': 4.24.0
+ '@rollup/rollup-linux-x64-gnu': 4.24.0
+ '@rollup/rollup-linux-x64-musl': 4.24.0
+ '@rollup/rollup-win32-arm64-msvc': 4.24.0
+ '@rollup/rollup-win32-ia32-msvc': 4.24.0
+ '@rollup/rollup-win32-x64-msvc': 4.24.0
fsevents: 2.3.3
run-parallel@1.2.0:
@@ -3822,7 +3842,7 @@ snapshots:
semver@7.6.3: {}
- send@0.18.0:
+ send@0.19.0:
dependencies:
debug: 2.6.9
depd: 2.0.0
@@ -3840,12 +3860,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- serve-static@1.15.0:
+ serve-static@1.16.2:
dependencies:
- encodeurl: 1.0.2
+ encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
- send: 0.18.0
+ send: 0.19.0
transitivePeerDependencies:
- supports-color
@@ -3885,8 +3905,6 @@ snapshots:
mrmime: 2.0.0
totalist: 3.0.1
- slash@3.0.0: {}
-
sorcery@0.11.1:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
@@ -3896,6 +3914,8 @@ snapshots:
source-map-js@1.2.0: {}
+ source-map-js@1.2.1: {}
+
source-map@0.8.0-beta.0:
dependencies:
whatwg-url: 7.1.0
@@ -3948,14 +3968,14 @@ snapshots:
dependencies:
has-flag: 4.0.0
- svelte-check@3.8.5(postcss@8.4.40)(svelte@4.2.18):
+ svelte-check@3.8.5(postcss@8.4.47)(svelte@4.2.19):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
chokidar: 3.6.0
picocolors: 1.0.1
sade: 1.8.1
- svelte: 4.2.18
- svelte-preprocess: 5.1.4(postcss@8.4.40)(svelte@4.2.18)(typescript@5.5.4)
+ svelte: 4.2.19
+ svelte-preprocess: 5.1.4(postcss@8.4.47)(svelte@4.2.19)(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- '@babel/core'
@@ -3968,30 +3988,30 @@ snapshots:
- stylus
- sugarss
- svelte-hmr@0.16.0(svelte@4.2.18):
+ svelte-hmr@0.16.0(svelte@4.2.19):
dependencies:
- svelte: 4.2.18
+ svelte: 4.2.19
- svelte-preprocess@5.1.4(postcss@8.4.40)(svelte@4.2.18)(typescript@5.5.4):
+ svelte-preprocess@5.1.4(postcss@8.4.47)(svelte@4.2.19)(typescript@5.5.4):
dependencies:
'@types/pug': 2.0.10
detect-indent: 6.1.0
magic-string: 0.30.11
sorcery: 0.11.1
strip-indent: 3.0.0
- svelte: 4.2.18
+ svelte: 4.2.19
optionalDependencies:
- postcss: 8.4.40
+ postcss: 8.4.47
typescript: 5.5.4
- svelte-preprocess@6.0.2(postcss@8.4.40)(svelte@4.2.18)(typescript@5.5.4):
+ svelte-preprocess@6.0.2(postcss@8.4.47)(svelte@4.2.19)(typescript@5.5.4):
dependencies:
- svelte: 4.2.18
+ svelte: 4.2.19
optionalDependencies:
- postcss: 8.4.40
+ postcss: 8.4.47
typescript: 5.5.4
- svelte@4.2.18:
+ svelte@4.2.19:
dependencies:
'@ampproject/remapping': 2.3.0
'@jridgewell/sourcemap-codec': 1.5.0
@@ -4008,11 +4028,11 @@ snapshots:
magic-string: 0.30.11
periscopic: 3.1.0
- sveltekit-i18n@2.4.2(svelte@4.2.18):
+ sveltekit-i18n@2.4.2(svelte@4.2.19):
dependencies:
- '@sveltekit-i18n/base': 1.3.7(svelte@4.2.18)
+ '@sveltekit-i18n/base': 1.3.7(svelte@4.2.19)
'@sveltekit-i18n/parser-default': 1.1.1
- svelte: 4.2.18
+ svelte: 4.2.19
syscall-napi@0.0.6:
optional: true
@@ -4032,6 +4052,11 @@ snapshots:
globalyzer: 0.1.0
globrex: 0.1.2
+ tinyglobby@0.2.9:
+ dependencies:
+ fdir: 6.4.0(picomatch@4.0.2)
+ picomatch: 4.0.2
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -4056,7 +4081,7 @@ snapshots:
tslib@2.6.3: {}
- tsup@8.2.4(postcss@8.4.40)(typescript@5.5.4):
+ tsup@8.3.0(postcss@8.4.47)(typescript@5.5.4):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.0)
cac: 6.7.14
@@ -4065,17 +4090,17 @@ snapshots:
debug: 4.3.6
esbuild: 0.23.0
execa: 5.1.1
- globby: 11.1.0
joycon: 3.1.1
- picocolors: 1.0.1
- postcss-load-config: 6.0.1(postcss@8.4.40)
+ picocolors: 1.1.0
+ postcss-load-config: 6.0.1(postcss@8.4.47)
resolve-from: 5.0.0
- rollup: 4.19.2
+ rollup: 4.24.0
source-map: 0.8.0-beta.0
sucrase: 3.35.0
+ tinyglobby: 0.2.9
tree-kill: 1.2.2
optionalDependencies:
- postcss: 8.4.40
+ postcss: 8.4.47
typescript: 5.5.4
transitivePeerDependencies:
- jiti
@@ -4098,15 +4123,15 @@ snapshots:
typedarray@0.0.6: {}
- typescript-eslint@7.18.0(eslint@8.57.0)(typescript@5.5.4):
+ typescript-eslint@8.8.0(eslint@8.57.0)(typescript@5.5.4):
dependencies:
- '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- '@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.5.4)
- eslint: 8.57.0
+ '@typescript-eslint/eslint-plugin': 8.8.0(@typescript-eslint/parser@8.8.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/parser': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
+ '@typescript-eslint/utils': 8.8.0(eslint@8.57.0)(typescript@5.5.4)
optionalDependencies:
typescript: 5.5.4
transitivePeerDependencies:
+ - eslint
- supports-color
typescript@5.5.4: {}
@@ -4140,18 +4165,18 @@ snapshots:
'@types/unist': 2.0.10
unist-util-stringify-position: 2.0.3
- vite@5.3.5(@types/node@20.14.14):
+ vite@5.4.8(@types/node@20.14.14):
dependencies:
esbuild: 0.21.5
- postcss: 8.4.40
- rollup: 4.19.2
+ postcss: 8.4.47
+ rollup: 4.24.0
optionalDependencies:
'@types/node': 20.14.14
fsevents: 2.3.3
- vitefu@0.2.5(vite@5.3.5(@types/node@20.14.14)):
+ vitefu@0.2.5(vite@5.4.8(@types/node@20.14.14)):
optionalDependencies:
- vite: 5.3.5(@types/node@20.14.14)
+ vite: 5.4.8(@types/node@20.14.14)
webidl-conversions@4.0.2: {}
@@ -4183,8 +4208,9 @@ snapshots:
yocto-queue@0.1.0: {}
- youtubei.js@10.3.0:
+ youtubei.js@10.5.0:
dependencies:
+ '@bufbuild/protobuf': 2.1.0
jintr: 2.1.1
tslib: 2.6.3
undici: 5.28.4
diff --git a/web/README.md b/web/README.md
index b4122d39..c528f6e5 100644
--- a/web/README.md
+++ b/web/README.md
@@ -15,7 +15,6 @@ them, you must specify them when building the frontend (or running a vite server
| `WEB_HOST` | `cobalt.tools` | domain on which the frontend will be running. used for meta tags and configuring plausible. |
| `WEB_PLAUSIBLE_HOST` | `plausible.io`* | enables plausible analytics with provided hostname as receiver backend. |
| `WEB_DEFAULT_API` | `https://api.cobalt.tools/` | changes url which is used for api requests by frontend clients. |
-| `WEB_TURNSTILE_KEY` | `1x00000000000000000000AA` | [cloudflare turnstile](https://www.cloudflare.com/products/turnstile/) public key for antibot protection |
\* don't use plausible.io as receiver backend unless you paid for their cloud service.
use your own domain when hosting community edition of plausible. refer to their [docs](https://plausible.io/docs) when needed.
diff --git a/web/changelogs/10.0.md b/web/changelogs/10.0.md
index 86da6d3b..0e5add71 100644
--- a/web/changelogs/10.0.md
+++ b/web/changelogs/10.0.md
@@ -2,8 +2,8 @@
title: "cobalt, reborn"
date: "9 Sept, 2024"
banner:
- file: "cobalt10.png"
- alt: "image of meowbalt smiling and loafing in front of a wall of text."
+ file: "cobalt10.webp"
+ alt: "meowth plush staring into a screen with cobalt 10 ui shown."
---
everything is new! this update marks the start of the latest chapter for cobalt. we spent the entire summer working hard to deliver the best experience ever, and we really hope you enjoy the rebirth of cobalt.
diff --git a/web/changelogs/10.1.md b/web/changelogs/10.1.md
new file mode 100644
index 00000000..885018d2
--- /dev/null
+++ b/web/changelogs/10.1.md
@@ -0,0 +1,55 @@
+---
+title: "squashing bugs, improving security and ux"
+date: "1 Oct, 2024"
+banner:
+ file: "meowth101hammer.webp"
+ alt: "meowth plush getting squished with a hammer."
+---
+
+this update enhances the cobalt experience all around, here's everything that we added or changed since 10.0:
+
+### saving improvements:
+- youtube videos encoded in av1 are now downloaded in the webm container. they also include opus audio for the best quality all around.
+- fixed various bugs related to the download process on older devices/browsers. cobalt should work everywhere within sane limits.
+- fixed downloading of twitch clips.
+- fixed a bug where cobalt wouldn't download bluesky videos that are in a post with a quote.
+- fixed a bug that caused some youtube music videos to fail to download due to differently formatted metadata.
+- cobalt will no longer unexpectedly open video files on iOS. instead, a dialog with other options will be shown. this had to be done due to missing "download" button in safari's video player. you can override this by enabling [forced tunneling](/settings/privacy#tunnel).
+- fixed a bug in filename generation where certain information was added to the filename even if cobalt didn't have it (such as youtube video format).
+
+### general ui/ux improvements:
+- added a button to quickly copy a link to the section in settings or about page.
+- added `(remux)` to filenames of remuxed videos to distinguish them from the original file.
+- improved the look & behavior of the sidebar.
+- fixed cursor appearance to update correctly when using the sidebar or subpage navigation.
+- added a stepped scroller to the donation options card [on the donate page](/donate).
+- tweaked the [donate page](/donate) layout to be cleaner and more flexible.
+- fixed tab navigation for donation option buttons.
+- updated the [10.0 changelog banner](/updates#10.0) to be less boring.
+- fixed a bug that caused some changelog dates to be displayed a day later than intended.
+- changelog banner can now be saved with a right click.
+- cobalt version now gently fades in on the [settings page](/settings).
+- fixed the position of the notch easter egg on iPhone XR, 11, 16 Pro, and 16 Pro Max.
+- cobalt will let you paste the link even if the anti-bot check isn't completed yet. if anything goes wrong regarding anti-bot checks, cobalt will let you know.
+- fixed a bunch of typos and minor grammatical errors.
+- other minor changes.
+
+### about page improvements:
+- added motivation section to the [general about page](/about/general).
+- added a list of beta testers to the [credits page](/about/credits).
+- rephrased some about sections to improve clarity and readability.
+- made about page body narrower to be easier to read.
+- added extra padding between sections on about page to increase readability.
+
+### internal improvements:
+- cobalt now preloads server info for quicker access to supported services & loading turnstile on demand.
+- converted all elements and the about page to be translatable in preparations for community-sourced translations *(coming soon!)*.
+- added `content-security-policy` header to restrict and better prevent XSS attacks.
+- moved the turnstile bot check key to the server, making it load the script on the client only if necessary.
+- fixed a bug in the api that allowed for making requests without a valid `Accept` header if authentication wasn't enabled on an instance.
+
+you can also check [all commits since the 10.0 release on github](https://github.com/imputnet/cobalt/compare/08bc5022...f461b02f).
+
+we hope you enjoy this stable update and have a wonderful day!
+
+\~ your friends at imput ❤️
diff --git a/web/i18n/en/a11y/save.json b/web/i18n/en/a11y/save.json
index 3a92de64..2dc85154 100644
--- a/web/i18n/en/a11y/save.json
+++ b/web/i18n/en/a11y/save.json
@@ -1,5 +1,6 @@
{
"link_area": "link input area",
+ "link_area.turnstile": "link input area. checking if you're not a robot.",
"clear_input": "clear input",
"download": "download",
"download.think": "processing the link...",
diff --git a/web/i18n/en/about.json b/web/i18n/en/about.json
index e566faaa..cfe9129f 100644
--- a/web/i18n/en/about.json
+++ b/web/i18n/en/about.json
@@ -12,5 +12,20 @@
"community.twitter": "news account on twitter",
"community.github": "github repo",
"community.email": "support email",
- "community.telegram": "news channel on telegram"
+ "community.telegram": "news channel on telegram",
+
+ "heading.general": "general terms",
+ "heading.licenses": "licenses",
+ "heading.summary": "best way to save what you love",
+ "heading.privacy": "leading privacy",
+ "heading.community": "open community",
+ "heading.local": "on-device processing",
+ "heading.saving": "saving",
+ "heading.encryption": "encryption",
+ "heading.plausible": "anonymous traffic analytics",
+ "heading.cloudflare": "web privacy & security",
+ "heading.responsibility": "user responsibilities",
+ "heading.abuse": "reporting abuse",
+ "heading.motivation": "motivation",
+ "heading.testers": "beta testers"
}
diff --git a/web/i18n/en/about/credits.md b/web/i18n/en/about/credits.md
new file mode 100644
index 00000000..6c001f58
--- /dev/null
+++ b/web/i18n/en/about/credits.md
@@ -0,0 +1,52 @@
+
+
+
+
+
+huge shoutout to our thing breakers for testing updates early and making sure they're stable.
+they also helped us ship cobalt 10!
+
+
+all links are external and lead to their personal websites or social media.
+
+
+
+
+
+meowbalt is cobalt's speedy mascot. he is an extremely expressive cat that loves fast internet.
+
+all amazing drawings of meowbalt that you see in cobalt were made by [GlitchyPSI](https://glitchypsi.xyz/).
+he is also the original designer of the character.
+
+you cannot use or modify GlitchyPSI's artworks of meowbalt without his explicit permission.
+
+you cannot use or modify the meowbalt character design commercially or in any form that isn't fan art.
+
+
+
+
+
+cobalt processing server is open source and licensed under [AGPL-3.0]({docs.apiLicense}).
+
+cobalt frontend is [source first](https://sourcefirst.com/) and licensed under [CC-BY-NC-SA 4.0]({docs.webLicense}).
+we decided to use this license to stop grifters from profiting off our work
+& from creating malicious clones that deceive people and hurt our public identity.
+
+we rely on many open source libraries, create & distribute our own.
+you can see the full list of dependencies on [github]({contacts.github}).
+
diff --git a/web/i18n/en/about/general.md b/web/i18n/en/about/general.md
new file mode 100644
index 00000000..2334ab8e
--- /dev/null
+++ b/web/i18n/en/about/general.md
@@ -0,0 +1,78 @@
+
+
+
+
+
+cobalt helps you save anything from your favorite websites: video, audio, photos or gifs. just paste the link and you're ready to rock!
+
+no ads, trackers, paywalls, or other nonsense. just a convenient web app that works anywhere, whenever you need it.
+
+
+
+
+
+cobalt was created for public benefit, to protect people from ads and malware pushed by its alternatives.
+we believe that the best software is safe, open, and accessible.
+
+it's possible to keep the main instances up thanks to our long-standing infrastructure partner, [royalehosting.net]({partners.royalehosting})!
+
+
+
+
+
+all requests to the backend are anonymous and all information about tunnels is encrypted.
+we have a strict zero log policy and don't track *anything* about individual people.
+
+when a request needs additional processing, cobalt processes files on-the-fly.
+it's done by tunneling processed parts directly to the client, without ever saving anything to disk.
+for example, this method is used when the source service provides video and audio channels as separate files.
+
+additionally, you can [enable forced tunneling](/settings/privacy#tunnel) to protect your privacy.
+when enabled, cobalt will tunnel all downloaded files.
+no one will know where you download something from, even your network provider.
+all they'll see is that you're using a cobalt instance.
+
+
+
+
+
+
+
+newest features, such as [remuxing](/remux), work locally on your device.
+on-device processing is efficient and never sends anything over the internet.
+it perfectly aligns with our future goal of moving as much processing as possible to the client.
+
diff --git a/web/i18n/en/about/privacy.md b/web/i18n/en/about/privacy.md
new file mode 100644
index 00000000..b19ca762
--- /dev/null
+++ b/web/i18n/en/about/privacy.md
@@ -0,0 +1,76 @@
+
+
+
+
+
+cobalt's privacy policy is simple: we don't collect or store anything about you. what you do is solely your business, not ours or anyone else's.
+
+these terms are applicable only when using the official cobalt instance. in other cases, you may need to contact the hoster for accurate info.
+
+
+
+
+
+tools that use on-device processing work offline, locally, and never send any data anywhere. they are explicitly marked as such whenever applicable.
+
+
+
+
+
+when using saving functionality, in some cases cobalt will encrypt & temporarily store information needed for tunneling. it's stored in processing server's RAM for 90 seconds and irreversibly purged afterwards. no one has access to it, even instance owners, as long as they don't modify the official cobalt image.
+
+processed/tunneled files are never cached anywhere. everything is tunneled live. cobalt's saving functionality is essentially a fancy proxy service.
+
+
+
+
+
+temporarily stored tunnel data is encrypted using the AES-256 standard. decryption keys are only included in the access link and never logged/cached/stored anywhere. only the end user has access to the link & encryption keys. keys are generated uniquely for each requested tunnel.
+
+
+{#if env.PLAUSIBLE_ENABLED}
+
+
+
+for sake of privacy, we use [plausible's anonymous traffic analytics](https://plausible.io/) to get an approximate number of active cobalt users. no identifiable information about you or your requests is ever stored. all data is anonymized and aggregated. the plausible instance we use is hosted & managed by us.
+
+plausible doesn't use cookies and is fully compliant with GDPR, CCPA, and PECR.
+
+[learn more about plausible's dedication to privacy.](https://plausible.io/privacy-focused-web-analytics)
+
+if you wish to opt out of anonymous analytics, you can do it in privacy settings .
+
+{/if}
+
+
+
+
+we use cloudflare services for ddos & bot protection. we also use cloudflare pages for deploying & hosting the static web app. all of these are required to provide the best experience for everyone. it's the most private & reliable provider that we know of.
+
+cloudflare is fully compliant with GDPR and HIPAA.
+
+[learn more about cloudflare's dedication to privacy.](https://www.cloudflare.com/trust-hub/privacy-and-data-protection/)
+
diff --git a/web/i18n/en/about/terms.md b/web/i18n/en/about/terms.md
new file mode 100644
index 00000000..a134ab84
--- /dev/null
+++ b/web/i18n/en/about/terms.md
@@ -0,0 +1,56 @@
+
+
+
+
+
+these terms are applicable only when using the official cobalt instance.
+in other cases, you may need to contact the hoster for accurate info.
+
+
+
+
+
+saving functionality simplifies downloading content from the internet and takes zero liability for what the saved content is used for.
+processing servers work like advanced proxies and don't ever write any content to disk.
+everything is handled in RAM and permanently purged once the tunnel is done.
+we have no downloading logs and can't identify anyone.
+
+[you can read more about how tunnels work in our privacy policy.](/about/privacy)
+
+
+
+
+
+you (end user) are responsible for what you do with our tools, how you use and distribute resulting content.
+please be mindful when using content of others and always credit original creators.
+make sure you don't violate any terms or licenses.
+
+when used in educational purposes, always cite sources and credit original creators.
+
+fair use and credits benefit everyone.
+
+
+
+
+
+we have no way of detecting abusive behavior automatically, as cobalt is 100% anonymous.
+however, you can report such activities to us and we will do our best to comply manually: [safety@imput.net](mailto:safety@imput.net)
+
+please note that this email is not intended for user support.
+if you're experiencing issues, contact us via any preferred method on [the support page](/about/community).
+
diff --git a/web/i18n/en/button.json b/web/i18n/en/button.json
index 849ac98d..1ea7fb41 100644
--- a/web/i18n/en/button.json
+++ b/web/i18n/en/button.json
@@ -7,6 +7,7 @@
"download": "download",
"share": "share",
"copy": "copy",
+ "copy.section": "copy the section link",
"copied": "copied",
"import": "import",
"continue": "continue",
diff --git a/web/i18n/en/donate.json b/web/i18n/en/donate.json
index bf99eb80..6907e4c4 100644
--- a/web/i18n/en/donate.json
+++ b/web/i18n/en/donate.json
@@ -2,14 +2,15 @@
"banner.title": "Support a safe\nand open Internet",
"banner.subtitle": "donate to imput or share the\njoy of cobalt with a friend",
- "body.motivation": "cobalt helps thousands of producers, educators, and other creative people to do what they love. we created cobalt because we believe that internet doesn’t have to be scary. greed and ads have ruined the internet — we fight back with friendly and open tools that are made with love, not for profit.",
- "body.keep_going": "you can help us stay motivated & keep creating safe alternatives to abusive tools by sharing cobalt with a friend or donating.",
+ "body.motivation": "cobalt helps producers, educators, video makers, and many others to do what they love. it's a different kind of service that is made with love, not for profit.",
+ "body.no_bullshit": "we believe that the internet doesn't have to be scary, which is why cobalt will never have ads or other kinds of malicious content. it's a promise that we firmly stand by. everything we do is built with privacy, accessibility, and ease of use in mind, making cobalt available for everyone.",
+ "body.keep_going": "if you found cobalt useful, please consider supporting our work! you can help us by making a donation or sharing cobalt with a friend. every donation is highly appreciated and helps us keep working on cobalt and other projects.",
"card.once": "one-time donation",
- "card.monthly": "monthly donation",
+ "card.recurring": "recurring donation",
"card.custom": "custom amount (from $2)",
- "card.processor": "processed by {{value}}",
+ "card.processor": "via {{value}}",
"card.option.5": "cup of coffee",
"card.option.10": "full size pizza",
diff --git a/web/i18n/en/error.json b/web/i18n/en/error.json
index 6a730052..67dd911c 100644
--- a/web/i18n/en/error.json
+++ b/web/i18n/en/error.json
@@ -8,6 +8,8 @@
"tunnel.probe": "couldn't verify whether you can download this file. try again in a few seconds!",
+ "captcha_ongoing": "still checking if you're not a bot. wait for the spinner to disappear and try again.\n\nif it takes too long, please let us know! we use cloudflare turnstile for bot protection and sometimes it blocks people for no reason.",
+
"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!",
@@ -36,7 +38,7 @@
"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.live": "this video is currently live, so i can't download it yet. wait for the live stream 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!",
@@ -45,7 +47,7 @@
"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.codec": "youtube didn't return anything with your preferred video codec. try another one in 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!"
diff --git a/web/i18n/en/settings.json b/web/i18n/en/settings.json
index 90b2e0f6..0537982f 100644
--- a/web/i18n/en/settings.json
+++ b/web/i18n/en/settings.json
@@ -50,7 +50,7 @@
"audio.bitrate": "audio bitrate",
"audio.bitrate.kbps": "kb/s",
- "audio.bitrate.description": "bitrate applies only to audio conversion. cobalt can't improve the source audio quality, so choosing a bitrate over 128kbps may inflate the file size with no audible difference. perceived quality may vary by format.",
+ "audio.bitrate.description": "bitrate is applied only when converting audio to a lossy format. cobalt can't improve the source audio quality, so choosing a bitrate over 128kbps may inflate the file size with no audible difference. perceived quality may vary by format.",
"audio.youtube.dub": "youtube",
"audio.youtube.dub.title": "use browser language for dubbed videos",
@@ -91,7 +91,7 @@
"language.auto.title": "automatic selection",
"language.auto.description": "cobalt will use your browser's default language if translation is available. if not, english will be used instead.",
"language.preferred.title": "preferred language",
- "language.preferred.description": "this language will be used when automatic selection is disabled. any text that isn't translated will be displayed in english.",
+ "language.preferred.description": "this language will be used when automatic selection is disabled. any text that isn't translated will be displayed in english.\n\nwe use community-sourced translations for languages other than english, russian, and czech. they may be inaccurate or incomplete.",
"privacy.analytics": "anonymous traffic analytics",
"privacy.analytics.title": "don't contribute to analytics",
diff --git a/web/package.json b/web/package.json
index 67c779bc..bd2854b5 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@imput/cobalt-web",
- "version": "10.0.0",
+ "version": "10.1.0",
"type": "module",
"private": true,
"scripts": {
@@ -25,35 +25,34 @@
"homepage": "https://cobalt.tools/",
"devDependencies": {
"@eslint/js": "^9.5.0",
+ "@fontsource-variable/noto-sans-mono": "^5.0.20",
+ "@fontsource/ibm-plex-mono": "^5.0.13",
"@fontsource/redaction-10": "^5.0.2",
+ "@imput/libav.js-remux-cli": "^5.5.6",
+ "@imput/version-info": "workspace:^",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
+ "@tabler/icons-svelte": "3.6.0",
"@types/eslint__js": "^8.42.3",
"@types/fluent-ffmpeg": "^2.1.25",
"@types/node": "^20.14.10",
+ "@vitejs/plugin-basic-ssl": "^1.1.0",
"compare-versions": "^6.1.0",
+ "dotenv": "^16.0.1",
"eslint": "^8.57.0",
"glob": "^10.4.5",
"mdsvex": "^0.11.2",
- "svelte": "^4.2.7",
+ "mime": "^4.0.4",
+ "svelte": "^4.2.19",
"svelte-check": "^3.6.0",
"svelte-preprocess": "^6.0.2",
+ "sveltekit-i18n": "^2.4.2",
+ "ts-deepmerge": "^7.0.1",
"tslib": "^2.4.1",
"turnstile-types": "^1.2.2",
"typescript": "^5.4.5",
- "typescript-eslint": "^7.13.1",
- "vite": "^5.0.3"
- },
- "dependencies": {
- "@fontsource-variable/noto-sans-mono": "^5.0.20",
- "@fontsource/ibm-plex-mono": "^5.0.13",
- "@imput/libav.js-remux-cli": "^5.5.6",
- "@imput/version-info": "workspace:^",
- "@tabler/icons-svelte": "3.6.0",
- "@vitejs/plugin-basic-ssl": "^1.1.0",
- "mime": "^4.0.4",
- "sveltekit-i18n": "^2.4.2",
- "ts-deepmerge": "^7.0.0"
+ "typescript-eslint": "^8.8.0",
+ "vite": "^5.3.6"
}
}
diff --git a/web/src/components/changelog/ChangelogEntry.svelte b/web/src/components/changelog/ChangelogEntry.svelte
index acb7256c..18f474ef 100644
--- a/web/src/components/changelog/ChangelogEntry.svelte
+++ b/web/src/components/changelog/ChangelogEntry.svelte
@@ -146,6 +146,7 @@
width: 100%;
aspect-ratio: 16/9;
border-radius: var(--padding);
+ pointer-events: all;
}
.changelog-banner.loading {
diff --git a/web/src/components/dialog/PickerItem.svelte b/web/src/components/dialog/PickerItem.svelte
index 7edf8c68..4a515a6e 100644
--- a/web/src/components/dialog/PickerItem.svelte
+++ b/web/src/components/dialog/PickerItem.svelte
@@ -14,6 +14,7 @@
export let number: number;
let imageLoaded = false;
+ const isTunnel = new URL(item.url).pathname === "/tunnel";
$: itemType = item.type ?? "photo";
@@ -23,6 +24,7 @@
on:click={() =>
downloadFile({
url: item.url,
+ urlType: isTunnel ? "tunnel" : "redirect",
})}
>
diff --git a/web/src/components/dialog/SavingDialog.svelte b/web/src/components/dialog/SavingDialog.svelte
index f71e2b9f..ec719aed 100644
--- a/web/src/components/dialog/SavingDialog.svelte
+++ b/web/src/components/dialog/SavingDialog.svelte
@@ -10,6 +10,8 @@
shareFile,
} from "$lib/download";
+ import type { CobaltFileUrlType } from "$lib/types/api";
+
import DialogContainer from "$components/dialog/DialogContainer.svelte";
import Meowbalt from "$components/misc/Meowbalt.svelte";
@@ -22,12 +24,14 @@
import IconFileDownload from "@tabler/icons-svelte/IconFileDownload.svelte";
import CopyIcon from "$components/misc/CopyIcon.svelte";
+
export let id: string;
export let dismissable = true;
export let bodyText: string = "";
export let url: string = "";
export let file: File | undefined = undefined;
+ export let urlType: CobaltFileUrlType | undefined = undefined;
let close: () => void;
@@ -55,7 +59,7 @@
-
- {#each Object.entries(PRESET_DONATION_AMOUNTS) as [ amount, component ]}
-
-
+
+
+ {#if !device.is.mobile}
+
+ {
+ scroll("left");
+ }}
+ >
+
+
+ {
+ scroll("right");
+ }}
+ >
+
+
+
+ {/if}
+
+
{
+ const currentPos = donateList.scrollLeft;
+ const maxPos = donateList.scrollWidth - donateList.getBoundingClientRect().width - 5;
+ showLeftScroll = currentPos > 0;
+ showRightScroll = currentPos < maxPos && currentPos !== maxPos;
+ }}
+ >
+ {#each Object.entries(PRESET_DONATION_AMOUNTS) as [amount, component]}
+
-
- {/each}
+ {/each}
+
+
-
- {$t("donate.card.processor", { value: processor })}
-
diff --git a/web/src/components/donate/DonateShareCard.svelte b/web/src/components/donate/DonateShareCard.svelte
index 3225c7ad..e73f834a 100644
--- a/web/src/components/donate/DonateShareCard.svelte
+++ b/web/src/components/donate/DonateShareCard.svelte
@@ -111,8 +111,9 @@
diff --git a/web/src/components/misc/Turnstile.svelte b/web/src/components/misc/Turnstile.svelte
index 29914995..f1c9b625 100644
--- a/web/src/components/misc/Turnstile.svelte
+++ b/web/src/components/misc/Turnstile.svelte
@@ -1,14 +1,14 @@
@@ -45,6 +49,7 @@
.settings-content.disabled {
opacity: 0.5;
+ pointer-events: none;
}
.settings-content.focus {
@@ -82,27 +87,6 @@
}
}
- .settings-content-header {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 8px;
- }
-
- .beta-label {
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 5px;
- padding: 0 5px;
- background: var(--secondary);
- color: var(--primary);
- font-size: 11px;
- font-weight: 500;
- line-height: 0;
- text-transform: uppercase;
- }
-
@media screen and (max-width: 750px) {
.settings-content {
padding: var(--padding);
diff --git a/web/src/components/sidebar/Sidebar.svelte b/web/src/components/sidebar/Sidebar.svelte
index df03e6ea..ca3a963e 100644
--- a/web/src/components/sidebar/Sidebar.svelte
+++ b/web/src/components/sidebar/Sidebar.svelte
@@ -70,7 +70,6 @@
#sidebar-tabs {
height: 100%;
- width: var(--sidebar-width);
justify-content: space-between;
padding: var(--sidebar-inner-padding);
padding-bottom: var(--border-radius);
@@ -110,7 +109,6 @@
overflow-x: scroll;
padding-bottom: 0;
padding: var(--sidebar-inner-padding) 0;
- width: unset;
height: fit-content;
}
diff --git a/web/src/components/sidebar/SidebarTab.svelte b/web/src/components/sidebar/SidebarTab.svelte
index 267d55f1..9f3b51ef 100644
--- a/web/src/components/sidebar/SidebarTab.svelte
+++ b/web/src/components/sidebar/SidebarTab.svelte
@@ -28,11 +28,6 @@
$: if (isTabActive && tab) {
showTab(tab);
-
- tab.classList.add("animate");
- setTimeout(() => {
- tab.classList.remove("animate");
- }, 250);
}
@@ -59,11 +54,11 @@
flex-direction: column;
align-items: center;
text-align: center;
- gap: 5px;
- padding: var(--padding) 5px;
+ gap: 3px;
+ padding: var(--padding) 3px;
color: var(--sidebar-highlight);
font-size: var(--sidebar-font-size);
- opacity: 0.8;
+ opacity: 0.75;
height: fit-content;
border-radius: var(--border-radius);
transition: transform 0.2s;
@@ -72,12 +67,14 @@
text-decoration-line: none;
position: relative;
scroll-behavior: smooth;
+
+ cursor: pointer;
}
.sidebar-tab :global(svg) {
stroke-width: 1.2px;
- height: 21px;
- width: 21px;
+ height: 22px;
+ width: 22px;
}
:global([data-iphone="true"] .sidebar-tab svg) {
@@ -88,19 +85,17 @@
color: var(--sidebar-bg);
background: var(--sidebar-highlight);
opacity: 1;
- transition: none;
transform: none;
+ transition: none;
+ animation: pressButton 0.3s;
+ cursor: default;
}
- :global(.sidebar-tab.animate) {
- animation: pressButton 0.2s;
- }
-
- .sidebar-tab:active:not(.active) {
+ .sidebar-tab:not(.active):active {
transform: scale(0.95);
}
- :global([data-reduce-motion="true"]) .sidebar-tab:active:not(.active) {
+ :global([data-reduce-motion="true"]) .sidebar-tab:active {
transform: none;
}
@@ -112,10 +107,10 @@
@keyframes pressButton {
0% {
- transform: scale(0.95);
+ transform: scale(0.9);
}
50% {
- transform: scale(1.02);
+ transform: scale(1.015);
}
100% {
transform: scale(1);
@@ -127,6 +122,7 @@
opacity: 1;
background-color: var(--sidebar-hover);
}
+
.sidebar-tab:hover:not(.active) {
opacity: 1;
background-color: var(--sidebar-hover);
@@ -149,10 +145,10 @@
@keyframes pressButton {
0% {
- transform: scale(0.9);
+ transform: scale(0.8);
}
- 60% {
- transform: scale(1.015);
+ 50% {
+ transform: scale(1.02);
}
100% {
transform: scale(1);
diff --git a/web/src/components/subnav/PageNav.svelte b/web/src/components/subnav/PageNav.svelte
index c3255c78..7fa46b0e 100644
--- a/web/src/components/subnav/PageNav.svelte
+++ b/web/src/components/subnav/PageNav.svelte
@@ -73,7 +73,10 @@
{:else}
{#if pageSubtitle}
-
+
{pageSubtitle}
{/if}
@@ -89,7 +92,10 @@
>
{#if isMobile && isHome && pageSubtitle}
-
+
{pageSubtitle}
{/if}
@@ -132,7 +138,7 @@
}
.subnav-page-content.wide {
- max-width: 800px;
+ max-width: 700px;
}
.subnav-page-content.padding {
@@ -170,6 +176,11 @@
.subnav-subtitle {
padding: 0;
+ transition: opacity 0.1s;
+ }
+
+ .subnav-subtitle.hidden {
+ opacity: 0;
}
.subnav-subtitle.center {
diff --git a/web/src/components/subnav/PageNavTab.svelte b/web/src/components/subnav/PageNavTab.svelte
index 380b9c03..898174fd 100644
--- a/web/src/components/subnav/PageNavTab.svelte
+++ b/web/src/components/subnav/PageNavTab.svelte
@@ -47,6 +47,8 @@
text-decoration: none;
text-decoration-line: none;
+
+ cursor: pointer;
}
.subnav-tab-left {
@@ -93,6 +95,7 @@
.subnav-tab.active {
background: var(--secondary);
color: var(--primary);
+ cursor: default;
}
.subnav-tab-text {
diff --git a/web/src/lib/api/api.ts b/web/src/lib/api/api.ts
index 74993a90..19f27c94 100644
--- a/web/src/lib/api/api.ts
+++ b/web/src/lib/api/api.ts
@@ -1,12 +1,16 @@
import { get } from "svelte/store";
import settings from "$lib/state/settings";
+import lazySettingGetter from "$lib/settings/lazy-get";
+
import { getSession } from "$lib/api/session";
import { currentApiURL } from "$lib/api/api-url";
+import { turnstileLoaded } from "$lib/state/turnstile";
import { apiOverrideWarning } from "$lib/api/safety-warning";
+import { cachedInfo, getServerInfo } from "$lib/api/server-info";
+
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));
@@ -34,11 +38,32 @@ const request = async (url: string) => {
await apiOverrideWarning();
- const usingCustomInstance = getSetting("processing", "enableCustomInstances")
- && getSetting("processing", "customInstanceURL");
+ await getServerInfo();
+
+ const getCachedInfo = get(cachedInfo);
+
+ if (!getCachedInfo) {
+ return {
+ status: "error",
+ error: {
+ code: "error.api.unreachable"
+ }
+ } as CobaltErrorResponse;
+ }
+
+ if (getCachedInfo?.info?.cobalt?.turnstileSitekey && !get(turnstileLoaded)) {
+ return {
+ status: "error",
+ error: {
+ code: "error.captcha_ongoing"
+ }
+ } as CobaltErrorResponse;
+ }
+
const api = currentApiURL();
- // FIXME: rewrite this to allow custom instances to specify their own turnstile tokens
- const session = usingCustomInstance ? undefined : await getSession();
+
+ const session = getCachedInfo?.info?.cobalt?.turnstileSitekey
+ ? await getSession() : undefined;
let extraHeaders = {}
diff --git a/web/src/lib/api/server-info.ts b/web/src/lib/api/server-info.ts
index 51a3a90a..8a3e1b60 100644
--- a/web/src/lib/api/server-info.ts
+++ b/web/src/lib/api/server-info.ts
@@ -1,3 +1,5 @@
+import { browser } from "$app/environment";
+
import { get, writable } from "svelte/store";
import { currentApiURL } from "$lib/api/api-url";
@@ -50,6 +52,17 @@ export const getServerInfo = async () => {
info: freshInfo,
origin: currentApiURL(),
});
+
+ /*
+ reload the page if turnstile sitekey changed.
+ there's no other proper way to do this, at least i couldn't find any :(
+ */
+ if (cache?.info?.cobalt?.turnstileSitekey && freshInfo?.cobalt?.turnstileSitekey) {
+ if (browser) {
+ window.location.reload();
+ }
+ }
+
return true;
}
diff --git a/web/src/lib/download.ts b/web/src/lib/download.ts
index 78e465de..827b33b1 100644
--- a/web/src/lib/download.ts
+++ b/web/src/lib/download.ts
@@ -4,16 +4,31 @@ import settings from "$lib/state/settings";
import { device } from "$lib/device";
import { t } from "$lib/i18n/translations";
-
import { createDialog } from "$lib/dialogs";
-import type { DialogInfo } from "$lib/types/dialog";
-const openSavingDialog = ({ url, file, body }: { url?: string, file?: File, body?: string }) => {
+import type { DialogInfo } from "$lib/types/dialog";
+import type { CobaltFileUrlType } from "$lib/types/api";
+
+type DownloadFileParams = {
+ url?: string,
+ file?: File,
+ urlType?: CobaltFileUrlType,
+}
+
+type SavingDialogParams = {
+ url?: string,
+ file?: File,
+ body?: string,
+ urlType?: CobaltFileUrlType,
+}
+
+const openSavingDialog = ({ url, file, body, urlType }: SavingDialogParams) => {
const dialogData: DialogInfo = {
type: "saving",
id: "saving",
file,
url,
+ urlType,
}
if (body) dialogData.bodyText = body;
@@ -60,13 +75,13 @@ export const copyURL = async (url: string) => {
return await navigator?.clipboard?.writeText(url);
}
-export const downloadFile = ({ url, file }: { url?: string, file?: File }) => {
+export const downloadFile = ({ url, file, urlType }: DownloadFileParams) => {
if (!url && !file) throw new Error("attempted to download void");
const pref = get(settings).save.savingMethod;
if (pref === "ask") {
- return openSavingDialog({ url, file });
+ return openSavingDialog({ url, file, urlType });
}
/*
@@ -85,6 +100,7 @@ export const downloadFile = ({ url, file }: { url?: string, file?: File }) => {
url,
file,
body: get(t)("dialog.saving.timeout"),
+ urlType
});
}
@@ -100,7 +116,8 @@ export const downloadFile = ({ url, file }: { url?: string, file?: File }) => {
if (url) {
if (pref === "share" && device.supports.share) {
return shareURL(url);
- } else if (pref === "download" && device.supports.directDownload) {
+ } else if (pref === "download" && device.supports.directDownload
+ && !(device.is.iOS && urlType === "redirect")) {
return openURL(url);
} else if (pref === "copy" && !file) {
return copyURL(url);
@@ -108,5 +125,5 @@ export const downloadFile = ({ url, file }: { url?: string, file?: File }) => {
}
} catch { /* catch & ignore */ }
- return openSavingDialog({ url, file });
+ return openSavingDialog({ url, file, urlType });
}
diff --git a/web/src/lib/env.ts b/web/src/lib/env.ts
index 734f482d..53b93a34 100644
--- a/web/src/lib/env.ts
+++ b/web/src/lib/env.ts
@@ -14,7 +14,6 @@ const variables = {
PLAUSIBLE_HOST: getEnv('PLAUSIBLE_HOST'),
PLAUSIBLE_ENABLED: getEnv('HOST') && getEnv('PLAUSIBLE_HOST'),
DEFAULT_API: getEnv('DEFAULT_API'),
- TURNSTILE_KEY: getEnv('TURNSTILE_KEY'),
}
const contacts = {
diff --git a/web/src/lib/polyfills.ts b/web/src/lib/polyfills.ts
index b5917b5c..621c9b2b 100644
--- a/web/src/lib/polyfills.ts
+++ b/web/src/lib/polyfills.ts
@@ -1 +1,2 @@
import "./polyfills/user-activation";
+import "./polyfills/abortsignal-timeout";
diff --git a/web/src/lib/polyfills/abortsignal-timeout.ts b/web/src/lib/polyfills/abortsignal-timeout.ts
new file mode 100644
index 00000000..8d9b3c66
--- /dev/null
+++ b/web/src/lib/polyfills/abortsignal-timeout.ts
@@ -0,0 +1,10 @@
+import { browser } from "$app/environment";
+
+if (browser && 'AbortSignal' in window && !window.AbortSignal.timeout) {
+ window.AbortSignal.timeout = (milliseconds: number) => {
+ const controller = new AbortController();
+ setTimeout(() => controller.abort("timed out"), milliseconds);
+
+ return controller.signal;
+ }
+}
diff --git a/web/src/lib/types/api.ts b/web/src/lib/types/api.ts
index 4df054aa..b2e47a40 100644
--- a/web/src/lib/types/api.ts
+++ b/web/src/lib/types/api.ts
@@ -40,6 +40,8 @@ type CobaltTunnelResponse = {
status: CobaltResponseType.Tunnel,
} & CobaltPartialURLResponse;
+export type CobaltFileUrlType = "redirect" | "tunnel";
+
export type CobaltSession = {
token: string,
exp: number,
@@ -51,6 +53,7 @@ export type CobaltServerInfo = {
url: string,
startTime: string,
durationLimit: number,
+ turnstileSitekey?: string,
services: string[]
},
git: {
@@ -64,6 +67,6 @@ export type CobaltSessionResponse = CobaltSession | CobaltErrorResponse;
export type CobaltServerInfoResponse = CobaltServerInfo | CobaltErrorResponse;
export type CobaltAPIResponse = CobaltErrorResponse
- | CobaltPickerResponse
- | CobaltRedirectResponse
- | CobaltTunnelResponse;
+ | CobaltPickerResponse
+ | CobaltRedirectResponse
+ | CobaltTunnelResponse;
diff --git a/web/src/lib/types/dialog.ts b/web/src/lib/types/dialog.ts
index fa0df5fe..69e27b5d 100644
--- a/web/src/lib/types/dialog.ts
+++ b/web/src/lib/types/dialog.ts
@@ -1,3 +1,4 @@
+import type { CobaltFileUrlType } from "$lib/types/api";
import type { MeowbaltEmotions } from "$lib/types/meowbalt";
export type DialogButton = {
@@ -43,6 +44,7 @@ type SavingDialog = Dialog & {
bodyText?: string,
url?: string,
file?: File,
+ urlType?: CobaltFileUrlType,
};
export type DialogInfo = SmallDialog | PickerDialog | SavingDialog;
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index 5263ffa2..598f71da 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -7,6 +7,7 @@
import { updated } from "$app/stores";
import { browser } from "$app/environment";
import { afterNavigate } from "$app/navigation";
+ import { getServerInfo, cachedInfo } from "$lib/api/server-info";
import "$lib/polyfills";
import env from "$lib/env";
@@ -31,10 +32,16 @@
$settings.appearance.reduceTransparency ||
device.prefers.reducedTransparency;
- afterNavigate(() => {
+ $: spawnTurnstile = !!$cachedInfo?.info?.cobalt?.turnstileSitekey;
+
+ afterNavigate(async() => {
const to_focus: HTMLElement | null =
document.querySelector("[data-first-focus]");
to_focus?.focus();
+
+ if ($page.url.pathname === "/") {
+ await getServerInfo();
+ }
});
@@ -77,7 +84,7 @@
- {#if (env.TURNSTILE_KEY && $page.url.pathname === "/") || $turnstileCreated}
+ {#if (spawnTurnstile && $page.url.pathname === "/") || $turnstileCreated}
{/if}
@@ -196,7 +203,7 @@
--input-border: #383838;
--toggle-bg: var(--input-border);
- --toggle-bg-enabled: #777777;
+ --toggle-bg-enabled: #8a8a8a;
--sidebar-mobile-gradient: linear-gradient(
90deg,
@@ -252,7 +259,7 @@
}
/* add padding for notch / dynamic island in landscape */
- @media screen and (orientation: landscape) {
+ @media screen and (orientation: landscape) and (min-width: 535px) {
#cobalt[data-iphone="true"] {
grid-template-columns:
calc(
@@ -471,6 +478,7 @@
:global(.long-text-noto ul) {
padding-inline-start: 30px;
+ margin-block-start: 9px;
}
:global(.long-text-noto li) {
@@ -485,6 +493,19 @@
margin-block-start: 0.3em;
}
+ :global(.long-text-noto.about .heading-container) {
+ padding-top: calc(var(--padding) / 2);
+ }
+
+ :global(.long-text-noto.about section:first-of-type .heading-container) {
+ padding-top: 0;
+ }
+
+ :global(::selection) {
+ color: var(--primary);
+ background: var(--secondary);
+ }
+
@media screen and (max-width: 535px) {
:global(.long-text-noto),
:global(.long-text-noto *:not(h1, h2, h3, h4, h5, h6)) {
diff --git a/web/src/routes/_headers/+server.ts b/web/src/routes/_headers/+server.ts
new file mode 100644
index 00000000..375c6bcd
--- /dev/null
+++ b/web/src/routes/_headers/+server.ts
@@ -0,0 +1,21 @@
+export function GET() {
+ const _headers = {
+ "/*": {
+ "Cross-Origin-Opener-Policy": "same-origin",
+ "Cross-Origin-Embedder-Policy": "require-corp",
+ }
+ }
+
+ return new Response(
+ Object.entries(_headers).map(
+ ([path, headers]) => [
+ path,
+ Object.entries(headers).map(
+ ([key, value]) => ` ${key}: ${value}`
+ )
+ ].flat().join("\n")
+ ).join("\n\n")
+ );
+}
+
+export const prerender = true;
diff --git a/web/src/routes/about/[page]/+page.svelte b/web/src/routes/about/[page]/+page.svelte
new file mode 100644
index 00000000..68bad426
--- /dev/null
+++ b/web/src/routes/about/[page]/+page.svelte
@@ -0,0 +1,6 @@
+
+
+
diff --git a/web/src/routes/about/[page]/+page.ts b/web/src/routes/about/[page]/+page.ts
new file mode 100644
index 00000000..be5c7a33
--- /dev/null
+++ b/web/src/routes/about/[page]/+page.ts
@@ -0,0 +1,29 @@
+import type { ComponentType, SvelteComponent } from 'svelte';
+import { get } from 'svelte/store';
+import { error } from '@sveltejs/kit';
+
+import type { PageLoad } from './$types';
+
+import locale from '$lib/i18n/locale';
+import type { DefaultImport } from '$lib/types/generic';
+import { defaultLocale } from '$lib/i18n/translations';
+
+const pages = import.meta.glob('$i18n/*/about/*.md');
+
+export const load: PageLoad = async ({ params }) => {
+ const getPage = (locale: string) => Object.keys(pages).find(
+ file => file.endsWith(`${locale}/about/${params.page}.md`)
+ );
+
+ const componentPath = getPage(get(locale)) || getPage(defaultLocale);
+ if (componentPath) {
+ type Component = ComponentType
;
+ const componentImport = pages[componentPath] as DefaultImport;
+
+ return { component: (await componentImport()).default }
+ }
+
+ error(404, 'Not found');
+};
+
+export const prerender = true;
diff --git a/web/src/routes/about/credits/+page.svelte b/web/src/routes/about/credits/+page.svelte
deleted file mode 100644
index a9148275..00000000
--- a/web/src/routes/about/credits/+page.svelte
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
- meowbalt
-
- meowbalt is cobalt's speedy mascot. he is an extremely expressive cat that loves fast internet.
-
-
- all amazing drawings of meowbalt that you see in cobalt were made by
- GlitchyPSI .
- he is also the original designer of the character.
-
-
- you cannot use or modify GlitchyPSI's artworks of meowbalt without his explicit permission.
-
-
- you cannot use or modify the meowbalt character design commercially or in any form that isn't fan art.
-
-
-
- cobalt licenses
-
- cobalt processing server is open source and licensed under AGPL-3.0 .
-
-
- cobalt frontend is
- source first
- and licensed under
- CC-BY-NC-SA 4.0 .
- we decided to use this license to stop grifters from profiting off our work & from creating malicious clones that deceive people and hurt our public identity.
-
-
- we rely on many open source libraries, create & distribute our own.
- you can see the full list of dependencies on github .
-
-
-
diff --git a/web/src/routes/about/general/+page.svelte b/web/src/routes/about/general/+page.svelte
deleted file mode 100644
index a7ecacdf..00000000
--- a/web/src/routes/about/general/+page.svelte
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
- best way to save what you love
-
- cobalt lets you save anything from your favorite websites: video, audio, photos or gifs — cobalt can do it all!
-
-
- no ads, trackers, or paywalls, no nonsense. just a convenient web app that works everywhere.
-
-
-
-
- leading privacy
-
- all requests to backend are anonymous and all tunnels are encrypted.
- we have a strict zero log policy and don't track anything about individual people.
-
-
- to avoid caching or storing downloaded files, cobalt processes them on-fly, sending processed pieces directly to client.
- this technology is used when your request needs additional processing, such as when source service stores video & audio in separate files.
-
-
- for even higher level of protection, you can ask cobalt to always tunnel everything .
- when enabled, cobalt will proxy everything through itself. no one will know what you download, even your network provider/admin.
- all they'll see is that you're using cobalt.
-
-
-
-
- blazing speed
-
- since we don't rely on any existing downloaders and develop our own from ground up,
- cobalt is extremely efficient and a processing server can run on basically any hardware.
-
-
- main processing instances are hosted on several dedicated servers in several countries,
- to reduce latency and distribute the traffic.
-
-
- we constantly improve our infrastructure along with our long-standing partner,
- royalehosting.net !
- you're in good hands, and will get what you need within seconds.
-
-
-
-
-
-
- on-device processing
-
- new features, such as remuxing , work on-device.
- on-device processing is efficient and never sends anything over the internet.
- it perfectly aligns with our future goal of moving as much processing as possible to client.
-
-
-
diff --git a/web/src/routes/about/privacy/+page.svelte b/web/src/routes/about/privacy/+page.svelte
deleted file mode 100644
index 28bdba59..00000000
--- a/web/src/routes/about/privacy/+page.svelte
+++ /dev/null
@@ -1,81 +0,0 @@
-
-
-
-
- general terms
-
- cobalt's privacy policy is simple: we don't collect or store anything about you. what you do is solely your business, not ours or anyone else's.
-
-
- these terms are applicable only when using the official cobalt instance. in other cases, you may need to contact the hoster for accurate info.
-
-
-
-
- on-device processing
-
- tools that use on-device processing work offline, locally, and never send any data anywhere. they are explicitly marked as such whenever applicable.
-
-
-
-
- saving
-
- when using saving functionality, in some cases cobalt will encrypt & temporarily store information needed for tunneling. it's stored in processing server's RAM for 90 seconds and irreversibly purged afterwards. no one has access to it, even instance owners, as long as they don't modify the official cobalt image.
-
-
- processed/tunneled files are never cached anywhere. everything is tunneled live. cobalt's saving functionality is essentially a fancy proxy service.
-
-
-
-
- encryption
-
- temporarily stored tunnel data is encrypted using the AES-256 standard. decryption keys are only included in the access link and never logged/cached/stored anywhere. only the end user has access to the link & encryption keys. keys are generated uniquely for each requested tunnel.
-
-
-
- {#if env.PLAUSIBLE_ENABLED}
-
- anonymous traffic analytics
-
- for sake of privacy, we use
- plausible's anonymous traffic analytics
- to get an approximate number of active cobalt users. no identifiable information about you or your requests is ever stored. all data is anonymized and aggregated. the plausible instance we use is hosted & managed by us.
-
-
-
- plausible doesn't use cookies and is fully compliant with GDPR, CCPA, and PECR.
-
-
-
-
- {$t("settings.privacy.analytics.learnmore")}
-
-
-
-
- if you wish to opt out of anonymous analytics, you can do it in privacy settings .
-
-
- {/if}
-
-
- web privacy & security
-
- we use cloudflare services for ddos & bot protection. we also use cloudflare pages for deploying & hosting the static web app. all of these are required to provide the best experience for everyone. it's the most private & reliable provider that we know of.
-
-
- cloudflare is fully compliant with GDPR and HIPAA.
-
-
-
- learn more about cloudflare's dedication to privacy.
-
-
-
-
diff --git a/web/src/routes/about/terms/+page.svelte b/web/src/routes/about/terms/+page.svelte
deleted file mode 100644
index 016e4539..00000000
--- a/web/src/routes/about/terms/+page.svelte
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
- general terms
-
- these terms are applicable only when using the official cobalt instance. in other cases, you may need to contact the hoster for accurate info.
-
-
- saving
-
- saving functionality simplifies downloading content from the internet and takes zero liability for what the saved content is used for. processing servers work like advanced proxies and don't ever write any content to disk. everything is handled in RAM and permanently purged once the tunnel is done. we have no downloading logs and can't identify anyone.
-
-
- you can read more about how tunnels work in our privacy policy.
-
-
-
-
- responsibilities
-
- you (end user) are responsible for what you do with our tools, how you use and distribute resulting content. please be mindful when using content of others and always credit original creators. make sure you don't violate any terms or licenses.
-
-
- when used in educational purposes, always cite sources and credit original creators.
-
-
- fair use and credits benefit everyone.
-
-
-
-
- reporting abuse
-
- we have no way of detecting abusive behavior automatically, as cobalt is 100% anonymous. however, you can report such activities to us and we will do our best to comply manually: safety@imput.net
-
-
-
diff --git a/web/src/routes/donate/+page.svelte b/web/src/routes/donate/+page.svelte
index beb4b856..66ae677b 100644
--- a/web/src/routes/donate/+page.svelte
+++ b/web/src/routes/donate/+page.svelte
@@ -33,6 +33,7 @@
{$t("donate.body.motivation")}
+ {$t("donate.body.no_bullshit")}
{$t("donate.body.keep_going")}
@@ -119,6 +120,10 @@
gap: 10px;
}
+ #motivation p:first-child {
+ margin-block-start: 10px;
+ }
+
@media screen and (max-width: 760px) {
#support-options {
flex-direction: column;
diff --git a/web/src/routes/settings/audio/+page.svelte b/web/src/routes/settings/audio/+page.svelte
index e2cda6f6..eb0ac679 100644
--- a/web/src/routes/settings/audio/+page.svelte
+++ b/web/src/routes/settings/audio/+page.svelte
@@ -1,4 +1,5 @@
-
+
{#each audioFormatOptions as value}
-
-
+
{#each audioBitrateOptions as value}
@@ -34,7 +34,7 @@
{
for (const module of modules) {
const distFolder = join(IMPUT_MODULE_DIR, module, 'dist/');
- console.log(distFolder);
await cp(distFolder, assets, { recursive: true });
}
}
@@ -72,7 +71,7 @@ export default defineConfig({
rollupOptions: {
output: {
manualChunks: (id) => {
- if (id.includes('/web/i18n')) {
+ if (id.includes('/web/i18n') && id.endsWith('.json')) {
const lang = id.split('/web/i18n/')?.[1].split('/')?.[0];
if (lang) {
return `i18n_${lang}`;