From 94acf10e9e3ad742986bbe8c2ecf21c8cd1944b9 Mon Sep 17 00:00:00 2001 From: wukko Date: Sat, 9 Jul 2022 00:17:56 +0600 Subject: [PATCH] moved to new repo --- LICENSE | 8 +- README.md | 64 +++ cobalt.js | 126 +++++ config.json | 31 ++ files/cobalt.css | 488 ++++++++++++++++++ files/cobalt.js | 214 ++++++++ files/cobalt.webmanifest | 64 +++ files/fonts/notosansmono/notosansmono.css | 1 + .../notosansmono/notosansmono_3dVQ.woff2 | Bin 0 -> 9292 bytes .../notosansmono/notosansmono_7dVXQQ.woff2 | Bin 0 -> 5912 bytes .../notosansmono/notosansmono_DdVXQQ.woff2 | Bin 0 -> 17708 bytes .../notosansmono/notosansmono_HdVXQQ.woff2 | Bin 0 -> 4460 bytes .../notosansmono/notosansmono_LdVXQQ.woff2 | Bin 0 -> 2592 bytes .../notosansmono/notosansmono_PdVXQQ.woff2 | Bin 0 -> 26380 bytes .../notosansmono/notosansmono_ndVXQQ.woff2 | Bin 0 -> 5272 bytes files/icons/android-chrome-192x192.png | Bin 0 -> 3580 bytes files/icons/android-chrome-512x512.png | Bin 0 -> 9818 bytes files/icons/apple-touch-icon.png | Bin 0 -> 3278 bytes files/icons/favicon-16x16.png | Bin 0 -> 215 bytes files/icons/favicon-32x32.png | Bin 0 -> 365 bytes files/icons/favicon.ico | Bin 0 -> 9662 bytes files/icons/generic.png | Bin 0 -> 9818 bytes files/icons/maskable/x128.png | Bin 0 -> 2648 bytes files/icons/maskable/x1280.png | Bin 0 -> 39521 bytes files/icons/maskable/x192.png | Bin 0 -> 3571 bytes files/icons/maskable/x384.png | Bin 0 -> 8751 bytes files/icons/maskable/x48.png | Bin 0 -> 854 bytes files/icons/maskable/x512.png | Bin 0 -> 14589 bytes files/icons/maskable/x72.png | Bin 0 -> 1508 bytes files/icons/maskable/x96.png | Bin 0 -> 1752 bytes files/icons/wide.png | Bin 0 -> 13485 bytes files/robots.txt | 2 + jsconfig.json | 13 + modules/api.js | 35 ++ modules/config.js | 18 + modules/page-renderer.js | 171 ++++++ modules/services/all.json | 72 +++ modules/services/bilibili.js | 38 ++ modules/services/reddit.js | 17 + modules/services/twitter.js | 57 ++ modules/services/vk.js | 47 ++ modules/services/youtube.js | 74 +++ modules/setup.js | 54 ++ modules/stream/manage.js | 45 ++ modules/stream/select-quality.js | 32 ++ modules/stream/stream.js | 27 + modules/stream/types.js | 131 +++++ modules/sub/api-helper.js | 51 ++ modules/sub/console-text.js | 49 ++ modules/sub/crypto.js | 11 + modules/sub/current-commit.js | 5 + modules/sub/errors.js | 11 + modules/sub/load-json.js | 9 + modules/sub/loc.js | 22 + modules/sub/match.js | 90 ++++ package.json | 35 ++ strings/en/accessibility.json | 10 + strings/en/apiError.json | 22 + strings/en/changelog.json | 5 + strings/en/desc.json | 15 + strings/en/settings.json | 20 + strings/en/title.json | 7 + 62 files changed, 2187 insertions(+), 4 deletions(-) create mode 100644 README.md create mode 100644 cobalt.js create mode 100644 config.json create mode 100644 files/cobalt.css create mode 100644 files/cobalt.js create mode 100644 files/cobalt.webmanifest create mode 100644 files/fonts/notosansmono/notosansmono.css create mode 100644 files/fonts/notosansmono/notosansmono_3dVQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_7dVXQQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_DdVXQQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_HdVXQQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_LdVXQQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_PdVXQQ.woff2 create mode 100644 files/fonts/notosansmono/notosansmono_ndVXQQ.woff2 create mode 100644 files/icons/android-chrome-192x192.png create mode 100644 files/icons/android-chrome-512x512.png create mode 100644 files/icons/apple-touch-icon.png create mode 100644 files/icons/favicon-16x16.png create mode 100644 files/icons/favicon-32x32.png create mode 100644 files/icons/favicon.ico create mode 100644 files/icons/generic.png create mode 100644 files/icons/maskable/x128.png create mode 100644 files/icons/maskable/x1280.png create mode 100644 files/icons/maskable/x192.png create mode 100644 files/icons/maskable/x384.png create mode 100644 files/icons/maskable/x48.png create mode 100644 files/icons/maskable/x512.png create mode 100644 files/icons/maskable/x72.png create mode 100644 files/icons/maskable/x96.png create mode 100644 files/icons/wide.png create mode 100644 files/robots.txt create mode 100644 jsconfig.json create mode 100644 modules/api.js create mode 100644 modules/config.js create mode 100644 modules/page-renderer.js create mode 100644 modules/services/all.json create mode 100644 modules/services/bilibili.js create mode 100644 modules/services/reddit.js create mode 100644 modules/services/twitter.js create mode 100644 modules/services/vk.js create mode 100644 modules/services/youtube.js create mode 100644 modules/setup.js create mode 100644 modules/stream/manage.js create mode 100644 modules/stream/select-quality.js create mode 100644 modules/stream/stream.js create mode 100644 modules/stream/types.js create mode 100644 modules/sub/api-helper.js create mode 100644 modules/sub/console-text.js create mode 100644 modules/sub/crypto.js create mode 100644 modules/sub/current-commit.js create mode 100644 modules/sub/errors.js create mode 100644 modules/sub/load-json.js create mode 100644 modules/sub/loc.js create mode 100644 modules/sub/match.js create mode 100644 package.json create mode 100644 strings/en/accessibility.json create mode 100644 strings/en/apiError.json create mode 100644 strings/en/changelog.json create mode 100644 strings/en/desc.json create mode 100644 strings/en/settings.json create mode 100644 strings/en/title.json diff --git a/LICENSE b/LICENSE index f288702..ab7cd99 100644 --- a/LICENSE +++ b/LICENSE @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - - Copyright (C) + cobalt is possibly the nicest social media downloader out there. + Copyright (C) 2022 wukko This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) + cobalt Copyright (C) 2022 wukko This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. @@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0dc1e6d --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# cobalt +Sleek and easy to use social media downloader built on JavaScript. Try it out live: [co.wukko.me](https://co.wukko.me/)! + +![cobalt logo](https://raw.githubusercontent.com/wukko/cobalt/master/files/icons/wide.png "cobalt logo") + +## What is cobalt? +Everyone is annoyed by the mess video downloaders are on the web, and cobalt aims to be the ultimate social media downloader, that is sleek, easy to use, and doesn't bother you with ads or privacy invasion agreement popups. + +cobalt doesn't remux any videos, so videos you get are max quality available (unless you change that in settings). + +## What's supported? +- Twitter +- YouTube and YouTube Music +- bilibili.com +- Reddit +- VK + +## Stuff that has to be done +- [ ] Quality switching for bilibili and Twitter +- [ ] Clean up the mess that localisation is right now + - [ ] Sort contents of .json files + - [ ] Rename each entry key to be less linked to specific service (entries like youtubeBroke are awful, I'm sorry) +- [ ] Add support for more languages when localisation clean up is done +- [ ] Clean up css +- [ ] Use esmbuild to minify frontend css and js +- [ ] Make switch buttons in settings selectable with keyboard +- [ ] Do something about changelog because the way it is right now is not really great +- [ ] Remake page rendering module to be more versatile +- [ ] Clean up code to be more consistent across modules +- [ ] Matching could be redone, I'll see what I can do +- [ ] Facebook and Instagram support +- [ ] TikTok support (?) +- [ ] Support for bilibili.tv (?) + +## Disclaimer +This is my passion project, so update scheduele depends on my motivation. Don't expect any consistency in that. + +## Make your own homegrown cobalt +Code might be a little messy, but I promise to improve it over time. + +### Requirements +- Node.js 14.16 or newer +- git + +### npm modules +- express +- got +- url-pattern +- xml-js +- dotenv +- express-rate-limit +- ffmpeg-static +- node-cache +- ytdl-core + +Setup script installs all needed **npm** dependencies, but you have to install Node.js and git yourself, if you don't have those already. + +1. Clone the repo: `git clone https://github.com/wukko/cobalt` +2. Run setup script and follow instructions: `npm run setup` +3. Run cobalt via `npm start` or `node cobalt` +4. Done. + +## License +cobalt is under [GPL-3.0 license](https://github.com/wukko/cobalt/LICENSE), keep that in mind when doing something with it. diff --git a/cobalt.js b/cobalt.js new file mode 100644 index 0000000..f7d3b0e --- /dev/null +++ b/cobalt.js @@ -0,0 +1,126 @@ +import "dotenv/config" + +import express from "express"; +import * as fs from "fs"; +import rateLimit from "express-rate-limit"; + +import currentCommit from "./modules/sub/current-commit.js"; +import { appName, genericUserAgent, version, internetExplorerRedirect } from "./modules/config.js"; +import { getJSON } from "./modules/api.js"; +import renderPage from "./modules/page-renderer.js"; +import { apiJSON } from "./modules/sub/api-helper.js"; +import loc from "./modules/sub/loc.js"; +import { Bright, Cyan } from "./modules/sub/console-text.js"; +import stream from "./modules/stream/stream.js"; + +const commitHash = currentCommit(); +const app = express(); + +if (fs.existsSync('./.env') && fs.existsSync('./config.json')) { + const apiLimiter = rateLimit({ + windowMs: 20 * 60 * 1000, + max: 100, + standardHeaders: true, + legacyHeaders: false, + handler: (req, res, next, opt) => { + res.status(429).json({ "status": "error", "text": loc('en', 'apiError', 'rateLimit') }); + } + }) + const apiLimiterStream = rateLimit({ + windowMs: 6 * 60 * 1000, + max: 24, + standardHeaders: true, + legacyHeaders: false, + handler: (req, res, next, opt) => { + res.status(429).json({ "status": "error", "text": loc('en', 'apiError', 'rateLimit') }); + } + }) + + app.set('etag', 'strong'); + app.use('/api/', apiLimiter); + app.use('/api/stream', apiLimiterStream); + app.use('/', express.static('files')); + + // avoid the %% URIError + app.use((req, res, next) => { + try { + decodeURIComponent(req.path) + } + catch(e) { + return res.redirect(process.env.selfURL); + } + next(); + }); + + app.get('/api/:type', async (req, res) => { + try { + switch (req.params.type) { + case 'json': + if (req.query.url && req.query.url.length < 150) { + let j = await getJSON( + req.query.url.trim(), + req.header('x-forwarded-for') ? req.header('x-forwarded-for') : req.ip, + req.header('Accept-Language') ? req.header('Accept-Language').slice(0, 2) : "en", + req.query.format ? req.query.format.slice(0, 5) : "webm", + req.query.quality ? req.query.quality.slice(0, 3) : "max" + ) + res.status(j.status).json(j.body); + } else { + let j = apiJSON(3, { t: loc('en', 'apiError', 'noURL', process.env.selfURL) }) + res.status(j.status).json(j.body); + } + break; + case 'stream': + if (req.query.p) { + res.status(200).json({ "status": "continue" }); + } else if (req.query.t) { + let ip = req.header('x-forwarded-for') ? req.header('x-forwarded-for') : req.ip + stream(res, ip, req.query.t, req.query.h, req.query.e); + } else { + let j = apiJSON(0, { t: loc('en', 'apiError', 'noStreamID') }) + res.status(j.status).json(j.body); + } + break; + default: + let j = apiJSON(0, { t: loc('en', 'apiError', 'noType') }) + res.status(j.status).json(j.body); + break; + } + } catch (e) { + res.status(500).json({ 'status': 'error', 'text': 'something went wrong.' }) + } + }); + app.get("/api", async (req, res) => { + res.redirect('/api/json') + }); + app.get("/", async (req, res) => { + // redirect masochists to a page where they can install a proper browser + if (req.header("user-agent") && req.header("user-agent").includes("Trident")) { + if (internetExplorerRedirect.newNT.includes(req.header("user-agent").split('NT ')[1].split(';')[0])) { + res.redirect(internetExplorerRedirect.new) + return + } else { + res.redirect(internetExplorerRedirect.old) + return + } + } else { + res.send(renderPage({ + "hash": commitHash, + "type": "default", + "lang": req.header('Accept-Language') ? req.header('Accept-Language').slice(0, 2) : "en", + "useragent": req.header('user-agent') ? req.header('user-agent') : genericUserAgent + })) + } + }); + app.get("/favicon.ico", async (req, res) => { + res.redirect('/icons/favicon.ico'); + }); + app.get("/*", async (req, res) => { + res.redirect('/') + }); + app.listen(process.env.port, () => { + console.log(`\n${Bright(`${appName} (${version})`)}\n\nURL: ${Cyan(`${process.env.selfURL}`)}\nPort: ${process.env.port}\nCurrent commit: ${Bright(`${commitHash}`)}\nStart time: ${Bright(Math.floor(new Date().getTime()))}\n`) + }); +} else { + console.log('Required config files are missing. Please run "npm run setup" first.') +} \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..b969349 --- /dev/null +++ b/config.json @@ -0,0 +1,31 @@ +{ + "appName": "cobalt", + "version": "2.1", + "streamLifespan": 1800000, + "maxVideoDuration": 1920000, + "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", + "repo": "https://github.com/wukko/cobalt", + "supportedLanguages": ["en"], + "authorInfo": { + "name": "wukko", + "link": "https://wukko.me/", + "contact": "https://wukko.me/contacts", + "twitter": "uwukko" + }, + "internetExplorerRedirect": { + "newNT": ["6.1", "6.2", "6.3", "10.0"], + "old": "https://mypal-browser.org/", + "new": "https://vivaldi.com/" + }, + "donations": { + "ethereum": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302", + "bitcoin": "bc1q64amsn0wd60urem3jkhpywed8q8kqwssw6ta5j", + "litecoin": "ltc1qvp0xhrk2m7pa6p6z844qcslfyxv4p3vf95rhna", + "bitcoin cash": "bitcoincash:qph0d7d02mvg5xxqjwjv5ahjx2254yx5kv0zfg0xsj" + }, + "quality": { + "hig": "1080", + "mid": "720", + "low": "480" + } +} \ No newline at end of file diff --git a/files/cobalt.css b/files/cobalt.css new file mode 100644 index 0000000..1ae1398 --- /dev/null +++ b/files/cobalt.css @@ -0,0 +1,488 @@ +:root { + --transparent: rgba(0, 0, 0, 0); + --without-padding: calc(100% - 4rem); + --border-15: 0.15rem solid var(--accent); +} +@media (prefers-color-scheme: dark) { + :root { + --accent: rgb(225, 225, 225); + --accent-hover: rgb(20, 20, 20); + --accent-press: rgb(10, 10, 10); + --accent-unhover: rgb(100, 100, 100); + --accent-unhover-2: rgb(110, 110, 110); + --background: rgb(0, 0, 0); + } +} +@media (prefers-color-scheme: light) { + :root { + --accent: rgb(25, 25, 25); + --accent-hover: rgb(230 230 230); + --accent-press: rgb(240 240 240); + --accent-unhover: rgb(190, 190, 190); + --accent-unhover-2: rgb(110, 110, 110); + --background: rgb(255, 255, 255); + } +} +[data-theme="dark"] { + --accent: rgb(225, 225, 225); + --accent-hover: rgb(20, 20, 20); + --accent-press: rgb(10, 10, 10); + --accent-unhover: rgb(100, 100, 100); + --accent-unhover-2: rgb(110, 110, 110); + --background: rgb(0, 0, 0); +} +[data-theme="light"] { + --accent: rgb(25, 25, 25); + --accent-hover: rgb(230 230 230); + --accent-press: rgb(240 240 240); + --accent-unhover: rgb(190, 190, 190); + --accent-unhover-2: rgb(110, 110, 110); + --background: rgb(255, 255, 255); +} +html, +body { + margin: 0; + background: var(--background); + color: var(--accent); + font-family: 'Noto Sans Mono', 'Consolas', 'SF Mono', monospace; + user-select: none; + -webkit-tap-highlight-color: var(--transparent); + overflow: hidden; + -ms-overflow-style: none; + scrollbar-width: none; +} +a { + color: var(--accent); + text-decoration: none; +} +::placeholder { + color: var(--accent-unhover-2); +} +::-webkit-scrollbar { + display: none; +} +:focus-visible { + outline: var(--border-15); +} +[type=checkbox] { + margin-right: 0.8rem; +} +[type="checkbox"] { + -webkit-appearance: none; + margin-right: 0.8rem; + z-index: 0; +} +[type="checkbox"]::before { + content: ""; + width: 15px; + height: 15px; + border: var(--border-15); + background-color: var(--background); + display: block; + z-index: 5; + position: relative; +} +[type="checkbox"]:checked::before { + box-shadow: inset 0 0 0 0.2rem var(--background); + background-color: var(--accent); +} +button { + background: none; + border: none; + font-family: 'Noto Sans Mono', 'Consolas', 'SF Mono', monospace; + color: var(--accent); + font-size: 0.9rem; +} +input { + border-radius: none; +} +button:hover, +.switch:hover, +.checkbox:hover { + background: var(--accent-hover); + cursor: pointer; +} +button:active, +.switch:active, +.checkbox:active { + background: var(--accent-press); + cursor: pointer; +} +input[type="checkbox"] { + cursor: pointer; +} +.button { + background: none; + border: var(--border-15); + color: var(--accent); + padding: 0.3rem 0.75rem 0.5rem; + font-size: 1rem; +} +.mono { + font-family: 'Noto Sans Mono', 'Consolas', 'SF Mono', monospace; +} +.center { + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +#cobalt-main-box { + position: fixed; + width: 60%; + height: auto; + display: inline-flex; + padding: 3rem; +} +#logo-area { + padding-right: 3rem; + padding-top: 0.1rem; + text-align: left; + font-size: 1rem; + white-space: nowrap; +} +#download-area { + display: inline-flex; + height: 2rem; + width: 100%; + margin-top: -0.6rem; +} +.box { + background: var(--background); + border: var(--border-15); + color: var(--accent); +} +#url-input-area { + background: var(--background); + padding: 1.2rem 1rem; + width: 100%; + color: var(--accent); + border: 0; + float: right; + border-bottom: 0.1rem solid var(--accent-unhover); + transition: border-bottom 0.2s; + outline: none; +} +#url-input-area:focus { + outline: none; + border-bottom: 0.1rem solid var(--accent); +} +#download-button { + height: 2.5rem; + color: var(--accent); + background: none; + border: none; + font-size: 1.4rem; + cursor: pointer; + padding: 0; + letter-spacing: -0.1rem; +} +#download-button:disabled { + color: var(--accent-unhover); + cursor: not-allowed; +} +#footer { + bottom: 0.5rem; + position: absolute; + left: 50%; + transform: translate(-50%, -50%); + font-size: 0.9rem; + text-align: center; + width: auto; +} +#footer-buttons { + display: block; +} +.footer-button { + cursor: pointer; + color: var(--accent-unhover-2); + border: 0.15rem var(--accent-unhover-2) solid; + padding: 0.4rem 0.8rem 0.5rem; +} +.footer-button:hover { + color: var(--accent); + border: var(--border-15); +} +.text-backdrop { + background: var(--accent); + color: var(--background); + padding: 0 0.1rem; +} +::-moz-selection { + background-color: var(--accent); + color: var(--background); +} +::selection { + background-color: var(--accent); + color: var(--background); +} +.popup { + visibility: hidden; + position: fixed; + width: 55%; + height: auto; + z-index: 999; + padding: 3rem; + font-size: 0.9rem; + max-height: 80%; +} +#popup-backdrop { + opacity: 0.5; + background-color: var(--background); + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 998; +} +.popup-narrow { + visibility: hidden; + position: fixed; + width: 30%; + height: auto; + z-index: 999; + padding: 3rem 2rem 2rem 2rem; + font-size: 0.9rem; + max-height: 80%; +} +.popup-narrow.scrollable { + height: 80%; +} +.nowrap { + white-space: nowrap; +} +.about-padding { + padding-bottom: 1.5rem; +} +.popup-subtitle { + font-size: 1.1rem; + padding-bottom: 0.5rem; +} +.little-subtitle { + font-size: 1.05rem; +} +.popup-desc { + width: 100%; + text-align: left; + float: left; + line-height: 1.7rem; +} +.popup-title { + font-size: 1.5rem; + margin-bottom: 0.5rem; + line-height: 1.85em; +} +.popup-footer { + bottom: 0; + position: fixed; + margin-bottom: 1.5rem; + background: var(--background); + width: var(--without-padding); +} +.popup-footer-content { + font-size: 0.8rem; + line-height: 1.7rem; + color: var(--accent-unhover-2); + border-top: 0.05rem solid var(--accent-unhover-2); + padding-top: 0.4rem; +} +.popup-above-title { + color: var(--accent-unhover-2); + font-size: 0.8rem; +} +.popup-narrow .popup-content { + overflow-x: hidden; + overflow-y: auto; + height: var(--without-padding); + scrollbar-width: none; +} +.popup-header { + position: relative; + background: var(--background); + z-index: 999; +} +.popup-content.with-footer { + margin-bottom: 3rem; +} +#close { + cursor: pointer; + float: right; + right: 3rem; + position: absolute; +} +.popup-narrow #close { + right: 0rem; +} +.settings-category { + padding-bottom: 1.2rem; +} +.title { + width: 100%; + text-align: left; + line-height: 1.7rem; + color: var(--accent-unhover-2); + border-bottom: 0.05rem solid var(--accent-unhover-2); + padding-bottom: 0.25rem; +} +.checkbox { + display: inline-flex; + align-items: center; + flex-direction: row; + flex-wrap: nowrap; + align-content: center; + padding: 0.6rem; + padding-right: 1rem; + border: 0.1rem solid; + width: auto; + margin: 0 1rem 0 0; +} +.checkbox-label { + line-height: 1.3rem; +} +.switch-container { + width: 100%; +} +.subtitle { + width: 100%; + text-align: left; + line-height: 1.7rem; + padding-bottom: 0.4rem; + color: var(--accent); + margin-top: 1rem; +} +.explanation { + padding-top: 1rem; + width: 100%; + font-size: 0.8rem; + text-align: left; + line-height: 1.3rem; + color: var(--accent-unhover-2); +} +.switch { + border-top: solid 0.1rem var(--accent); + border-bottom: solid 0.1rem var(--accent); + padding: 0.8rem; + width: 100%; + text-align: center; + color: var(--accent); + background: var(--background); + display: grid; + align-items: center; + cursor: pointer; +} +.switch.full { + border: solid 0.1rem var(--accent); +} +.switch.left { + border-left: solid 0.1rem var(--accent); +} +.switch.right { + border-right: solid 0.1rem var(--accent); +} +.switch[data-enabled="true"] { + color: var(--background); + background: var(--accent); + cursor: default; +} +.switches { + display: flex; + width: auto; + flex-direction: row; + flex-wrap: nowrap; +} +.text-to-copy { + user-select: text; + border: var(--border-15); + padding: 1rem; + overflow: auto; +} +/* adapt the page according to screen size */ +@media screen and (min-width: 2300px) { + html { + zoom: 130%; + } +} +@media screen and (min-width: 3840px) { + html { + zoom: 180%; + } +} +@media screen and (min-width: 5000px) { + html { + zoom: 300%; + } +} +@media screen and (max-width: 1440px) { + #cobalt-main-box, + .popup { + width: 65%; + } + .popup-narrow { + width: 35%; + } +} +@media screen and (max-width: 1024px) { + #cobalt-main-box, + .popup { + width: 75%; + } + .popup-narrow { + width: 50%; + } +} +@media screen and (max-height: 850px) { + .popup-narrow { + height: 80% + } +} +/* mobile page */ +@media screen and (max-width: 768px) { + #logo-area { + padding-right: 0; + padding-top: 0; + position: fixed; + line-height: 0; + margin-top: -2rem; + width: 100%; + text-align: center; + } + #cobalt-main-box { + width: 80%; + display: flex; + border: none; + padding: 0; + } + .popup, + .popup-narrow, + .popup-narrow.scrollable { + border: none; + width: 90%; + height: 90%; + max-height: 100%; + } +} +@media screen and (max-width: 524px) { + #logo-area { + padding-right: 0; + padding-top: 0; + position: fixed; + line-height: 0; + margin-top: -2rem; + width: 100%; + text-align: center; + } + #cobalt-main-box { + width: 90%; + display: flex; + border: none; + padding: 0; + } + .popup, + .popup-narrow, + .popup-narrow.scrollable { + border: none; + width: 90%; + height: 90%; + max-height: 100%; + } +} \ No newline at end of file diff --git a/files/cobalt.js b/files/cobalt.js new file mode 100644 index 0000000..f47c5c4 --- /dev/null +++ b/files/cobalt.js @@ -0,0 +1,214 @@ +function eid(id) { + return document.getElementById(id) +} +function enable(id) { + eid(id).dataset.enabled = "true"; +} +function disable(id) { + eid(id).dataset.enabled = "false"; +} +function vis(state) { + return (state === 1) ? "visible" : "hidden"; +} +function changeDownloadButton(action, text) { + switch (action) { + case 0: + eid("download-button").disabled = true + if (localStorage.getItem("alwaysVisibleButton") == "true") { + eid("download-button").value = text + eid("download-button").style.padding = '0 1rem' + } else { + eid("download-button").value = '' + eid("download-button").style.padding = '0' + } + break; + case 1: + eid("download-button").disabled = false + eid("download-button").value = text + eid("download-button").style.padding = '0 1rem' + break; + case 2: + eid("download-button").disabled = true + eid("download-button").value = text + eid("download-button").style.padding = '0 1rem' + break; + } +} +document.addEventListener("keydown", function(event) { + if (event.key == "Tab") { + eid("download-button").value = '>>' + eid("download-button").style.padding = '0 1rem' + } +}) +function button() { + let regex = /https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/.test(eid("url-input-area").value); + regex ? changeDownloadButton(1, '>>') : changeDownloadButton(0, '>>'); +} +function copy(id) { + let e = document.getElementById(id); + e.classList.add("text-backdrop"); + navigator.clipboard.writeText(e.innerText); + setTimeout(() => { e.classList.remove("text-backdrop") }, 600); +} +function detectColorScheme() { + let theme = "auto"; + let localTheme = localStorage.getItem("theme"); + if (localTheme) { + theme = localTheme; + } else if (!window.matchMedia) { + theme = "dark" + } + document.documentElement.setAttribute("data-theme", theme); +} +function popup(type, action, text) { + eid("popup-backdrop").style.visibility = vis(action); + switch (type) { + case "about": + eid("popup-about").style.visibility = vis(action); + if (!localStorage.getItem("seenAbout")) { + localStorage.setItem("seenAbout", "true"); + } + break; + case "error": + eid("desc-error").innerHTML = text; + eid("popup-error").style.visibility = vis(action); + break; + case "settings": + eid("popup-settings").style.visibility = vis(action); + break; + case "changelog": + eid("popup-changelog").style.visibility = vis(action); + break; + case "donate": + eid("popup-donate").style.visibility = vis(action); + break; + } +} +function changeSwitcher(li, b, u) { + if (u) { + localStorage.setItem(li, b); + } + let l = { + "theme": ["auto", "light", "dark"], + "youtubeFormat": ["mp4", "webm", "audio"], + "quality": ["max", "hig", "mid", "low"] + } + if (b) { + for (i in l[li]) { + if (l[li][i] == b) { + enable(`${li}-${b}`) + } else { + disable(`${li}-${l[li][i]}`) + } + } + if (li == "theme") { + detectColorScheme(); + } + } else { + localStorage.setItem(li, l[li][0]); + for (i in l[li]) { + if (l[li][i] == l[li][0]) { + enable(`${li}-${l[li][0]}`) + } else { + disable(`${li}-${l[li][i]}`) + } + } + } +} +function internetError() { + eid("url-input-area").disabled = false + changeDownloadButton(2, '!!') + popup("error", 1, loc.noInternet); +} +function checkbox(action) { + switch(action) { + case 'alwaysVisibleButton': + if (eid("always-visible-button").checked) { + localStorage.setItem("alwaysVisibleButton", "true"); + button(); + } else { + localStorage.setItem("alwaysVisibleButton", "false"); + button(); + } + break; + } +} +function loadSettings() { + if (localStorage.getItem("alwaysVisibleButton") == "true") { + eid("always-visible-button").checked = true; + eid("download-button").value = '>>' + eid("download-button").style.padding = '0 1rem'; + } + changeSwitcher("theme", localStorage.getItem("theme")) + changeSwitcher("youtubeFormat", localStorage.getItem("youtubeFormat")) + changeSwitcher("quality", localStorage.getItem("quality")) +} +async function download(url) { + changeDownloadButton(2, '...'); + eid("url-input-area").disabled = true; + let format = ''; + if (url.includes(".youtube.com/") || url.includes("/youtu.be/")) { + format = `&format=${localStorage.getItem("youtubeFormat")}` + } + fetch(`/api/json?quality=${localStorage.getItem("quality")}${format}&url=${encodeURIComponent(url)}`).then(async (response) => { + let j = await response.json(); + if (j.status != "error" && j.status != "rate-limit") { + switch (j.status) { + case "redirect": + changeDownloadButton(2, '>>>') + setTimeout(() => { + changeDownloadButton(1, '>>') + eid("url-input-area").disabled = false + }, 3000) + if (navigator.userAgent.toLowerCase().match("iphone os")) { + window.location.href = j.url; + } else { + window.open(j.url, '_blank'); + } + break; + case "stream": + changeDownloadButton(2, '?..') + fetch(`${j.url}&p=1&origin=front`).then(async (response) => { + let jp = await response.json(); + if (jp.status == "continue") { + changeDownloadButton(2, '>>>') + window.location.href = j.url + setTimeout(() => { + changeDownloadButton(1, '>>') + eid("url-input-area").disabled = false + }, 5000) + } else { + eid("url-input-area").disabled = false + changeDownloadButton(2, '!!') + popup("error", 1, jp.text); + } + }).catch((error) => { + internetError() + }); + break; + } + } else { + eid("url-input-area").disabled = false + changeDownloadButton(2, '!!') + popup("error", 1, j.text); + } + }).catch((error) => { + internetError() + }); +} +window.onload = function () { + loadSettings(); + detectColorScheme(); + changeDownloadButton(0, '>>'); + eid("cobalt-main-box").style.visibility = 'visible'; + eid("footer").style.visibility = 'visible'; + eid("url-input-area").value = ""; + if (!localStorage.getItem("seenAbout")) { + popup('about', 1) + } +} +eid("url-input-area").addEventListener("keyup", (event) => { + if (event.key === 'Enter') { + eid("download-button").click(); + } +}) \ No newline at end of file diff --git a/files/cobalt.webmanifest b/files/cobalt.webmanifest new file mode 100644 index 0000000..c26bac4 --- /dev/null +++ b/files/cobalt.webmanifest @@ -0,0 +1,64 @@ +{ + "name": "cobalt", + "short_name": "cobalt", + "start_url": "/", + "icons": [ + { + "src": "/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, { + "src": "/icons/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, { + "src": "/icons/generic.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, { + "src": "/icons/maskable/x48.png", + "sizes": "48x48", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + }, { + "src": "/icons/maskable/x1280.png", + "sizes": "1280x1280", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#000000", + "background_color": "#000000", + "display": "standalone" +} \ No newline at end of file diff --git a/files/fonts/notosansmono/notosansmono.css b/files/fonts/notosansmono/notosansmono.css new file mode 100644 index 0000000..f87ca7f --- /dev/null +++ b/files/fonts/notosansmono/notosansmono.css @@ -0,0 +1 @@ +@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_DdVXQQ.woff2') format('woff2');unicode-range:U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_ndVXQQ.woff2') format('woff2');unicode-range:U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_HdVXQQ.woff2') format('woff2');unicode-range:U+1F00-1FFF}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_7dVXQQ.woff2') format('woff2');unicode-range:U+0370-03FF}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_LdVXQQ.woff2') format('woff2');unicode-range:U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_PdVXQQ.woff2') format('woff2');unicode-range:U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF}@font-face{font-family:'Noto Sans Mono';font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url('notosansmono_3dVQ.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD} \ No newline at end of file diff --git a/files/fonts/notosansmono/notosansmono_3dVQ.woff2 b/files/fonts/notosansmono/notosansmono_3dVQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1174c3628ccfb454e1934e3c3570f289cad42b9d GIT binary patch literal 9292 zcmV-SB(vLhPew8T0RR9103=KR4gdfE07_f{03-1L0RR9100000000000000000000 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 literal 0 HcmV?d00001 diff --git a/files/fonts/notosansmono/notosansmono_7dVXQQ.woff2 b/files/fonts/notosansmono/notosansmono_7dVXQQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e9fcb07d7865eb71abf45f9d3232f00cdb766753 GIT binary patch literal 5912 zcmV+z7w71APew8T0RR9102del4gdfE04^*502aLf0RR9100000000000000000000 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 literal 0 HcmV?d00001 diff --git a/files/fonts/notosansmono/notosansmono_HdVXQQ.woff2 b/files/fonts/notosansmono/notosansmono_HdVXQQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..95050fe689e872094a48523cba6723519f3ab3ed GIT binary patch literal 4460 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 literal 0 HcmV?d00001 diff --git a/files/fonts/notosansmono/notosansmono_LdVXQQ.woff2 b/files/fonts/notosansmono/notosansmono_LdVXQQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..137ea72c0d381bec968d3b2c4ecb74fe75557b48 GIT binary patch literal 2592 zcmV+*3g7j2Pew8T0RR91016-g4gdfE02)XD013tb0RR9100000000000000000000 z0000QHX9%uHU?lnQ&d4zBmjI0qyV603xW&)0X7081B3_!AO(aP2Ot})7NeqmaZ+Lg z|2&M1S%-(IUB#+h8jGOPVbt=R z6CDt2{~E}C0l)v>+N<50nHSyw+fCTG%$X87xx0U9&i@l+Sq8Pg_(7{k?Go$n+ZzOo3hK*CQ7~G82qrTS5CQ0ysIpaXo$*>vcvn|gT-T!Xn^ee=q-STP$xi+1%Z%&D{|%5 zbjrozAONO34htcmbTC_dF(6yb;`-wKd14R|hIo;|#7eu#N$JnhU!{M`kGfnyDDfnN zh=q2M6VhGMU!=d4zn3manf-oG?9y-lL;~A^>*@mjgx`AWRuFWZ zxkCm+)DnK{93a|lA|8h%<6^IQ$LVJoUs}r( z1EVKmszYt(bu#Y;zx*2f)uH>#g6B@uMe$RexAd1Ob_n+UjpzNz#{KIzb>)OiRm+dw z`bHSPZRgGqGhcnOx3_NFojV~sw!C;Th+~KtMnO9U=PplwljGPv9Qo*4$8YNDB19&( zTzyD8tJJIB>{3U>sMF}tzjkNmDucltcs1nEizv?4`DR3+@9??Bf{$hVu3p>tHr8$1 zd*aHi{&SZeDmfamk?TBv`TW(chXQLaTp{zE&HH@G<_nX0=G?1sq1+@DAH4C-b_5&z zwr+<%`69vVgD2BIeA2w;*Ytfyv*V6#Ok4MB#IrEd2Yx1)oyTgjovIqu*V#3&6s!`5 z>bZiY1#7Ia=9y!NFuPurULU%+s(sXcyUCQ@piFBBTcp*7ElI0yRAp4FGc=*o4GXeX zSE@5LVVlU%wB^}hu&~mSrkGUzyyGO<25 z#oA$RuvCfj+2*XDIqi@fY4>o2nas-#;AKO{K`R_RTaz`zWJsIYkyV;MK2_abrkok7 zD5#K+8lv|?!L?ea? z)#k{|+{g(z)oIh)(hCaO)6!?uWJgZWirCLvo5#_d{%c?s!wO%+Qbjgf-^+ zx^YeAsR~VM{IvjGibqpOl_b|K#OGry{>?}GsqWOT9h4~5eU@f)nU6X^ zYaf}FTi;ldVUh|9jfI$F_3k}z)^=`xZ-gj!PD5AM$ko{xT$5K>D$A&TYV(!Znle3f zWf(F+e+Ng+@So092Kmj&m3jHu%!#Fmxg-4M#4Eh~&9*~9%y-mye}74kpn00E0L>Qn`+N0@iW~_@*z^&(%0(`66D^Jqu_Dkbs{$vq+>J4! ziYkgI8(0DzXg|beKT|HEAct7OZbflscr6qW6H!GKMU)LKF@l8|KS z$ffS_@0rCKK%Ek+WHh=MN|-()lTq^x<@AekcFmeFpYTT}8?2`w^{`u$>CyeKh4neG z{o-nhsWZWi|Bs!u6!cB1@n{+?MkP$2Q8}YF*)>b@34dg=!Fr0|p<#&W=_Y*b)N@P= z-e)?s-mt(IfLiUdvBe6aPTA8E8uHokzF|iFSy4|u{lfdg7;V5;09{f8$fgsi^E1{V zpEd6rX4Jwr+!Or*T-9Ubzg+gIMbY%To?9G36?#x_?VRsh5M7A9lwPVvZXY86C|TCj zwJ)VklJdJd!+Zt6d$-b!xR5h?)&Kv!4RB|l{@&oN00I7EvCB49{Qu1Zg4#4^jHung z3uag_80nA!=z#`_D1-{xh{2D5_F^?SZHZ7i=3*7Tz$7ip!QfsM@8>O63-j_ygr%@B ztYeV^>B~ABGFglTz;zCVf5_hFt!jM>db*bo0E-fZf@H!Efl}_PTLU6|F(6h5pccF~ z!d`6~qrzv_;S@VHVB13UUbRJ-eb-ju81J@5XmVRyBGTATAk_N&2r@DqS=zLl#?ER2 z3kNx+SR;)hm#0lzxw_dnRoocQG6E{2fac!HaB`xn#@S8d*iBxmv4O6o+BmdrsnVd8 z9xu`6ur?Q4tR`v6P!nX=W)19=P_d1!@si~!Q+eX22J3Jw4u@XZDP}!YF2|qaR^@EE zwo9U*z!+uq-CC0)@>R3_yxZ7Kpim}PalP?148hEe8(7O<*ujDU0KnBv2_OIz0001O CKLx%3 literal 0 HcmV?d00001 diff --git a/files/fonts/notosansmono/notosansmono_PdVXQQ.woff2 b/files/fonts/notosansmono/notosansmono_PdVXQQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e3267a5ef390d835dfeaa411c652a523045aad90 GIT binary patch literal 26380 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 literal 0 HcmV?d00001 diff --git a/files/fonts/notosansmono/notosansmono_ndVXQQ.woff2 b/files/fonts/notosansmono/notosansmono_ndVXQQ.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..bb374b0a546c5711cfa8f97be947d5768065be78 GIT binary patch literal 5272 zcmV;J6ld#qPew8T0RR9102G)24gdfE04QVt02Dj`0RR9100000000000000000000 z0000QJR2Y!fd&R(KT}jeR3rd_0t&w%v0n><6aWD>0we>3AOs)Y=>{A+iGoAJ+ml9|e`EL!|xo047?ZqWRQcopIumPbT_-*z+d^nvt~YYf9Cycx(w<7l})(UJz|NfwTV7p zmOz?_zCUsA0v_-f1p=5%D+R%#7W-fG13VmT0YHHN_HpZ@nzg)V+HsE`6gP=CUVI5& z_kwrHij4lC{9$^ulZK!s_f-YxLMO75aI|Spg@_Tte zfB=PXBqnn)B2X-YzzVA&u=WGU-Y5W+R}Bcj>2ras9x^bbu|T%E1lO+ZnpFKw63`D> zd(TZJfcc&ZkHNqvFf#!IVW%f()dN&0zD#l|R7+q-3W#o*L^70s!rCTNg)AUICS-DT z*?^$q&JfT+32TwlEC68>!2tkj@-&DS_!$5o^y7z(r< zZC&31)YW0#F>=>W8~}mTGyp0T5I`Us2<369PMwX$W)AabpP3j^gTO>(nFt7}Lt)}k zDitF_CJJaiT7c?N1L{V7=uY$*T4iaB2n2=08c`3r1AY2KS)tyPIy~gy(tnp$KScq7 zW3OD8x6+(>z{Wa2O1)dIk-Z%tPXT~D34m-#-=>&7DME$=51PXflp*H>|EqohY!VkLX} zIpW%gr1D_$b6C&j_$!QdGHu5Y@f9I#S`n{oHAs)ExnzO_OF=Rc`O1xX8+JIU1E#=)3^9uLn$j zNSZ#Xhnc&*y8Qkw1zlJyOkz2tgBH|_iSBm@M09_3stF)RYO2A-d^?O+;>K7UV|M-M za<|Hi;Wd?5;gE!I4`@B;5zyiSWbgVA*~K^OjDXL&VRBP@nL7@31dW|0If` zLGV0J73}>4<8r))dYh$0=L05`K?o(geRG06?Vj&*Zrl566M_T6q z_U@DYDk*}OPOF?99~x;smc5lG#9o}^O;UFTp}{*HHkDlzXZ?OY&Ig0S3i%&^qwlC; z;WmT5P2_nSB=(OfTkw8Jj^Bc9jnl>~40YfG;F_)z$#O1`uB|Tec$YSRoR6(bsnh;h z%hYxyQNy&;uHelFMdKrsbuG8OTgsZ|9ZzjYUCW@QKEGBwy3cX8yjQwvHx2+hBF_UNx4fy);JW7TF~Gj3+xBmJ*d{d};e;)%VZ_{dBmv-pX&~83IZ( z=w|kot1LF??eD9t4uzzGHev4UP{KY3o;$b$b6qNb*69#gxkPJB&`>WDSYp0!dqY`4 zR~;&89X{&!0ONhEUHghx_3rvHKJiu!o%%hdkwJ>>HP&k~S)Md_IVjtj?j7XNl{I|t z`j)QWjRSdP(W_LHO6BaGWAeyr#ZrN#o-FFoN~7`M>rC}v@~~RjqU?VB9B5j1(qhvY zoIbg0o*yL_KVBic5ZD%Cs_6CboX$Njiv3W+ia&fbzP*Y0l z*+FA6MV?HHG*Q?plA}$$pr3>7D!Ek6-S@f5igB4!R_M?ZYVOHD0KF%IXR9b($-~Si zD<1vuU1En4V%qc3JOF8@jVbNp`&FCG#BmwUr^5@UDnPKRVw zorKq|l53zd-6IuQR7a4@|jS0|CAyP`B1%sar4c2L&>B zhYTan+gJB*H+9Idx_P_3ZeET7&M&TCG7*vRMpF`BpuidxE66R}fx&KdBnxmpX-T$q zI{U>m&Ar}NJ7?}E`|Ge-Ax8eYNj@ynNzOqfI^*T-fnD)hQQJ9$P5W|y@#uZJcAlnJ z+F_0~EBN7x-88GgRLW+}yvL-!{L`6@)^@IM{|rGqpnA+8D*=H8A6pr&h2T^mYL|< zX@6~Rd=d}D%mu?}qW?s$rFpRa_Es*AtQ@M!qqKy<2-8$o&zx@rTRz)mXf>9!*ISE! zpmZvlX=Y5$Fe+H0MonpvmtrXb3!E#k{sos^vTU+nHyr8fNa^{@$Xun7Dr&Y>K8x?e zOFDIVi<6CgT2(d%D)Y6{v4;y(<@=r}9sFmK5~MSNR7#LR55n80shXUx+@gn{FKbcH zwSD_=fvRk9BoQI}7NwXNbUBq8q%$XhgASY_A7VrY-IDpFRH;l!qzj$_Mid)zF*>Z= zqxJTl-g@g%-tbVw)kCpgh2ejmrBIQP^ZB!EzK$sxCPm~f!E&Ee#(eHmG*et%^xNv* z!9HKe-|JDxF^9R#-4lj#@bO}O2*Yc;yW8m%&UjgdvSF6P7s$L6jB77>mtx#8RKOHB z*H_%O^jU_AwX|Kx9}Llf{gl3$wXo`~)o;zem36RmVPkqr8rGdIv;1m(=2RhN%|CCC z{XkAE?S8FXo+8@bOPj)7$?q*nzFEzIyW-VcDveWJ!=X{R*ThOHja*aROQTBRX#5nT z;(__xcZP3TH3)VOSC!b#j~=ONK{E@_{l>J7_`;siLB*Y-=5Hw$NdYuv3X_f^bAK>a zP8l1O7h3g@;BD^q2^d3p<`=Fn9xSSm4zu+_og@&5rKVkbU9Y|njf}bNUaQLk*I{Ch zQ+3aHQvJ|P-lWi3F+#JH43@QsjnoCI!7H_61chKCD%H2BO35y+sR)jgEZJ=gex6%> ze?po)lC?JLKBDgcK26G)$cSXPvNV!lo#J%2m$zLiyIELs!DUI~?5Y71kBQ5|u9O$J z3^8A~zph`F4zWlP?^tNWH|a$!yjDz#T@X}Xgz0h`zXN~g`LRxIRbhpklM%Z}t2^5g z^25brF}PUr+qPILFBMIH3pnp`b-Xv~O7MAZX?P~JJj0s`}WQJN{10NH~B?jhc?(pqn%?4e#m8Taev=(hX>dY}a zRFZDbQXxng*ic?lLMlS9^sGqxH&vpg%?0Q+45W-X367aed)eed_E-XjQF7*h{l0KP z)vu-UfIiooKVP^z%Yvt4$y+tKb9qzwL356b>G&R`{5Bm;<)vUxPW3^tui>VO`nA7& z{cA>YyHh#0S%S94=ep)oICc@9jnNV`@VYgWbz!|Kaq~H61IeZ@0N_r zPR^r_K}9dtQYNe92Tu)+uQf|BWal;E@{z!#54G^Qn3OYen8CZqx1>kXfXDc^yfFt; zILHr9FR8m%VaECt#`?Qw?S&he{*$Y6(!S; zp$$ErCd^7(mq)y@6xE?z=w@133K z?YA^NN7YD--40pP0QLJ-;$jy~_B_nVC!H43f9<^7!|2%`#oQ)^qhTmZBV5|nJR^!d z=szhaW6|H?#kax}WR2y|!_Yn!xAeB{t-cR2mK_-`_Prr~Ls<0UtV>e4hP51W8q%g~ z(dz+8k(CgwuH7UZ+f-TQuG&}$S?C<8C~_A0NxdAbL{#6TZn8=Ao$dWav|8p=H&p&fR(${crr@tR74+JPvL`bajHK|vow@flzBlLrj;x*Av#@6^*~O`8qoXSy z)ZOqPS}Y~CfYOM3cj>z@DZF@T5tMR}96o&MFr5EhF@I}*?Fd{}-MNYF3)e+2R2Q z5FYuXOIz2D2R+Ug!!L+;h}i?A%zwh9FmNPvByl9@wTH+z@1xGNqNwHduYRwG%^#W# zhe(lcXTu{}ouEnOU=I>CZ5uc(R>Aaxz!CSidM~ck>v1cdq2koEaQJ^W}&P{3|}4AvYgW9_#*x1^xvb8ax6IWDMI zo+FtRPlUtivHuKL47M{{`#Zg^P%x?F-J4akbFes?_b_qAA@fq=hSKL~(Rr@9;V%y7 z#U|!MRt?$qE!%9>>hyM-8HnQkmeZ}Gvj>HGtJNGN2fp2E+f-n45#io_7N!=r5va=4-suOrZ1zEiH}Lg;8&Bk6slTn_H3c4=;89R{}!`< zv^7-G>XvFH@swDjND8G4bqk#}*v?^O= zs2t8Uil_>_!c&0)y$*o46^!xotcHt~T-pGvUS<-WD{57#N>mM3$MjP+R80A?IFcAL zV|55u`CKNqtSJM4%R!OF9>^G1@TZbfVt>7ejNbH)xettXv z#4EaYmdc@5-p!iZx9L8~hYulBZ*TS;V4@O0S(2FU+e|WU0HP!!{EZ_M! zXMG*ueM1Rw=_8OL20yrH5*)$-5hnL}0$Tx1~!V6g5;In*dxh ze(E1V0AOR7l#xTlD5C7s)dBM1Mnpi+902eFW*`F1yaNeP2pc95p*JXxDhFmjtxpGL z0^9!s3qhFDfyKxf8c-r13AQ0%{E3%`KrS*5{gP7VOo@w%nL)+J(@um+At@==C4`F_ znu^+hWIPfrrcj>dC`{&rO}Wm|h`y+kR3JIeEMP)QT2@n)v}Ru165|mlYAB&DPT?Ek zorV_cp`a&46~&BP-`S^ literal 0 HcmV?d00001 diff --git a/files/icons/android-chrome-192x192.png b/files/icons/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..0d3ac83a24696f6d2e99ffe51177a583e01a6533 GIT binary patch literal 3580 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 literal 0 HcmV?d00001 diff --git a/files/icons/apple-touch-icon.png b/files/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b2abfa63d553dc474982d8b3d36dee71f9ceb0ce GIT binary patch literal 3278 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 N(3fm4)*(HT{tc`e{yYEx literal 0 HcmV?d00001 diff --git a/files/icons/favicon-16x16.png b/files/icons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..94f9c1a907c7a34881b87009d4903b834f305556 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UA=6Sj}hDcoQ?c2-8m?+|Me(!y8 z)?Sy88@=p~?n;X$bu=BR;$ZDnf6QwpHFZAQ#Q*`VY|+_wHC%iQPtNpFW3Km5*`~6w zVFpJ}z@~Cxt7i+Pn6g&Lv^o8~QP1ke*z&IK`m*9E^Un4&37ZQfbDyV%FmnDWzIt%~ z6|TVLTlbs|>b+Vz%k99WYQIet2Q8Yp6huC2Fp5;AC5e4;xGM-W}gvosB>hpHfYLLS0VLDM<83$;d| zeamHOGYLWGj>peT`3wQN4_tn1Bq65f$YUYhYOSEXr(`jm0ICKq$oJJ4r0h;bif&;o z4hbVe?=97A*??k?sTPQ;L5|CfJ{u4WxBV8!;sLv&kEq~b{&;)=hwo_B9UbI+00000 LNkvXXu0mjfO!k}l literal 0 HcmV?d00001 diff --git a/files/icons/favicon.ico b/files/icons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1b08e4bb950906a1ac5b144997ccb290d21b1e8b GIT binary patch literal 9662 zcmeHKODJ|*7~bwlxr8I+eh!DC4`Cu@ppcMT2Fh`n8JWqLnNlWZGU8;wKvI-M2IMZ6 zC<7w~QX+Tn`JVNkXSIC)r_S%&=RbAY>uc||+iN}Rectzd-nG|fS$^)H%VoLmsn(yr zS=KL>W&Q5H+^?)Z+!x-f_N!&BhJU-hy-;^?-<`{efujLO1C9n94LBNbG~j69Zw>hS z`%6GTz?b6T3)YS^8F&W<1}g49$^|(3{i9f4xx*gTNJ~qTm6a8#t*zDZ#~6L`W{22< zf&y7xU6u0k@()=0&U0Yj|s;VmS^Yc@_VD7(> z53U~>87aHFyUI5Bzr4I?E_-`>(%jsvwFEBkgZz_{lH~mS+$(l{eO+p5YCia3#=tHe zVV$6$AZcxFm7Sd(#Rai6{Wv%{P~(_~`Sb(Np`oGD+1V+_$H!i=o12?bUth0tKrG|u zs~=d0Yhi7~3>siFb#-)fByDYN5*!??{D3Z~4fxj5(jr@1TiOp`sVl^$si{fNx6=>s zH+6xSU{6_CSkU%OKTb|gq`kdeLPA26A0`&Os;jG2J)1G0AN%|JDwouf89!fh0X*Uf z4qU6axL9UpW(54vP$T4WdU`5dU0q)N0KTlOOy=k3^?Vb1czF0Bm$YYBPd})w+}vE5 zn3&LYVb%w96&o9?YlB#FDJ(3M>FH_ZJA5#8g}p8!B0^zli#R)aaDld&6MXLwJ2dQ@ zPft%OzFApWnm_G&VsmnG^nL{#@z}$Gp$+1F?Xey>a2?hJ>>`HL)Ya9MBqSt!;NXd^ ztgMvfEG{l8 zEO5j=JUqzo@UYe$Z826~>9K~nm!zhq%J}%WiWvx)nnz7%XJ>!P8+w|Wn$moMMFaoI z$w|q`$k5oYa5XWWyklZwWN2te?(grFP1r`=q2b$5T3V`Y!(Y?~oy2>)hn@PoJ?4a*p|7VPVqO*C*H4*V;BUjy=Dzu~E&#T-Y}4M@2=+z`%gq z-rjzP#W@7u1adNQeCJD#_;q)83u*}soSE;-%S-9$>G|%P%Xs7b(A(Q9s9`kVh{bvx z9Uc1oM4x@(N-XV%hllHLzRCOU?oI{=2Li|2vU4$GzF(7|sRQk4j2P zG;h0W)0=(s9pdEeJZ#SS>=|{B3*$eVx5)|d{Azvqf;Blf&-U$YPAr{RIvQ{^;Ap_n SfTMx`g$CSdblQIvfxiHiNSp`& literal 0 HcmV?d00001 diff --git a/files/icons/generic.png b/files/icons/generic.png new file mode 100644 index 0000000000000000000000000000000000000000..d1777d489d3824d04f6d6db9f1d7e167b5f4dbc7 GIT binary patch literal 9818 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 literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x128.png b/files/icons/maskable/x128.png new file mode 100644 index 0000000000000000000000000000000000000000..49a3370e2776e0c929043184dc32c304dd0462b3 GIT binary patch literal 2648 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 literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x1280.png b/files/icons/maskable/x1280.png new file mode 100644 index 0000000000000000000000000000000000000000..7aff5da7ad9b7be1f0a54472344a8363bdcaac81 GIT binary patch literal 39521 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 literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x384.png b/files/icons/maskable/x384.png new file mode 100644 index 0000000000000000000000000000000000000000..e63b50d551bb4b0622f9911edce6f8c8a4b1ef92 GIT binary patch literal 8751 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 literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x512.png b/files/icons/maskable/x512.png new file mode 100644 index 0000000000000000000000000000000000000000..a58bb230bfe99302e25b7690453bb9e3ada96f34 GIT binary patch literal 14589 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 K?$cSv2mcF;CBp{* literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x72.png b/files/icons/maskable/x72.png new file mode 100644 index 0000000000000000000000000000000000000000..b3d7045ba455e0fa66daf50d39dd1a9f549bced8 GIT binary patch literal 1508 zcmVPx)o=HSORCr$PnOR6xZ4`!IGsH}cvNAO<(k{}JpsYj+vLqWR9KZ^_5HF(8=t}a^ zF1#%Q5r?2qDZ$JpZE(ne$R_hrj+Q0WX7OW4%^qhS>-*cYz zuJ!Gl0DOb4UlMRqK)<&`0Zmgq3P=H&3s^Ns4N`;55m1B7@zV@agVZ2%1k@mN{4|5q zAT`Jw0X4`RKg}RDNDVSaKn*g-PcujjQiIG9P=n0z(+qO7L5mhG!us{=(bLm|XV0Gf z(Z{!W^JXkqumIPuU&ps^f5(6LT5gb!j}K0sJc+2NC}d@2;oP}%c>Ve{zI^#IEv$nF z590j!^Qfq(Kt@IenwpyM`Sa&lg=IHDNl8hvF>dnIsZ*$|tQ1fa6BEA#ML>mxh4T2j zckkri1l-=i5ApKl zOZnZ@wSD_`oIQIMw2;>kFyYXu^73-DwYA~Xr%%(GXyp?u2Q+u?T&!BP3UP69*s)^= zf`fzM=H@2vnHF5Wd>NN6UBbP4_r$0lv&P-s9kH>o*uQ^2Hg4RAWy_Yyb+oRru@S|^ z#kh0l4xT=J>M)xu2V{JD^XARN+O=!3d-rba*|P_$SFgsLIdjAu8bl+mT)BdVh6a52 z@WJ+>(;PoPKkVAIORftK568lV3+3^TA3x&m-MhGU?V4QQ)zxLCEI)lMy8$x(8oLrf zal`TP@mRimxje-1eevRj7;)jk1w4B6NPcH57Z(=<1O#B)wryhLh7B9w>gsCCr@Fd2 zvGK-@8yFZEup21512W#Zr>Ccs+kpcIWWyW>3@bW1I%K2S+1cps?w+(+mgkBUE2JwX zCnrn!(lU0%zP>&wRR(!=b+s5~^>P>>zHx4hpw_HegQG`}N=C61xrT>_rS$Ui^O2jI zJE`pW`26|v;p^)QcDmHmR9o56y8ixt+`4rOr%#_oYisL_Mv9q&bSNO>O)-nOVV2K{ z6DP24-8wisJIljkV`I2~|GtzYLw)LE>3Ms5BRxGG2?+@{zzp*D@84tR&YgJt__1Xn zJ}ZC>o48r#)0s18)vH%+WksMg zje#B!5g|Z>f`V-2G&D3MS;HetSy`DJjDL5G`Rf2NbUDXxa|~mCU%Ys+&6wWaUQ|_8 z$q}TcrUq}`ys?!9joGwmlNhsg>sD#)%quP)HCTe|TAZQSwZ_NC|L^{%z0Puj3^055 z?v>4jhK33ln#8Q&@q!aHCt;q;ILjC%!I_8o!U>v#;mVaOCxNN0t(EfQ>_cPh3C192 zIUvF~cI=phD2G9U;TjwqL{U)@N=iy(b2MgZR`9UOVUb4-qce?;j*2nN7nUM3#t~rb z2FSP{#M>QQ3{eJeUS1wLJ39r`uR`?4+Z`MLA3S&3!LSEIKXjO2W{?_W&Y=O{n`0000< KMNUMnLSTYB@Y|9A literal 0 HcmV?d00001 diff --git a/files/icons/maskable/x96.png b/files/icons/maskable/x96.png new file mode 100644 index 0000000000000000000000000000000000000000..2972d6077830956a5ebec49f1bb2ea87f5aa3644 GIT binary patch literal 1752 zcmchY`&ZHj7{XCVCYV4X<-C=U#xh6dlxg)fNW~`JqMS}m zF|%?% z7ZF;m;y>&%v?bYSQ&S9$?x@>7Bk!`L$~{@f8?i>seWj)5BzIYNrxuof+ns-0=k!m4 z{SJC^&-(%{&UoTe!Ln@_x;8vc<6gsb_u4#O%=8DAv?RYFDp{~oa~>U52{{B1Gu^8e z2;gCVe~Hx#fwiiq)W6pGzql^dmT=c_F)=Z2p`p8#8&!>)Xr!^R)_6^1a`Ezx!#}Q=n3zPZ%=&FNG2w&8IJbxhYcD!o z{H^hG;wd%VEr$>tSJ9W3mzVwQZQwSp&aSSyGMUWA!QtU7!`C1KY~s^tw1Z40no$>{ zeIQP0A!pSBJj0UP0)b|el>2^K7=tTws{$?d?Mu$uOHULEP$*PPJce2qzuEW% zHaA+Ol`j&>Mn{i1RTmWjJvRxFcE%n+Du8-Z9WnlE%G})9k^4U;Pmgf9Tt&NZ=hBCn z9|8jd`(9l}My)TMw4I-us1LB$A5MPoz|I1P(=69N%vL!DmgEh*4Z8wojv9l(aHw@~ zbksR&ZEbz1c65B4=#%*bSZshh@fd#n{jfNA>g!GoWo}bTvVkcUOEsp3 zg_S^qQr-q@E6Tlng(TL~*Kh8>Xf)PZ(jeNS${D5!=kxg)g@w4) zm7$>_%aKXfxK~e+(1$k&-WTPlg-gNJU!tQ;9UP2MXmmhpPj4@g&Hna9ZM0UU-4NG3 zSV&GshHO`F=B~^lp^F#KTm|WkCEhm@!+HzzP&FcvCPkdX@gZwR87)whqF)`7h<S@xy2L0`0(Z4sqS^0Ku4uXT4MCt( z^4`qM9P3TD{#@SXmyn%}gt%PdsZ+(k^wd=Gch|+i(zJPt3Q!W=AHJ}#5H;0_;6w=1 zEB#xNl&`vSGqSR_FqzDOB}cQq+v;a0>ioRy`}_M3bqLa}adquGaMcoN#}BJH)J_iG)4jdZJhsuh+W&$A0s@w+{|Q=DC~kG> zMp8MmvkMFUGQaQl<^epSs(c2isi~~4l5-=?N3RHlu%AIrnT+rjPaqIDH-9fED35(E z?7g)41wH%rZBmhH|1i^=ES`zCx;^{N1hhc~IhJtXm869XU$ literal 0 HcmV?d00001 diff --git a/files/icons/wide.png b/files/icons/wide.png new file mode 100644 index 0000000000000000000000000000000000000000..012c1f813b23c6683d2fd6320288e9e5661ee449 GIT binary patch literal 13485 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 { + if (s[p].enabled) { + return true + } +}).sort().map((p) => { + if (s[p].alias) { + return s[p].alias + } else { + return p + } +}).join(', ') +let donate = `` +for (let i in donations) { + donate += `
${i} (${loc("en", 'desc', 'clicktocopy').trim()})
${donations[i]}
` +} +export default function(obj) { + let isIOS = obj.useragent.toLowerCase().match("iphone os") + try { + return ` + + + + + + ${appName} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +`; + } catch (err) { + return `${loc('en', 'apiError', 'fatal', obj.hash)}`; + } +} \ No newline at end of file diff --git a/modules/services/all.json b/modules/services/all.json new file mode 100644 index 0000000..94a89e9 --- /dev/null +++ b/modules/services/all.json @@ -0,0 +1,72 @@ +{ + "bilibili": { + "alias": "bilibili.com", + "patterns": ["video/:id"], + "quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"], + "enabled": true + }, + "reddit": { + "patterns": ["r/:sub/comments/:id/:title"], + "enabled": true + }, + "twitter": { + "patterns": [":user/status/:id"], + "quality_match": ["1080", "720", "480", "360", "240", "144"], + "enabled": true, + "api": "api.twitter.com", + "token": "AAAAAAAAAAAAAAAAAAAAAIK1zgAAAAAA2tUWuhGZ2JceoId5GwYWU5GspY4%3DUq7gzFoCZs1QfwGoVdvSac3IniczZEYXIcDyumCauIXpcAPorE", + "apiURLs": { + "activate": "1.1/guest/activate.json", + "status_show": "1.1/statuses/show.json" + } + }, + "vk": { + "patterns": ["video-:userId_:videoId"], + "quality_match": { + "2160": 7, + "1440": 6, + "1080": 5, + "720": 3, + "480": 2, + "360": 1, + "240": 0, + "144": 4 + }, + "quality": { + "1080": "hig", + "720": "mid", + "480": "low" + }, + "enabled": true + }, + "youtube": { + "patterns": ["watch?v=:id"], + "quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"], + "quality": { + "1080": "hig", + "720": "mid", + "480": "low" + }, + "enabled": true + }, + "youtube music": { + "patterns": ["watch?v=:id"], + "enabled": true + }, + "tumblr": { + "patterns": ["post/:id"], + "enabled": false + }, + "facebook": { + "patterns": [":pageid/:type/:postid"], + "enabled": false + }, + "instagram": { + "patterns": [":type/:id"], + "enabled": false + }, + "tiktok": { + "patterns": [":pageid/:type/:postid", ":id"], + "enabled": false + } +} \ No newline at end of file diff --git a/modules/services/bilibili.js b/modules/services/bilibili.js new file mode 100644 index 0000000..7282dad --- /dev/null +++ b/modules/services/bilibili.js @@ -0,0 +1,38 @@ +import got from "got"; +import loc from "../sub/loc.js"; +import { genericUserAgent, maxVideoDuration } from "../config.js"; + +export default async function(obj) { + try { + let html = await got.get(`https://bilibili.com/video/${obj.id}`, { + headers: { "user-agent": genericUserAgent } + }); + html.on('error', (err) => { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + }); + html = html.body; + if (html.includes('')[0]); + if (streamData.data.timelength <= maxVideoDuration) { + let video = streamData["data"]["dash"]["video"].filter((v) => { + if (!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/") && v["height"] != 4320) { + return true; + } + }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth)); + let audio = streamData["data"]["dash"]["audio"].filter((a) => { + if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) { + return true; + } + }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth)); + return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], time: streamData.data.timelength, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` }; + } else { + return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) }; + } + } else { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + } + } catch (e) { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + } +} + diff --git a/modules/services/reddit.js b/modules/services/reddit.js new file mode 100644 index 0000000..38e0771 --- /dev/null +++ b/modules/services/reddit.js @@ -0,0 +1,17 @@ +import got from "got"; +import loc from "../sub/loc.js"; +import { genericUserAgent, maxVideoDuration } from "../config.js"; + +export default async function(obj) { + try { + let req = await got.get(`https://www.reddit.com/r/${obj.sub}/comments/${obj.id}/${obj.name}.json`, { headers: { "user-agent": genericUserAgent } }); + let data = (JSON.parse(req.body))[0]["data"]["children"][0]["data"]; + if ("reddit_video" in data["secure_media"] && data["secure_media"]["reddit_video"]["duration"] * 1000 < maxVideoDuration) { + return { urls: [data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0], `${data["secure_media"]["reddit_video"]["fallback_url"].split('_')[0]}_audio.mp4`], filename: `reddit_${data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3]}.mp4` }; + } else { + return { error: loc('en', 'apiError', 'nothingToDownload') }; + } + } catch (err) { + return { error: loc("en", "apiError", "nothingToDownload") }; + } +} \ No newline at end of file diff --git a/modules/services/twitter.js b/modules/services/twitter.js new file mode 100644 index 0000000..494e48c --- /dev/null +++ b/modules/services/twitter.js @@ -0,0 +1,57 @@ +import got from "got"; +import loc from "../sub/loc.js"; +import { services } from "../config.js"; + +const configSt = services.twitter; + +async function fetchTweetInfo(obj) { + let cantConnect = { error: loc('en', 'apiError', 'cantConnectToAPI', 'twitter') } + try { + let _headers = { + "Authorization": `Bearer ${configSt.token}`, + "Host": configSt.api, + "Content-Type": "application/json", + "Content-Length": 0 + }; + let req_act = await got.post(`https://${configSt.api}/${configSt.apiURLs.activate}`, { + headers: _headers + }); + req_act.on('error', (err) => { + return cantConnect + }) + _headers["x-guest-token"] = req_act.body["guest_token"]; + let req_status = await got.get(`https://${configSt.api}/${configSt.apiURLs.status_show}?id=${obj.id}&tweet_mode=extended`, { + headers: _headers + }); + req_status.on('error', (err) => { + return cantConnect + }) + return JSON.parse(req_status.body); + } catch (err) { + return { error: cantConnect }; + } +} +export default async function (obj) { + let nothing = { error: loc('en', 'apiError', 'nothingToDownload') } + try { + let parsbod = await fetchTweetInfo(obj); + if (!parsbod.error) { + if (parsbod.hasOwnProperty("extended_entities") && parsbod["extended_entities"].hasOwnProperty("media")) { + if (parsbod["extended_entities"]["media"][0]["type"] === "video" || parsbod["extended_entities"]["media"][0]["type"] === "animated_gif") { + let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"] + return variants.filter((v) => { + if (v["content_type"] == "video/mp4") { + return true + } + }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"] + } else { + return nothing + } + } else { + return nothing + } + } else return parsbod; + } catch (err) { + return { error: loc("en", "apiError", "youtubeBroke") }; + } +} \ No newline at end of file diff --git a/modules/services/vk.js b/modules/services/vk.js new file mode 100644 index 0000000..aa81e56 --- /dev/null +++ b/modules/services/vk.js @@ -0,0 +1,47 @@ +import got from "got"; +import { xml2json } from "xml-js"; +import loc from "../sub/loc.js"; +import { genericUserAgent, maxVideoDuration, services } from "../config.js"; +import selectQuality from "../stream/select-quality.js"; + +export default async function(obj) { + try { + let html = await got.get(`https://vk.com/video-${obj.userId}_${obj.videoId}`, { headers: { "user-agent": genericUserAgent } }); + html.on('error', (err) => { + return false; + }); + html = html.body; + if (html.includes(`{"lang":`)) { + let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]); + if (js["mvData"]["is_active_live"] == '0') { + if (js["mvData"]["duration"] <= maxVideoDuration / 1000) { + let mpd = JSON.parse(xml2json(js["player"]["params"][0]["manifest"], { compact: true, spaces: 4 })); + + let repr = mpd["MPD"]["Period"]["AdaptationSet"]["Representation"]; + if (!mpd["MPD"]["Period"]["AdaptationSet"]["Representation"]) { + repr = mpd["MPD"]["Period"]["AdaptationSet"][0]["Representation"]; + } + let attr = repr[repr.length - 1]["_attributes"]; + let selectedQuality = `url${attr["height"]}`; + + let maxQuality = js["player"]["params"][0][selectedQuality].split('type=')[1].slice(0, 1) + let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r;})[maxQuality]) + + if (selectedQuality in js["player"]["params"][0]) { + return { url: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${js["player"]["params"][0][selectedQuality].split("id=")[1]}_${attr['width']}x${attr['height']}.mp4` }; + } else { + return { error: loc('en', 'apiError', 'nothingToDownload') }; + } + } else { + return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) }; + } + } else { + return { error: loc('en', 'apiError', 'liveVideo') }; + } + } else { + return { error: loc('en', 'apiError', 'nothingToDownload') }; + } + } catch (err) { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + } +} \ No newline at end of file diff --git a/modules/services/youtube.js b/modules/services/youtube.js new file mode 100644 index 0000000..6f2f4c6 --- /dev/null +++ b/modules/services/youtube.js @@ -0,0 +1,74 @@ +import ytdl from "ytdl-core"; +import loc from "../sub/loc.js"; +import { maxVideoDuration, quality as mq } from "../config.js"; +import selectQuality from "../stream/select-quality.js"; + +export default async function (obj) { + try { + let info = await ytdl.getInfo(obj.id); + if (info) { + info = info.formats; + if (!info[0]["isLive"]) { + if (obj.isAudioOnly) { + obj.format = "webm" + obj.quality = "max" + } + let selectedVideo, videoMatch = [], video = [], audio = info.filter((a) => { + if (!a["isLive"] && !a["isHLS"] && !a["isDashMPD"] && a["hasAudio"] && !a["hasVideo"] && a["container"] == obj.format) { + return true; + } + }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); + if (!obj.isAudioOnly) { + video = info.filter((a) => { + if (!a["isLive"] && !a["isHLS"] && !a["isDashMPD"] && !a["hasAudio"] && a["hasVideo"] && a["container"] == obj.format && a["height"] != 4320) { + if (obj.quality != "max" && mq[obj.quality] == a["height"]) { + videoMatch.push(a) + } + return true; + } + }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); + selectedVideo = video[0] + if (obj.quality != "max") { + if (videoMatch.length > 0) { + selectedVideo = videoMatch[0] + } else { + let ss = selectQuality("youtube", obj.quality, video[0]["height"]) + selectedVideo = video.filter((a) => { + if (a["height"] == ss) { + return true + } + }) + selectedVideo = selectedVideo[0] + } + } + if (obj.quality == "los") { + selectedVideo = video[video.length - 1] + } + } + if (audio[0]["approxDurationMs"] <= maxVideoDuration) { + if (!obj.isAudioOnly && video.length > 0) { + let filename = `youtube_${obj.id}_${selectedVideo["width"]}x${selectedVideo["height"]}.${obj.format}`; + if (video.length > 0 && audio.length > 0) { + return { type: "render", urls: [selectedVideo["url"], audio[0]["url"]], time: video[0]["approxDurationMs"], filename: filename }; + } else { + return { error: loc('en', 'apiError', 'youtubeBroke') }; + } + } else if (audio.length > 0) { + return { type: "render", isAudioOnly: true, urls: [audio[0]["url"]], filename: `youtube_${obj.id}_${audio[0]["audioBitrate"]}kbps.opus` }; + } else { + return { error: loc('en', 'apiError', 'youtubeBroke') }; + } + } else { + return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) }; + } + } else { + return { error: loc('en', 'apiError', 'liveVideo') }; + } + } else { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + } + } catch (e) { + return { error: loc('en', 'apiError', 'youtubeFetch') }; + } +} + diff --git a/modules/setup.js b/modules/setup.js new file mode 100644 index 0000000..d230c19 --- /dev/null +++ b/modules/setup.js @@ -0,0 +1,54 @@ +import { randomBytes } from "crypto"; +import { existsSync, unlinkSync, appendFileSync } from "fs"; +import { createInterface } from "readline"; +import { Cyan, Bright, Green } from "./sub/console-text.js"; +import { execSync } from "child_process"; + +let envPath = './.env'; +let q = `${Cyan('?')} \x1b[1m`; +let ob = { streamSalt: randomBytes(64).toString('hex') } +let rl = createInterface({ input: process.stdin,output: process.stdout }); + +console.log( + `${Cyan("Welcome to cobalt!")}\n${Bright("We'll get you up and running in no time.\nLet's start by creating a ")}${Cyan(".env")}${Bright(" file. You can always change it later.")}` +) +console.log( + Bright("\nWhat's the selfURL we'll be running on? (localhost)") +) + +rl.question(q, r1 => { + if (r1) { + ob['selfURL'] = `https://${r1}/` + } else { + ob['selfURL'] = `http://localhost` + } + console.log(Bright("\nGreat! Now, what's the port we'll be running on? (9000)")) + rl.question(q, r2 => { + if (!r1 && !r2) { + ob['selfURL'] = `http://localhost:9000/` + ob['port'] = 9000 + } else if (!r1 && r2) { + ob['selfURL'] = `http://localhost:${r2}/` + ob['port'] = r2 + } else { + ob['port'] = r2 + } + final() + }); +}) + +let final = () => { + if (existsSync(envPath)) { + unlinkSync(envPath) + } + for (let i in ob) { + appendFileSync(envPath, `${i}=${ob[i]}\n`) + } + console.log(Bright("\nI've created a .env file with selfURL, port, and stream salt.")) + 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]}); + console.log(`\n\n${Green("All done!\n")}`) + console.log("You can re-run this script any time to update the configuration.") + console.log("\nYou're now ready to start the main project.\nHave fun!") + rl.close() +} \ No newline at end of file diff --git a/modules/stream/manage.js b/modules/stream/manage.js new file mode 100644 index 0000000..8a43bd8 --- /dev/null +++ b/modules/stream/manage.js @@ -0,0 +1,45 @@ +import NodeCache from "node-cache"; + +import { UUID, encrypt } from "../sub/crypto.js"; +import { streamLifespan } from "../config.js"; + +const streamCache = new NodeCache({ stdTTL: streamLifespan, checkperiod: 120 }); + +export function createStream(obj) { + let streamUUID = UUID(), + exp = Math.floor(new Date().getTime()) + streamLifespan, + ghmac = encrypt(`${streamUUID},${obj.url},${obj.ip},${exp}`, obj.salt), + iphmac = encrypt(`${obj.ip}`, obj.salt); + + streamCache.set(streamUUID, { + id: streamUUID, + service: obj.service, + type: obj.type, + urls: obj.urls, + filename: obj.filename, + hmac: ghmac, + ip: iphmac, + exp: exp, + isAudioOnly: obj.isAudioOnly ? true : false, + time: obj.time + }); + return `${process.env.selfURL}api/stream?t=${streamUUID}&e=${exp}&h=${ghmac}`; +} + +export function verifyStream(ip, id, hmac, exp, salt) { + try { + let streamInfo = streamCache.get(id); + if (streamInfo) { + let ghmac = encrypt(`${id},${streamInfo.url},${ip},${exp}`, salt); + if (hmac == ghmac && encrypt(`${ip}`, salt) == streamInfo.ip && ghmac == streamInfo.hmac && exp > Math.floor(new Date().getTime()) && exp == streamInfo.exp) { + return streamInfo; + } else { + return { error: 'Unauthorized', status: 401 }; + } + } else { + return { error: 'this stream token does not exist', status: 400 }; + } + } catch (e) { + return { status: 500, body: { status: "error", text: "Internal Server Error" } }; + } +} \ No newline at end of file diff --git a/modules/stream/select-quality.js b/modules/stream/select-quality.js new file mode 100644 index 0000000..2bc2228 --- /dev/null +++ b/modules/stream/select-quality.js @@ -0,0 +1,32 @@ +import { services, quality as mq } from "../config.js"; + +function closest(goal, array) { + return array.sort().reduce(function(prev, curr) { + return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev); + }); +} + +export default function(service, quality, maxQuality) { + if (quality == "max") { + return maxQuality + } + + quality = parseInt(mq[quality]) + maxQuality = parseInt(maxQuality) + + if (quality >= maxQuality || quality == maxQuality) { + return maxQuality + } + if (quality < maxQuality) { + if (services[service]["quality"][quality]) { + return quality + } else { + let s = Object.keys(services[service]["quality_match"]).filter((q) => { + if (q <= quality) { + return true + } + }) + return closest(quality, s) + } + } +} \ No newline at end of file diff --git a/modules/stream/stream.js b/modules/stream/stream.js new file mode 100644 index 0000000..b24badb --- /dev/null +++ b/modules/stream/stream.js @@ -0,0 +1,27 @@ +import { apiJSON } from "../sub/api-helper.js"; +import { verifyStream } from "./manage.js"; +import { streamAudioOnly, streamDefault, streamLiveRender } from "./types.js"; + +export default function(res, ip, id, hmac, exp) { + try { + let streamInfo = verifyStream(ip, id, hmac, exp, process.env.streamSalt); + if (!streamInfo.error) { + if (streamInfo.isAudioOnly) { + streamAudioOnly(streamInfo, res); + } else { + switch (streamInfo.type) { + case "render": + streamLiveRender(streamInfo, res); + break; + default: + streamDefault(streamInfo, res); + break; + } + } + } else { + res.status(streamInfo.status).json(apiJSON(0, { t: streamInfo.error }).body); + } + } catch (e) { + internalError(res) + } +} diff --git a/modules/stream/types.js b/modules/stream/types.js new file mode 100644 index 0000000..66d20dd --- /dev/null +++ b/modules/stream/types.js @@ -0,0 +1,131 @@ +import { spawn } from "child_process"; +import ffmpeg from "ffmpeg-static"; +import got from "got"; +import { genericUserAgent } from "../config.js"; +import { msToTime } from "../sub/api-helper.js"; +import { internalError } from "../sub/errors.js"; +import loc from "../sub/loc.js"; + +export async function streamDefault(streamInfo, res) { + try { + res.setHeader('Content-disposition', `attachment; filename="${streamInfo.filename}"`); + const stream = got.get(streamInfo.urls, { + headers: { + "user-agent": genericUserAgent + }, + isStream: true + }); + stream.pipe(res).on('error', (err) => { + internalError(res); + throw Error("File stream pipe error."); + }); + stream.on('error', (err) => { + internalError(res); + throw Error("File stream error.") + }); + } catch (e) { + internalError(res); + } +} +export async function streamLiveRender(streamInfo, res) { + try { + if (streamInfo.urls.length == 2) { + let headers = {}; + if (streamInfo.service == "bilibili") { + headers = { "user-agent": genericUserAgent }; + } + const audio = got.get(streamInfo.urls[1], { isStream: true, headers: headers }); + const video = got.get(streamInfo.urls[0], { isStream: true, headers: headers }); + let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ + '-loglevel', '-8', + '-i', 'pipe:3', + '-i', 'pipe:4', + '-map', '0:a', + '-map', '1:v', + '-c:v', 'copy', + '-c:a', 'copy', + ]; + if (format == 'mp4') { + args.push('-movflags', 'frag_keyframe+empty_moov'); + if (streamInfo.service == "youtube") { + args.push('-t', msToTime(streamInfo.time)); + } + } else if (format == 'webm') { + args.push('-t', msToTime(streamInfo.time)); + } + args.push('-f', format, 'pipe:5'); + const ffmpegProcess = spawn(ffmpeg, args, { + windowsHide: true, + stdio: [ + 'inherit', 'inherit', 'inherit', + 'pipe', 'pipe', 'pipe' + ], + }); + ffmpegProcess.on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + audio.on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + video.on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`); + ffmpegProcess.stdio[5].pipe(res); + audio.pipe(ffmpegProcess.stdio[3]).on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + video.pipe(ffmpegProcess.stdio[4]).on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + } else { + res.status(400).json({ status: "error", text: loc('en', 'apiError', 'corruptedVideo') }); + } + } catch (e) { + internalError(res); + } +} +export async function streamAudioOnly(streamInfo, res) { + try { + let headers = {}; + if (streamInfo.service == "bilibili") { + headers = { "user-agent": genericUserAgent }; + } + const audio = got.get(streamInfo.urls[0], { isStream: true, headers: headers }); + const ffmpegProcess = spawn(ffmpeg, [ + '-loglevel', '-8', + '-i', 'pipe:3', + '-vn', + '-c:a', 'copy', + '-f', `${streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1]}`, + 'pipe:4', + ], { + windowsHide: true, + stdio: [ + 'inherit', 'inherit', 'inherit', + 'pipe', 'pipe' + ], + }); + ffmpegProcess.on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + audio.on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`); + ffmpegProcess.stdio[4].pipe(res); + audio.pipe(ffmpegProcess.stdio[3]).on('error', (err) => { + ffmpegProcess.kill(); + internalError(res); + }); + } catch (e) { + internalError(res); + } +} \ No newline at end of file diff --git a/modules/sub/api-helper.js b/modules/sub/api-helper.js new file mode 100644 index 0000000..86c1ab9 --- /dev/null +++ b/modules/sub/api-helper.js @@ -0,0 +1,51 @@ +import { createStream } from "../stream/manage.js"; + +export function apiJSON(type, obj) { + try { + switch (type) { + case 0: + return { status: 400, body: { status: "error", text: obj.t } }; + case 1: + return { status: 200, body: { status: "redirect", url: obj.u } }; + case 2: + return { status: 200, body: { status: "stream", url: createStream(obj) } }; + case 3: + return { status: 200, body: { status: "success", text: obj.t } }; + case 4: + return { status: 429, body: { status: "rate-limit", text: obj.t } }; + default: + return { status: 400, body: { status: "error", text: "Bad Request" } }; + } + } catch (e) { + return { status: 500, body: { status: "error", text: "Internal Server Error" } }; + } +} +export function msToTime(d) { + let milliseconds = parseInt((d % 1000) / 100), + seconds = parseInt((d / 1000) % 60), + minutes = parseInt((d / (1000 * 60)) % 60), + hours = parseInt((d / (1000 * 60 * 60)) % 24), + r; + + hours = (hours < 10) ? "0" + hours : hours; + minutes = (minutes < 10) ? "0" + minutes : minutes; + seconds = (seconds < 10) ? "0" + seconds : seconds; + r = hours + ":" + minutes + ":" + seconds; + milliseconds ? r += "." + milliseconds : r += ""; + return r; +} +export function cleanURL(url, host) { + url = url.replace('}', '').replace('{', '').replace(')', '').replace('(', '').replace(' ', ''); + if (url.includes('youtube.com/shorts/')) { + url = url.split('?')[0].replace('shorts/', 'watch?v='); + } + if (host == "youtube") { + url = url.split('&')[0]; + } else { + url = url.split('?')[0]; + if (url.substring(url.length - 1) == "/") { + url = url.substring(0, url.length - 1); + } + } + return url +} \ No newline at end of file diff --git a/modules/sub/console-text.js b/modules/sub/console-text.js new file mode 100644 index 0000000..3a4001c --- /dev/null +++ b/modules/sub/console-text.js @@ -0,0 +1,49 @@ +export function t(color, tt) { + return color + tt + "\x1b[0m" +} +export function Reset(tt) { + return "\x1b[0m" + tt +} +export function Bright(tt) { + return t("\x1b[1m", tt) +} +export function Dim(tt) { + return t("\x1b[2m", tt) +} +export function Underscore(tt) { + return t("\x1b[4m", tt) +} +export function Blink(tt) { + return t("\x1b[5m", tt) +} +export function Reverse(tt) { + return t("\x1b[7m", tt) +} +export function Hidden(tt) { + return t("\x1b[8m", tt) +} + +export function Black(tt) { + return t("\x1b[30m", tt) +} +export function Red(tt) { + return t("\x1b[31m", tt) +} +export function Green(tt) { + return t("\x1b[32m", tt) +} +export function Yellow(tt) { + return t("\x1b[33m", tt) +} +export function Blue(tt) { + return t("\x1b[34m", tt) +} +export function Magenta(tt) { + return t("\x1b[35m", tt) +} +export function Cyan(tt) { + return t("\x1b[36m", tt) +} +export function White(tt) { + return t("\x1b[37m", tt) +} diff --git a/modules/sub/crypto.js b/modules/sub/crypto.js new file mode 100644 index 0000000..4946bf7 --- /dev/null +++ b/modules/sub/crypto.js @@ -0,0 +1,11 @@ +import { createHmac, createHash, randomUUID } from "crypto"; + +export function encrypt(str, salt) { + return createHmac("sha256", salt).update(str).digest("hex"); +} +export function md5(string) { + return createHash('md5').update(string).digest('hex'); +} +export function UUID() { + return randomUUID(); +} \ No newline at end of file diff --git a/modules/sub/current-commit.js b/modules/sub/current-commit.js new file mode 100644 index 0000000..b9c37cf --- /dev/null +++ b/modules/sub/current-commit.js @@ -0,0 +1,5 @@ +import { execSync } from "child_process"; + +export default function() { + return execSync('git rev-parse --short HEAD').toString().trim() +} \ No newline at end of file diff --git a/modules/sub/errors.js b/modules/sub/errors.js new file mode 100644 index 0000000..a56cb0e --- /dev/null +++ b/modules/sub/errors.js @@ -0,0 +1,11 @@ +import loc from "./loc.js"; + +export function internalError(res) { + res.status(501).json({ status: "error", text: "Internal Server Error" }); +} +export function errorUnsupported(lang) { + return loc(lang, 'apiError', 'notSupported') + loc(lang, 'apiError', 'letMeKnow'); +} +export function genericError(lang, host) { + return loc(lang, 'apiError', 'brokenLink', host) + loc(lang, 'apiError', 'letMeKnow'); +} \ No newline at end of file diff --git a/modules/sub/load-json.js b/modules/sub/load-json.js new file mode 100644 index 0000000..c431fc5 --- /dev/null +++ b/modules/sub/load-json.js @@ -0,0 +1,9 @@ +import * as fs from "fs"; + +export default function(path) { + try { + return JSON.parse(fs.readFileSync(path, 'utf-8')) + } catch(e) { + return false + } +} \ No newline at end of file diff --git a/modules/sub/loc.js b/modules/sub/loc.js new file mode 100644 index 0000000..a178746 --- /dev/null +++ b/modules/sub/loc.js @@ -0,0 +1,22 @@ +import { supportedLanguages, appName } from "../config.js"; +import loadJson from "./load-json.js"; + +export default function(lang, cat, string, replacement) { + if (!lang in supportedLanguages) { + lang = 'en' + } + try { + let str = loadJson(`./strings/${lang}/${cat}.json`); + if (str && str[string]) { + let s = str[string].replace(/\n/g, '
').replace(/{appName}/g, appName) + if (replacement) { + s = s.replace(/{s}/g, replacement) + } + return s + ' ' + } else { + return string + } + } catch (e) { + return string + } +} \ No newline at end of file diff --git a/modules/sub/match.js b/modules/sub/match.js new file mode 100644 index 0000000..c1b747e --- /dev/null +++ b/modules/sub/match.js @@ -0,0 +1,90 @@ +import { apiJSON } from "./api-helper.js"; +import { errorUnsupported, genericError } from "./errors.js"; + +import bilibili from "../services/bilibili.js"; +import reddit from "../services/reddit.js"; +import twitter from "../services/twitter.js"; +import youtube from "../services/youtube.js"; +import vk from "../services/vk.js"; + +export default async function (host, patternMatch, url, ip, lang, format, quality) { + try { + switch (host) { + case "twitter": + if (patternMatch["id"] && patternMatch["id"].length < 20) { + let r = await twitter({ + id: patternMatch["id"], + lang: lang + }); + return (!r.error) ? apiJSON(1, { u: r.split('?')[0] }) : apiJSON(0, { t: r.error }) + } else throw Error() + case "vk": + if (patternMatch["userId"] && patternMatch["videoId"] && patternMatch["userId"].length <= 10 && patternMatch["videoId"].length == 9) { + let r = await vk({ + userId: patternMatch["userId"], + videoId: patternMatch["videoId"], + lang: lang, quality: quality + }); + return (!r.error) ? apiJSON(2, { type: "bridge", urls: r.url, filename: r.filename, service: host, ip: ip, salt: process.env.streamSalt }) : apiJSON(0, { t: r.error }); + } else throw Error() + case "bilibili": + if (patternMatch["id"] && patternMatch["id"].length >= 12) { + let r = await bilibili({ + id: patternMatch["id"].slice(0, 12), + lang: lang + }); + return (!r.error) ? apiJSON(2, { + type: "render", urls: r.urls, + service: host, ip: ip, + filename: r.filename, + salt: process.env.streamSalt, time: r.time + }) : apiJSON(0, { t: r.error }); + } else throw Error() + case "youtube": + if (patternMatch["id"]) { + let fetchInfo = { + id: patternMatch["id"].slice(0, 11), + lang: lang, quality: quality, + format: "mp4" + }; + switch (format) { + case "webm": + fetchInfo["format"] = "webm"; + break; + case "audio": + fetchInfo["format"] = "webm"; + fetchInfo["isAudioOnly"] = true; + fetchInfo["quality"] = "max"; + break; + } + if (url.match('music.youtube.com')) { + fetchInfo["isAudioOnly"] = true; + } + let r = await youtube(fetchInfo); + return (!r.error) ? apiJSON(2, { + type: r.type, urls: r.urls, service: host, ip: ip, + filename: r.filename, salt: process.env.streamSalt, + isAudioOnly: fetchInfo["isAudioOnly"] ? fetchInfo["isAudioOnly"] : false, + time: r.time, + }) : apiJSON(0, { t: r.error }); + } else throw Error() + case "reddit": + if (patternMatch["sub"] && patternMatch["id"] && patternMatch["title"] && patternMatch["sub"].length <= 22 && patternMatch["id"].length <= 10 && patternMatch["title"].length <= 96) { + let r = await reddit({ + sub: patternMatch["sub"], + id: patternMatch["id"], + title: patternMatch["title"] + }); + return (!r.error) ? apiJSON(2, { + type: "render", urls: r.urls, + service: host, ip: ip, + filename: r.filename, salt: process.env.streamSalt + }) : apiJSON(0, { t: r.error }); + } else throw Error() + default: + return apiJSON(0, { t: errorUnsupported(lang) }) + } + } catch (e) { + return apiJSON(0, { t: genericError(lang, host) }) + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..53e7fd1 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "cobalt", + "description": "easy to use social media downloader", + "version": "2.1", + "author": "wukko", + "exports": "./cobalt.js", + "type": "module", + "engines": { + "node": ">=14.16" + }, + "scripts": { + "start": "node cobalt", + "setup": "node modules/setup.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wukko/cobalt-web.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/wukko/cobalt-web/issues" + }, + "homepage": "https://github.com/wukko/cobalt-web#readme", + "dependencies": { + "dotenv": "^16.0.1", + "express": "^4.17.1", + "express-rate-limit": "^6.3.0", + "ffmpeg-static": "^5.0.0", + "got": "^12.1.0", + "node-cache": "^5.1.2", + "url-pattern": "^1.0.3", + "xml-js": "^1.6.11", + "ytdl-core": "4.11.0" + } +} diff --git a/strings/en/accessibility.json b/strings/en/accessibility.json new file mode 100644 index 0000000..3f2714e --- /dev/null +++ b/strings/en/accessibility.json @@ -0,0 +1,10 @@ +{ + "about": "About", + "settings": "Settings", + "input": "URL input area", + "download": "Download button", + "changelog": "Changelog", + "close": "Close button", + "alwaysVisibleButton": "Keep the download button always visible", + "donate": "Support {appName} by donating" +} \ No newline at end of file diff --git a/strings/en/apiError.json b/strings/en/apiError.json new file mode 100644 index 0000000..788bee0 --- /dev/null +++ b/strings/en/apiError.json @@ -0,0 +1,22 @@ +{ + "generic": "something went wrong and i couldn't get the video. you can try again,", + "notSupported": "it seems like this service is not supported yet or your link is invalid.", + "brokenLink": "{s} is supported, but something is wrong with your link.", + "noURL": "something is wrong with your link. it's probably missing. i can't guess what you want to download!", + "badResolution": "your screen resolution is not supported. try a different device.", + "tryAgain": "\ncheck the link and try again.", + "letMeKnow": "but if issue persists, please let me know.", + "fatal": "something went wrong and page couldn't be rendered. if you want me to fix this, please contact me. it'd be useful if you provided the commit hash ({s}) along with recreation steps. thank you :D", + "rateLimit": "you're making way too many requests. calm down and try again in a few minutes.", + "youtubeFetch": "couldn't fetch metadata. check if your link is correct and try again.", + "youtubeLimit": "current length limit is {s} minutes. what you tried to download was longer than {s} minutes.", + "youtubeBroke": "something went wrong with info fetching. you can try a different format or just try again later.", + "corruptedVideo": "oddly enough the requested video is corrupted on its origin server. youtube does this sometimes because it's fucking stupid.", + "corruptedAudio": "oddly enough the requested audio is corrupted on its origin server. youtube does this sometimes because it's fucking stupid.", + "noInternet": "it seems like either {appName} api is down or there's no internet. check your connection and try again.", + "liveVideo": "i can't download a live video. wait for stream to finish and try again.", + "nothingToDownload": "it seems like there's nothing to download. try another link!", + "cantConnectToAPI": "i couldn't connect to {s} api. seems like either {s} is down or server ip got blocked. try again later.", + "noStreamID": "there's no such stream id. you can't fool me!", + "noType": "there's no such expected response type." +} \ No newline at end of file diff --git a/strings/en/changelog.json b/strings/en/changelog.json new file mode 100644 index 0000000..6b8c35c --- /dev/null +++ b/strings/en/changelog.json @@ -0,0 +1,5 @@ +{ + "subtitle": "everything is new! (v.2.1)", + "text": "- added support for: bilibili.com, youtube, youtube music, reddit, vk;\n- remade the way downloads are handled;\n- added proper website branding;\n- added settings, donations, and changelog menu;\n- added manual theme picker;\n- added format picker for youtube;\n- added quality picker for youtube and vk downloads (bilibili and twitter later);\n- improved usability;\n- upgraded the download button to be adaptive depending on current status;\n- popups are now adaptive, too;\n- better scalability;\n- took out trash;\n- moved from commonjs to ems;\n- overall revamp of backend and frontend;\n- fixed various issues that were present in older version.", + "github": ">> see previous changes and contribute on github" +} \ No newline at end of file diff --git a/strings/en/desc.json b/strings/en/desc.json new file mode 100644 index 0000000..d2d1d19 --- /dev/null +++ b/strings/en/desc.json @@ -0,0 +1,15 @@ +{ + "input": "paste the link here", + "about": "{appName} is your go-to place for social media downloads. zero ads or other creepy bullshit attached. simply paste a share link and you're ready to rock!", + "embed": "save content from social media without creeps following you around", + "support_1": "currently supported services:", + "sourcecode": ">> report issues and check out the source code on github", + "popupBottom": "made with <3 by wukko", + "noScript": "{appName} uses javascript for api requests and interactive interface. you have to allow javascript to use this site. we don't have ads or any trackers, pinky promise.", + "ios": "it's impossible to download videos directly from browser on ios. you'll have to open share sheet and select \"save to files\" in order to save videos from twitter. if you don't see that option, you have to install the official \"files\" app from app store first.", + "iosTitle": "notice for ios users", + "donationsSub": "it's quite complicated to pay for hosting right now", + "donations": "you can currently donate only in crypto, because i live under dictatorship and can't use debit cards anywhere outside of country. i don't like crypto the way it is right now, but it's the only way for me to pay for anything (including hosting) online. everything else like paypal is no longer available.", + "donateDm": ">> let me know if currency you want to donate isn't listed", + "clicktocopy": "click to copy" +} \ No newline at end of file diff --git a/strings/en/settings.json b/strings/en/settings.json new file mode 100644 index 0000000..2cbbc9a --- /dev/null +++ b/strings/en/settings.json @@ -0,0 +1,20 @@ +{ + "category-appearance": "appearance", + "always-visible": "keep >> visible", + "picker": "always download max quality", + "format": "download format", + "format-info": "webm videos are usually higher quality but not all devices can play them. select webm only if you need max quality available. audio only downloads will always be max quality.", + "theme": "theme", + "theme-auto": "auto", + "theme-light": "light", + "theme-dark": "dark", + "misc": "miscellaneous", + "general": "downloads", + "quality": "quality", + "q-max": "max", + "q-hig": "high\n", + "q-mid": "medium\n", + "q-low": "low\n", + "q-los": "lowest", + "q-desc": "all resolutions listed here are max values. if there's no video of preferred quality closest one gets picked instead." +} \ No newline at end of file diff --git a/strings/en/title.json b/strings/en/title.json new file mode 100644 index 0000000..228b055 --- /dev/null +++ b/strings/en/title.json @@ -0,0 +1,7 @@ +{ + "about": "owo what's this?", + "settings": "settings", + "error": "uh-oh...", + "changelog": "what's new?", + "donate": "support {appName}" +} \ No newline at end of file