Merge branch 'main' into main

This commit is contained in:
Aphex 2024-09-22 16:16:31 +02:00 committed by GitHub
commit d336e5bd2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 64 additions and 42 deletions

View File

@ -105,6 +105,18 @@ export const runAPI = (express, app, __dirname) => {
app.post('/', apiLimiter); app.post('/', apiLimiter);
app.use('/tunnel', apiLimiterStream); 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) => { app.post('/', (req, res, next) => {
if (!env.turnstileSecret || !env.jwtSecret) { if (!env.turnstileSecret || !env.jwtSecret) {
return next(); return next();
@ -128,14 +140,6 @@ export const runAPI = (express, app, __dirname) => {
return fail(res, "error.api.auth.jwt.invalid"); return fail(res, "error.api.auth.jwt.invalid");
} }
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");
}
req.authorized = true; req.authorized = true;
} catch { } catch {
return fail(res, "error.api.generic"); return fail(res, "error.api.generic");

View File

@ -65,3 +65,14 @@ export function merge(a, b) {
return a; return a;
} }
export function splitFilenameExtension(filename) {
const parts = filename.split('.');
const ext = parts.pop();
if (!parts.length) {
return [ ext, "" ]
} else {
return [ parts.join('.'), ext ]
}
}

View File

@ -3,6 +3,7 @@ import createFilename from "./create-filename.js";
import { createResponse } from "./request.js"; import { createResponse } from "./request.js";
import { audioIgnore } from "./service-config.js"; import { audioIgnore } from "./service-config.js";
import { createStream } from "../stream/manage.js"; import { createStream } from "../stream/manage.js";
import { splitFilenameExtension } from "../misc/utils.js";
export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disableMetadata, filenameStyle, twitterGif, requestIP, audioBitrate, alwaysProxy }) { export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disableMetadata, filenameStyle, twitterGif, requestIP, audioBitrate, alwaysProxy }) {
let action, let action,
@ -32,10 +33,11 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
} }
if (action === "muteVideo" && isAudioMuted && !r.filenameAttributes) { if (action === "muteVideo" && isAudioMuted && !r.filenameAttributes) {
const parts = r.filename.split("."); const [ name, ext ] = splitFilenameExtension(r.filename);
const ext = parts.pop(); defaultParams.filename = `${name}_mute.${ext}`;
} else if (action === "gif") {
defaultParams.filename = `${parts.join(".")}_mute.${ext}`; const [ name ] = splitFilenameExtension(r.filename);
defaultParams.filename = `${name}.gif`;
} }
switch (action) { switch (action) {

View File

@ -176,7 +176,7 @@ export const services = {
Object.values(services).forEach(service => { Object.values(services).forEach(service => {
service.patterns = service.patterns.map( service.patterns = service.patterns.map(
pattern => new UrlPattern(pattern, { pattern => new UrlPattern(pattern, {
segmentValueCharset: UrlPattern.defaultOptions.segmentValueCharset + '@\\.' segmentValueCharset: UrlPattern.defaultOptions.segmentValueCharset + '@\\.:'
}) })
) )
}) })

View File

@ -291,7 +291,7 @@ const convertGif = (streamInfo, res) => {
const [,,, muxOutput] = process.stdio; const [,,, muxOutput] = process.stdio;
res.setHeader('Connection', 'keep-alive'); res.setHeader('Connection', 'keep-alive');
res.setHeader('Content-Disposition', contentDisposition(streamInfo.filename.split('.')[0] + ".gif")); res.setHeader('Content-Disposition', contentDisposition(streamInfo.filename));
pipe(muxOutput, res, shutdown); pipe(muxOutput, res, shutdown);

View File

@ -1,7 +1,7 @@
import { existsSync, unlinkSync, appendFileSync } from "fs"; import { existsSync, unlinkSync, appendFileSync } from "fs";
import { createInterface } from "readline"; import { createInterface } from "readline";
import { Cyan, Bright } from "./misc/console-text.js"; import { Cyan, Bright } from "../misc/console-text.js";
import { loadJSON } from "./misc/load-from-fs.js"; import { loadJSON } from "../misc/load-from-fs.js";
import { execSync } from "child_process"; import { execSync } from "child_process";
const { version } = loadJSON("./package.json"); const { version } = loadJSON("./package.json");

View File

@ -1401,7 +1401,7 @@
"bsky": [ "bsky": [
{ {
"name": "horizontal video", "name": "horizontal video",
"url": "https://bsky.app/profile/haileyok.com/post/3l3giwtwp222m", "url": "https://bsky.app/profile/did:plc:oisofpd7lj26yvgiivf3lxsi/post/3l3giwtwp222m",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,
@ -1410,7 +1410,7 @@
}, },
{ {
"name": "horizontal video, recordWithMedia", "name": "horizontal video, recordWithMedia",
"url": "https://bsky.app/profile/juicysteak117.gay/post/3l3wonhk23g2i", "url": "https://bsky.app/profile/did:plc:ywbm3iywnhzep3ckt6efhoh7/post/3l3wonhk23g2i",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,
@ -1419,7 +1419,7 @@
}, },
{ {
"name": "vertical video", "name": "vertical video",
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m", "url": "https://bsky.app/profile/did:plc:oisofpd7lj26yvgiivf3lxsi/post/3l3jhpomhjk2m",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,
@ -1428,7 +1428,7 @@
}, },
{ {
"name": "vertical video (muted)", "name": "vertical video (muted)",
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m", "url": "https://bsky.app/profile/did:plc:oisofpd7lj26yvgiivf3lxsi/post/3l3jhpomhjk2m",
"params": { "params": {
"downloadMode": "mute" "downloadMode": "mute"
}, },
@ -1439,7 +1439,7 @@
}, },
{ {
"name": "vertical video (audio)", "name": "vertical video (audio)",
"url": "https://bsky.app/profile/haileyok.com/post/3l3jhpomhjk2m", "url": "https://bsky.app/profile/did:plc:oisofpd7lj26yvgiivf3lxsi/post/3l3jhpomhjk2m",
"params": { "params": {
"downloadMode": "audio" "downloadMode": "audio"
}, },
@ -1450,7 +1450,7 @@
}, },
{ {
"name": "single image", "name": "single image",
"url": "https://bsky.app/profile/thehardyboycats.bsky.social/post/3l33flpoygt26", "url": "https://bsky.app/profile/did:plc:k4a7d65fcyevbrnntjxh57go/post/3l33flpoygt26",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,
@ -1459,7 +1459,7 @@
}, },
{ {
"name": "several images", "name": "several images",
"url": "https://bsky.app/profile/tracey-m.bsky.social/post/3kzxuxbiul626", "url": "https://bsky.app/profile/did:plc:rai7s6su2sy22ss7skouedl7/post/3kzxuxbiul626",
"params": {}, "params": {},
"expected": { "expected": {
"code": 200, "code": 200,
@ -1467,8 +1467,8 @@
} }
}, },
{ {
"name": "deleted post", "name": "deleted post/invalid user",
"url": "https://bsky.app/profile/samuel.bsky.team/post/3l2udah76ch2c", "url": "https://bsky.app/profile/notreal.bsky.team/post/3l2udah76ch2c",
"params": {}, "params": {},
"expected": { "expected": {
"code": 400, "code": 400,

1
packages/api-client/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -47,7 +47,7 @@ and for nerds, we have a giant list of backend changes (that we are also excited
this update allows us to actually innovate and develop new & exciting features. we are no longer held back by the legacy codebase. first feature of such kind is on-device remuxing. go check it out! this update allows us to actually innovate and develop new & exciting features. we are no longer held back by the legacy codebase. first feature of such kind is on-device remuxing. go check it out!
oh yeah, we now have 2.5 million monthly users. kind of insane. oh yeah, we now have over 2 million monthly users. kind of insane.
we hope you enjoy this update as much as we enjoyed making it. it was a really fun summer project for both of us. we hope you enjoy this update as much as we enjoyed making it. it was a really fun summer project for both of us.

View File

@ -65,7 +65,7 @@
"metadata.filename.basic": "basic", "metadata.filename.basic": "basic",
"metadata.filename.pretty": "pretty", "metadata.filename.pretty": "pretty",
"metadata.filename.nerdy": "nerdy", "metadata.filename.nerdy": "nerdy",
"metadata.filename.description": "filename style will only be used for files tunnelled by cobalt. some services don't support filename styles other than classic.", "metadata.filename.description": "filename style will only be used for files tunneled by cobalt. some services don't support filename styles other than classic.",
"metadata.filename.preview.video": "Video Title", "metadata.filename.preview.video": "Video Title",
"metadata.filename.preview.audio": "Audio Title - Audio Author", "metadata.filename.preview.audio": "Audio Title - Audio Author",
@ -88,17 +88,17 @@
"accessibility.motion.description": "disables animations and transitions whenever possible.", "accessibility.motion.description": "disables animations and transitions whenever possible.",
"language": "language", "language": "language",
"language.auto.title": "use default browser language", "language.auto.title": "automatic selection",
"language.auto.description": "automatically picks the best language for you. if preferred browser language isn't available, english is used instead.", "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.title": "preferred language",
"language.preferred.description": "if any text isnt translated to the preferred language, it will fall back to 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.",
"privacy.analytics": "anonymous traffic analytics", "privacy.analytics": "anonymous traffic analytics",
"privacy.analytics.title": "don't contribute to analytics", "privacy.analytics.title": "don't contribute to analytics",
"privacy.analytics.description": "anonymous traffic analytics are needed to get an approximate number of active cobalt users. no identifiable information about you is ever stored. all processed data is anonymized and aggregated.\n\nwe use a self-hosted plausible instance that doesn't use cookies and is fully compliant with GDPR, CCPA, and PECR.", "privacy.analytics.description": "anonymous traffic analytics are needed to get an approximate number of active cobalt users. no identifiable information about you is ever stored. all processed data is anonymized and aggregated.\n\nwe use a self-hosted plausible instance that doesn't use cookies and is fully compliant with GDPR, CCPA, and PECR.",
"privacy.analytics.learnmore": "learn more about plausible's dedication to privacy.", "privacy.analytics.learnmore": "learn more about plausible's dedication to privacy.",
"privacy.tunnel": "tunnelling", "privacy.tunnel": "tunneling",
"privacy.tunnel.title": "always tunnel files", "privacy.tunnel.title": "always tunnel files",
"privacy.tunnel.description": "cobalt will hide your ip address, browser info, and bypass local network restrictions. when enabled, files will also have readable filenames that otherwise would be gibberish.", "privacy.tunnel.description": "cobalt will hide your ip address, browser info, and bypass local network restrictions. when enabled, files will also have readable filenames that otherwise would be gibberish.",

View File

@ -59,7 +59,7 @@
<div class="action-button-icon"> <div class="action-button-icon">
<CopyIcon check={copied} /> <CopyIcon check={copied} />
</div> </div>
copy {$t("button.copy")}
</button> </button>
{#if device.supports.share} {#if device.supports.share}

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import locale from "$lib/i18n/locale";
import languages from "$i18n/languages.json"; import languages from "$i18n/languages.json";
import { t, locales } from "$lib/i18n/translations"; import { t, locales } from "$lib/i18n/translations";
@ -10,10 +9,12 @@
$: currentSetting = $settings.appearance.language; $: currentSetting = $settings.appearance.language;
$: disabled = $settings.appearance.autoLanguage; $: disabled = $settings.appearance.autoLanguage;
const updateLocale = (lang: string) => { const updateLocale = (event: Event) => {
const target = event.target as HTMLSelectElement;
updateSetting({ updateSetting({
appearance: { appearance: {
language: lang as keyof typeof languages, language: target.value as keyof typeof languages,
}, },
}); });
}; };
@ -34,8 +35,7 @@
</div> </div>
<select <select
id="setting-dropdown-appearance-language" id="setting-dropdown-appearance-language"
bind:value={$locale} on:change={updateLocale}
on:change={() => updateLocale($locale)}
{disabled} {disabled}
> >
{#each $locales as value} {#each $locales as value}

View File

@ -7,7 +7,7 @@ import type {
LocalizationContent LocalizationContent
} from '$lib/types/i18n'; } from '$lib/types/i18n';
import languages from '$i18n/languages.json'; import _languages from '$i18n/languages.json';
const locFiles = import.meta.glob('$i18n/*/**/*.json'); const locFiles = import.meta.glob('$i18n/*/**/*.json');
const parsedLocfiles: StructuredLocfileInfo = {}; const parsedLocfiles: StructuredLocfileInfo = {};
@ -22,6 +22,8 @@ for (const [path, loader] of Object.entries(locFiles)) {
} }
const defaultLocale = 'en'; const defaultLocale = 'en';
const languages: Record<string, string> = _languages;
const config: Config<{ const config: Config<{
value?: string; value?: string;
formats?: string; formats?: string;
@ -30,6 +32,8 @@ const config: Config<{
}> = { }> = {
fallbackLocale: defaultLocale, fallbackLocale: defaultLocale,
translations: Object.keys(parsedLocfiles).reduce((obj, lang) => { translations: Object.keys(parsedLocfiles).reduce((obj, lang) => {
languages[lang] ??= `${lang} (missing name)`;
return { return {
...obj, ...obj,
[lang]: { languages } [lang]: { languages }

View File

@ -18,7 +18,7 @@
<h3>leading privacy</h3> <h3>leading privacy</h3>
<p> <p>
all requests to backend are anonymous and all tunnels are encrypted. all requests to backend are anonymous and all tunnels are encrypted.
we have a strict zero log policy and don't track <i>anything at all</i>. we have a strict zero log policy and don't track <i>anything</i> about individual people.
</p> </p>
<p> <p>
to avoid caching or storing downloaded files, cobalt processes them on-fly, sending processed pieces directly to client. to avoid caching or storing downloaded files, cobalt processes them on-fly, sending processed pieces directly to client.

View File

@ -25,10 +25,10 @@
<section id="saving"> <section id="saving">
<h3>saving</h3> <h3>saving</h3>
<p> <p>
when using saving functionality, in some cases cobalt will encrypt & temporarily store information needed for tunnelling. 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. 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.
</p> </p>
<p> <p>
processed/tunnelled files are never cached anywhere. everything is tunnelled live. cobalt's saving functionality is essentially a fancy proxy service. processed/tunneled files are never cached anywhere. everything is tunneled live. cobalt's saving functionality is essentially a fancy proxy service.
</p> </p>
</section> </section>