Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
ZEREX222 authored Jan 23, 2025
2 parents a963a72 + 899d1ef commit d31b577
Show file tree
Hide file tree
Showing 27 changed files with 499 additions and 112 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
<a href="https://discord.gg/pQPt8HBUPu">
💬 community discord server
</a>
<br/>
<a href="https://x.com/justusecobalt">
🐦 twitter
</a>
<a href="https://bsky.app/profile/cobalt.tools">
🦋 bluesky
</a>
</p>
<br/>
</div>
Expand All @@ -34,11 +38,10 @@ this monorepo includes source code for api, frontend, and related packages:
it also includes documentation in the [docs tree](/docs/):
- [cobalt api documentation](/docs/api.md)
- [how to run a cobalt instance](/docs/run-an-instance.md)
- [how to protect a cobalt instance](/docs/protect-an-instance.md)
- [how to configure a cobalt instance for youtube](/docs/configure-for-youtube.md)
- [how to protect a cobalt instance](/docs/protect-an-instance.md) (recommended if you host a public instance)

### thank you
cobalt is sponsored by [royalehosting.net](https://royalehosting.net/?partner=cobalt) and the main processing servers are hosted on their network. we really appreciate their kindness and support!
cobalt is sponsored by [royalehosting.net](https://royalehosting.net/?partner=cobalt). a part of our infrastructure is hosted on their network. we really appreciate their kindness and support!

### ethics
cobalt is a tool that makes downloading public content easier. it takes **zero liability**.
Expand All @@ -50,7 +53,7 @@ it can only download free & publicly accessible content.
same content can be downloaded via dev tools of any modern web browser.

### contributing
thank you for considering making a contribution to cobalt! please check the [contributing guidelines here](/CONTRIBUTING.md) before making a pull request.
if you're considering contributing to cobalt, first of all, thank you! check the [contribution guidelines here](/CONTRIBUTING.md) before getting started, they'll help you do your best right away.

### licenses
for relevant licensing information, see the [api](api/README.md) and [web](web/README.md) READMEs.
Expand Down
55 changes: 26 additions & 29 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ we recommend [deploying your own instance](/docs/run-an-instance.md) if you wish

you can read [the api documentation here](/docs/api.md).

> [!WARNING]
> the v7 public api (/api/json) will be shut down on **november 11th, 2024**.
> you can access documentation for it [here](https://github.com/imputnet/cobalt/blob/7/docs/api.md).
## supported services
this list is not final and keeps expanding over time. if support for a service you want is missing, create an issue (or a pull request 👀).
this list is not final and keeps expanding over time!
if the desired service isn't supported yet, feel free to create an appropriate issue (or a pull request 👀).

| service | video + audio | only audio | only video | metadata | rich file names |
| :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: |
Expand All @@ -39,12 +36,13 @@ this list is not final and keeps expanding over time. if support for a service y
| twitter/x ||||||
| vimeo ||||||
| vk videos & clips ||||||
| xiaohongshu ||||||
| youtube ||||||

| emoji | meaning |
| :-----: | :---------------------- |
|| supported |
|| impossible/unreasonable |
|| unreasonable/impossible |
|| not supported |

### additional notes or features (per service)
Expand All @@ -71,36 +69,35 @@ as long as you:
- provide a link to the license and indicate if changes to the code were made, and
- release the code under the **same license**

## acknowledgements
## open source acknowledgements
### ffmpeg
cobalt heavily relies on ffmpeg for converting and merging media files. it's an absolutely amazing piece of software offered for anyone for free, yet doesn't receive as much credit as it should.
cobalt relies on ffmpeg for muxing and encoding media files. ffmpeg is absolutely spectacular and we're privileged to have an ability to use it for free, just like anyone else. we believe it should be way more recognized.

you can [support ffmpeg here](https://ffmpeg.org/donations.html)!

#### ffmpeg-static
we use [ffmpeg-static](https://github.com/eugeneware/ffmpeg-static) to get binaries for ffmpeg depending on the platform.

you can support the developer via various methods listed on their github page! (linked above)

### youtube.js
cobalt relies on [youtube.js](https://github.com/LuanRT/YouTube.js) for interacting with the innertube api, it wouldn't have been possible without it.
cobalt relies on **[youtube.js](https://github.com/LuanRT/YouTube.js)** for interacting with youtube's innertube api, it wouldn't have been possible without this package.

you can support the developer via various methods listed on their github page! (linked above)
you can support the developer via various methods listed on their github page!
(linked above)

### many others
cobalt also depends on:

- [content-disposition-header](https://www.npmjs.com/package/content-disposition-header) to simplify the provision of `content-disposition` headers.
- [cors](https://www.npmjs.com/package/cors) to manage cross-origin resource sharing within expressjs.
- [dotenv](https://www.npmjs.com/package/dotenv) to load environment variables from the `.env` file.
- [express](https://www.npmjs.com/package/express) as the backbone of cobalt servers.
- [express-rate-limit](https://www.npmjs.com/package/express-rate-limit) to rate limit api endpoints.
- [hls-parser](https://www.npmjs.com/package/hls-parser) to parse `m3u8` playlists for certain services.
- [ipaddr.js](https://www.npmjs.com/package/ipaddr.js) to parse ip addresses (for rate limiting).
- [nanoid](https://www.npmjs.com/package/nanoid) to generate unique (temporary) identifiers for each requested stream.
- [psl](https://www.npmjs.com/package/psl) as the domain name parser.
- [set-cookie-parser](https://www.npmjs.com/package/set-cookie-parser) to parse cookies that cobalt receives from certain services.
- [undici](https://www.npmjs.com/package/undici) for making http requests.
- [url-pattern](https://www.npmjs.com/package/url-pattern) to match provided links with supported patterns.
cobalt-api also depends on:

- **[content-disposition-header](https://www.npmjs.com/package/content-disposition-header)** to simplify the provision of `content-disposition` headers.
- **[cors](https://www.npmjs.com/package/cors)** to manage cross-origin resource sharing within expressjs.
- **[dotenv](https://www.npmjs.com/package/dotenv)** to load environment variables from the `.env` file.
- **[express](https://www.npmjs.com/package/express)** as the backbone of cobalt servers.
- **[express-rate-limit](https://www.npmjs.com/package/express-rate-limit)** to rate limit api endpoints.
- **[ffmpeg-static](https://www.npmjs.com/package/ffmpeg-static)** to get binaries for ffmpeg depending on the platform.
- **[hls-parser](https://www.npmjs.com/package/hls-parser)** to parse HLS playlists according to spec (very impressive stuff).
- **[ipaddr.js](https://www.npmjs.com/package/ipaddr.js)** to parse ip addresses (used for rate limiting).
- **[nanoid](https://www.npmjs.com/package/nanoid)** to generate unique identifiers for each requested tunnel.
- **[set-cookie-parser](https://www.npmjs.com/package/set-cookie-parser)** to parse cookies that cobalt receives from certain services.
- **[undici](https://www.npmjs.com/package/undici)** for making http requests.
- **[url-pattern](https://www.npmjs.com/package/url-pattern)** to match provided links with supported patterns.
- **[zod](https://www.npmjs.com/package/zod)** to lock down the api request schema.
- **[@datastructures-js/priority-queue](https://www.npmjs.com/package/@datastructures-js/priority-queue)** for sorting stream caches for future clean up (without redis).
- **[@imput/psl](https://www.npmjs.com/package/@imput/psl)** as the domain name parser, our fork of [psl](https://www.npmjs.com/package/psl).

...and many other packages that these packages rely on.
4 changes: 2 additions & 2 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@imput/cobalt-api",
"description": "save what you love",
"version": "10.5.4",
"version": "10.6",
"author": "imput",
"exports": "./src/cobalt.js",
"type": "module",
Expand Down Expand Up @@ -39,7 +39,7 @@
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",
"youtubei.js": "^12.2.0",
"youtubei.js": "^13.0.0",
"zod": "^3.23.8"
},
"optionalDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion api/src/core/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export const runAPI = async (express, app, __dirname, isPrimary = true) => {
...Object.entries(req.headers)
]);

return stream(res, { type: 'internal', ...streamInfo });
return stream(res, { type: 'internal', data: streamInfo });
};

app.get('/itunnel', itunnelHandler);
Expand Down
18 changes: 15 additions & 3 deletions api/src/misc/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
export function getRedirectingURL(url) {
return fetch(url, { redirect: 'manual' }).then((r) => {
if ([301, 302, 303].includes(r.status) && r.headers.has('location'))
const redirectStatuses = new Set([301, 302, 303, 307, 308]);

export async function getRedirectingURL(url, dispatcher) {
const location = await fetch(url, {
redirect: 'manual',
dispatcher,
}).then((r) => {
if (redirectStatuses.has(r.status) && r.headers.has('location')) {
return r.headers.get('location');
}
}).catch(() => null);

return location;
}

export function merge(a, b) {
Expand All @@ -29,3 +37,7 @@ export function splitFilenameExtension(filename) {
return [ parts.join('.'), ext ]
}
}

export function zip(a, b) {
return a.map((value, i) => [ value, b[i] ]);
}
7 changes: 5 additions & 2 deletions api/src/processing/match-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
filename: r.filenameAttributes ?
createFilename(r.filenameAttributes, filenameStyle, isAudioOnly, isAudioMuted) : r.filename,
fileMetadata: !disableMetadata ? r.fileMetadata : false,
requestIP
requestIP,
originalRequest: r.originalRequest
},
params = {};

Expand Down Expand Up @@ -47,7 +48,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
});

case "photo":
responseType = "redirect";
params = { type: "proxy" };
break;

case "gif":
Expand Down Expand Up @@ -83,6 +84,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
case "twitter":
case "snapchat":
case "bsky":
case "xiaohongshu":
params = { picker: r.picker };
break;

Expand Down Expand Up @@ -143,6 +145,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
case "ok":
case "vk":
case "tiktok":
case "xiaohongshu":
params = { type: "proxy" };
break;

Expand Down
10 changes: 10 additions & 0 deletions api/src/processing/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import snapchat from "./services/snapchat.js";
import loom from "./services/loom.js";
import facebook from "./services/facebook.js";
import bluesky from "./services/bluesky.js";
import xiaohongshu from "./services/xiaohongshu.js";

let freebind;

Expand Down Expand Up @@ -239,6 +240,15 @@ export default async function({ host, patternMatch, params }) {
});
break;

case "xiaohongshu":
r = await xiaohongshu({
...patternMatch,
h265: params.tiktokH265,
isAudioOnly,
dispatcher,
});
break;

default:
return createResponse("error", {
code: "error.api.service.unsupported"
Expand Down
8 changes: 8 additions & 0 deletions api/src/processing/service-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ export const services = {
subdomains: ["m"],
altDomains: ["vkvideo.ru", "vk.ru"],
},
xiaohongshu: {
patterns: [
"explore/:id?xsec_token=:token",
"discovery/item/:id?xsec_token=:token",
"a/:shareId"
],
altDomains: ["xhslink.com"],
},
youtube: {
patterns: [
"watch?v=:id",
Expand Down
4 changes: 4 additions & 0 deletions api/src/processing/service-patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,8 @@ export const testers = {

"bsky": pattern =>
pattern.user?.length <= 128 && pattern.post?.length <= 128,

"xiaohongshu": pattern =>
pattern.id?.length <= 24 && pattern.token?.length <= 64
|| pattern.shareId?.length <= 12,
}
65 changes: 49 additions & 16 deletions api/src/processing/services/bluesky.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ const extractImages = ({ getPost, filename, alwaysProxy }) => {
return { picker };
}

const extractGif = ({ url, filename }) => {
const gifUrl = new URL(url);

if (!gifUrl || gifUrl.hostname !== "media.tenor.com") {
return { error: "fetch.empty" };
}

// remove downscaling params from gif url
// such as "?hh=498&ww=498"
gifUrl.search = "";

return {
urls: gifUrl,
isPhoto: true,
filename: `${filename}.gif`,
}
}

export default async function ({ user, post, alwaysProxy, dispatcher }) {
const apiEndpoint = new URL("https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?depth=0&parentHeight=0");
apiEndpoint.searchParams.set(
Expand Down Expand Up @@ -102,22 +120,37 @@ export default async function ({ user, post, alwaysProxy, dispatcher }) {
const embedType = getPost?.thread?.post?.embed?.$type;
const filename = `bluesky_${user}_${post}`;

if (embedType === "app.bsky.embed.video#view") {
return extractVideo({
media: getPost.thread?.post?.embed,
filename,
})
}

if (embedType === "app.bsky.embed.recordWithMedia#view") {
return extractVideo({
media: getPost.thread?.post?.embed?.media,
filename,
})
}

if (embedType === "app.bsky.embed.images#view") {
return extractImages({ getPost, filename, alwaysProxy });
switch (embedType) {
case "app.bsky.embed.video#view":
return extractVideo({
media: getPost.thread?.post?.embed,
filename,
});

case "app.bsky.embed.images#view":
return extractImages({
getPost,
filename,
alwaysProxy
});

case "app.bsky.embed.external#view":
return extractGif({
url: getPost?.thread?.post?.embed?.external?.uri,
filename,
});

case "app.bsky.embed.recordWithMedia#view":
if (getPost?.thread?.post?.embed?.media?.$type === "app.bsky.embed.external#view") {
return extractGif({
url: getPost?.thread?.post?.embed?.media?.external?.uri,
filename,
});
}
return extractVideo({
media: getPost.thread?.post?.embed?.media,
filename,
});
}

return { error: "fetch.empty" };
Expand Down
Loading

0 comments on commit d31b577

Please sign in to comment.