Merge branch 'current' into instagram-stories

This commit is contained in:
dumbmoron 2023-09-16 23:34:23 +02:00 committed by GitHub
commit f7e0871a8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 1045 additions and 269 deletions

55
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Build Docker image
on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get version from package.json
id: package-version
uses: martinbeentjes/npm-get-version-action@v1.3.1
- name: Get short commit hash
id: commit-hash
run: echo "commit_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
tags: |
type=raw,value=latest
type=raw,value=${{ steps.package-version.outputs.current-version }}
type=raw,value=${{ steps.package-version.outputs.current-version }}-${{ steps.commit-hash.outputs.commit_short }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -1,6 +1,6 @@
# cobalt
Best way to save what you love.
Live web app: [co.wukko.me](https://co.wukko.me/)
Live web app: [cobalt.tools](https://cobalt.tools/)
![cobalt logo with repeated logo pattern background](https://raw.githubusercontent.com/wukko/cobalt/current/src/front/icons/pattern.png "cobalt logo with repeated logo pattern background")
@ -20,10 +20,12 @@ Paste the link, get the video, move on. It's that simple. Just how it should be.
| Instagram Reels | ✅ | ✅ | ✅ | |
| Pinterest | ✅ | ✅ | ✅ | Support for videos and stories. |
| Reddit | ✅ | ✅ | ✅ | Support for GIFs and videos. |
| Rutube | ✅ | ✅ | ✅ | |
| SoundCloud | | ✅ | | Audio metadata, downloads from private links. |
| Streamable | ✅ | ✅ | ✅ | |
| TikTok | ✅ | ✅ | ✅ | Supports downloads of: videos with or without watermark, images from slideshow without watermark, full (original) audios. |
| Tumblr | ✅ | ✅ | ✅ | Support for audio file downloads. |
| Twitch Clips | ✅ | ✅ | ✅ | |
| Twitter/X * | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media tweets. |
| Vimeo | ✅ | ✅ | ✅ | Audio downloads are only available for dash files. |
| Vine Archive | ✅ | ✅ | ✅ | |

View file

@ -2,10 +2,12 @@ version: '3.5'
services:
cobalt-api:
build: .
image: ghcr.io/wukko/cobalt:latest
restart: unless-stopped
container_name: cobalt-api
init: true
# if container doesn't run detached on your machine, uncomment the next line:
#tty: true
@ -21,14 +23,22 @@ services:
# replace apiName with your instance's distinctive name
- apiName=eu-nl
# if you want to use cookies when fetching data from services, uncomment the next line
#- cookiePath=cookies.json
#- cookiePath=/cookies.json
# see src/modules/processing/cookie/cookies_example.json for example file.
labels:
- com.centurylinklabs.watchtower.scope=cobalt
# if you want to use cookies when fetching data from services, uncomment volumes and next line
#volumes:
#- ./cookies.json:/cookies.json
cobalt-web:
build: .
image: ghcr.io/wukko/cobalt:latest
restart: unless-stopped
container_name: cobalt-web
init: true
# if container doesn't run detached on your machine, uncomment the next line:
#tty: true
@ -40,6 +50,17 @@ services:
environment:
- webPort=9001
# replace webURL with your instance's target url in same format
- webURL=https://co.wukko.me/
- webURL=https://cobalt.tools/
# replace apiURL with preferred api instance url
- apiURL=https://co.wuk.sh/
labels:
- com.centurylinklabs.watchtower.scope=cobalt
# update the cobalt image automatically with watchtower
watchtower:
image: ghcr.io/containrrr/watchtower
restart: unless-stopped
command: --cleanup --scope cobalt --interval 900
volumes:
- /var/run/docker.sock:/var/run/docker.sock

View file

@ -4,8 +4,7 @@ This document provides info about methods and acceptable variables for all cobal
```
⚠️ Main API instance has moved to https://co.wuk.sh/
Previous API domain will stop redirecting users to correct API instance after July 25th.
Make sure to update your projects in time.
Make sure your projects use the correct API domain.
```
## POST: ``/api/json``
@ -15,17 +14,18 @@ Request Body Type: ``application/json``<br>
Response Body Type: ``application/json``
### Request Body Variables
| key | type | variables | default | description |
|:----------------|:--------|:----------------------------------|:----------|:-------------------------------------------------------------------------------|
| url | string | Sharable URL encoded as URI | ``null`` | **Must** be included in every request. |
| vCodec | string | ``h264 / av1 / vp9`` | ``h264`` | Applies only to YouTube downloads. ``h264`` is recommended for phones. |
| vQuality | string | ``144 / ... / 2160 / max`` | ``720`` | ``720`` quality is recommended for phones. |
| aFormat | string | ``best / mp3 / ogg / wav / opus`` | ``mp3`` | |
| isAudioOnly | boolean | ``true / false`` | ``false`` | |
| isNoTTWatermark | boolean | ``true / false`` | ``false`` | Changes whether downloaded TikTok & Douyin videos have watermarks. |
| isTTFullAudio | boolean | ``true / false`` | ``false`` | Enables download of original sound used in a TikTok video. |
| isAudioMuted | boolean | ``true / false`` | ``false`` | Disables audio track in video downloads. |
| dubLang | boolean | ``true / false`` | ``false`` | Backend uses Accept-Language for YouTube video audio tracks when ``true``. |
| key | type | variables | default | description |
|:--------------------|:--------|:----------------------------------|:----------|:-------------------------------------------------------------------------------|
| ``url`` | string | Sharable URL encoded as URI | ``null`` | **Must** be included in every request. |
| ``vCodec`` | string | ``h264 / av1 / vp9`` | ``h264`` | Applies only to YouTube downloads. ``h264`` is recommended for phones. |
| ``vQuality`` | string | ``144 / ... / 2160 / max`` | ``720`` | ``720`` quality is recommended for phones. |
| ``aFormat`` | string | ``best / mp3 / ogg / wav / opus`` | ``mp3`` | |
| ``isAudioOnly`` | boolean | ``true / false`` | ``false`` | |
| ``isNoTTWatermark`` | boolean | ``true / false`` | ``false`` | Changes whether downloaded TikTok videos have watermarks. |
| ``isTTFullAudio`` | boolean | ``true / false`` | ``false`` | Enables download of original sound used in a TikTok video. |
| ``isAudioMuted`` | boolean | ``true / false`` | ``false`` | Disables audio track in video downloads. |
| ``dubLang`` | boolean | ``true / false`` | ``false`` | Backend uses Accept-Language for YouTube video audio tracks when ``true``. |
| ``disableMetadata`` | boolean | ``true / false`` | ``false`` | Disables file metadata when set to ``true``. |
### Response Body Variables
| key | type | variables |

View file

@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
"version": "7.1.3",
"version": "7.5",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
@ -30,6 +30,7 @@
"express": "^4.18.1",
"express-rate-limit": "^6.3.0",
"ffmpeg-static": "^5.1.0",
"hls-parser": "^0.10.7",
"nanoid": "^4.0.2",
"node-cache": "^5.1.2",
"set-cookie-parser": "2.6.0",

View file

@ -9,9 +9,6 @@ import { loadLoc } from "./localization/manager.js";
import path from 'path';
import { fileURLToPath } from 'url';
import { runWeb } from "./core/web.js";
import { runAPI } from "./core/api.js";
const app = express();
const gitCommit = shortCommit();
@ -28,8 +25,10 @@ const apiMode = process.env.apiURL && process.env.apiPort && !((process.env.webU
const webMode = process.env.webURL && process.env.webPort && !((process.env.apiURL && process.env.apiPort) || (process.env.selfURL && process.env.port));
if (apiMode) {
const { runAPI } = await import('./core/api.js');
runAPI(express, app, gitCommit, gitBranch, __dirname)
} else if (webMode) {
const { runWeb } = await import('./core/web.js');
await runWeb(express, app, gitCommit, gitBranch, __dirname)
} else {
console.log(Red(`cobalt wasn't configured yet or configuration is invalid.\n`) + Bright(`please run the setup script to fix this: `) + Green(`npm run setup`))

View file

@ -1,23 +1,28 @@
{
"streamLifespan": 20000,
"maxVideoDuration": 10800000,
"maxVideoDuration": 18000000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"authorInfo": {
"name": "wukko",
"link": "https://wukko.me/",
"contact": "https://wukko.me/contacts",
"support": {
"twitter": {
"url": "https://twitter.com/justusecobalt",
"handle": "@justusecobalt"
},
"mastodon": {
"url": "https://wetdry.world/@cobalt",
"handle": "@cobalt@wetdry.world"
},
"discord": {
"url": "https://discord.gg/pQPt8HBUPu",
"handle": "cobalt community server"
"default": {
"twitter": {
"emoji": "🐦",
"url": "https://twitter.com/justusecobalt",
"handle": "@justusecobalt"
},
"mastodon": {
"emoji": "🐘",
"url": "https://wetdry.world/@cobalt",
"handle": "@cobalt@wetdry.world"
},
"discord": {
"emoji": "👾",
"url": "https://discord.gg/pQPt8HBUPu",
"handle": "cobalt community server"
}
}
}
},
@ -40,6 +45,7 @@
"02-17": "😺",
"02-22": "😺",
"03-01": "😺",
"03-08": "💪",
"05-26": "🎂",
"08-08": "😺",
"08-26": "🐶",
@ -59,8 +65,7 @@
"12-28": "🎄",
"12-29": "🎄",
"12-30": "🎄",
"12-31": "🎄",
"03-08": "💪"
"12-31": "🎄"
},
"supportedAudio": ["mp3", "ogg", "wav", "opus"],
"ffmpegArgs": {

View file

@ -175,6 +175,24 @@ input[type="text"],
backdrop-filter: blur(7px);
-webkit-backdrop-filter: blur(7px);
}
.glass-bkg.alone {
z-index: -1;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
}
.glass-bkg.small {
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: -1;
position: absolute;
border: var(--accent-highlight) solid 0.15rem;
border-radius: 8px/9px;
}
.desktop button:hover,
.desktop .switch:hover,
.desktop .checkbox:hover,
@ -198,7 +216,7 @@ button:active,
.popup.small .switch {
background: var(--accent-button-elevated);
}
.popup.small .switch:hover {
.desktop .popup.small .switch:hover {
background: var(--accent-hover-elevated);
}
.switch.text-backdrop,
@ -267,7 +285,6 @@ button:active,
}
.box {
background: var(--background);
border: var(--glass) solid .2rem;
color: var(--accent);
}
#url-input-area {
@ -375,7 +392,8 @@ button:active,
max-height: 95%;
opacity: 0;
transform: translate(-50%,-48%)scale(.95);
box-shadow: 0 0 20px 0 var(--accent-hover-transparent);
box-shadow: 0 0 0 0.2rem var(--glass) inset,
0 0 20px 0 var(--accent-hover-transparent);
}
.popup.visible {
visibility: visible;
@ -404,7 +422,6 @@ button:active,
.popup.small {
width: 20%;
box-shadow: 0px 0px 60px 0px var(--accent-hover);
border: var(--accent-highlight) solid 0.15rem;
padding: 1.7rem;
transform: translate(-50%,-50%)scale(.95);
pointer-events: all;
@ -462,6 +479,7 @@ button:active,
align-items: center;
gap: 0.7rem;
padding-bottom: 0.7rem;
flex-wrap: wrap;
}
.changelog-tag-version {
font-size: 1rem;
@ -478,14 +496,14 @@ button:active,
padding-top: 0!important;
}
.desc-padding {
padding-bottom: 1.5rem;
padding-bottom: 0.7rem;
}
#popup-subtitle {
font-size: 1.1rem;
padding-bottom: var(--padding-1);
}
#popup-desc,
#desc-error,
.desc-error,
#popup-info-desc {
width: 100%;
text-align: left;
@ -494,6 +512,9 @@ button:active,
user-select: text;
-webkit-user-select: text;
}
.desc-error {
padding-bottom: 1.5rem;
}
#popup-title {
font-size: 1.5rem;
line-height: 1.2em;
@ -515,12 +536,12 @@ button:active,
.popup-content-inner,
.tab-content-settings,
#picker-holder {
padding-top: calc(env(safe-area-inset-top)/2 + 4.9rem);
padding-top: calc(env(safe-area-inset-top)/2 + 4.7rem);
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 4.8rem);
}
.tab-content-settings,
#tab-about-about .popup-content-inner {
padding-top: calc(env(safe-area-inset-top)/2 + 6.2rem);;
padding-top: calc(env(safe-area-inset-top)/2 + 6rem);;
}
.bullpadding {
padding-left: 0.58rem;
@ -530,10 +551,9 @@ button:active,
z-index: 999;
padding-top: calc(env(safe-area-inset-top)/2 + 1.7rem);
width: 100%;
border-bottom: var(--accent-highlight) solid 0.1rem;
}
.settings-category {
padding-bottom: 1rem;
padding-bottom: 0.7rem;
}
.separator {
float: left;
@ -584,6 +604,10 @@ button:active,
line-height: 1.3rem!important;
color: var(--accent-subtext);
}
.explanation.embedded {
margin-top: 0.825rem;
margin-bottom: 0.825rem;
}
.subtext {
color: var(--accent-subtext);
}
@ -629,7 +653,6 @@ button:active,
width: auto;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: scroll;
scrollbar-width: none;
}
.switches .switch {
@ -672,7 +695,6 @@ button:active,
width: 100%;
padding-top: 0.2rem;
padding-bottom: 1.7rem;
border-top: var(--accent-highlight) solid 0.1rem;
}
.popup-tabs-child {
width: 100%;
@ -797,12 +819,16 @@ button:active,
width: 100%;
text-align: center;
position: absolute;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
padding-top: calc(env(safe-area-inset-top) + 1rem);
}
.urgent-text {
display: flex;
align-items: center;
cursor: pointer;
}
.no-transparency .glass-bkg,
.no-transparency #popup-backdrop {
backdrop-filter: none;
@ -815,23 +841,6 @@ button:active,
.no-animation #popup-backdrop {
transition: none;
}
#floating-notification-area {
visibility: visible;
z-index: 999999;
position: absolute;
display: flex;
justify-content: center;
width: 100%;
padding-top: 2rem;
}
.floating-notification {
text-align: center;
padding: 0.6rem 1.2rem;
background: var(--accent-hover-elevated);
display: flex;
box-shadow: 0 0 20px 10px var(--accent-hover);
font-size: 0.85rem;
}
.popup-from-bottom {
position: fixed;
width: 100%;
@ -903,13 +912,17 @@ button:active,
.scrollable #popup-content {
border-radius: 8px / 9px;
}
#popup-header {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
#popup-header .glass-bkg {
border-top-left-radius: 8px 9px;
border-top-right-radius: 8px 9px;
border-bottom: var(--accent-highlight) solid 0.1rem;
top: -1px;
}
#popup-tabs {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
#popup-tabs .glass-bkg {
border-bottom-left-radius: 8px 9px;
border-bottom-right-radius: 8px 9px;
border-top: var(--accent-highlight) solid 0.1rem;
bottom: -1px;
}
.switches .first {
border-top-left-radius: 5px 6px;
@ -1005,87 +1018,6 @@ button:active,
width: calc(100% - 1.3rem);
}
}
@media screen and (max-width: 320px) {
:root {
--gap: 0.38rem;
--gap-no-icon: 0.38rem;
--line-height: 1.2rem;
}
#popup-title {
font-size: 1.07rem;
line-height: 1.5rem;
}
.checkbox {
width: calc(100% - 1rem);
}
.footer-button,
#audioMode-false,
#audioMode-true,
#paste {
font-size: 0!important;
}
.footer-button .emoji,
#audioMode-false .emoji,
#audioMode-true .emoji,
#paste .emoji {
margin-right: 0;
}
.switch,
.checkbox,
.category-title,
.subtitle,
#popup-desc,
.collapse-title {
font-size: 0.7rem;
}
.collapse-header {
padding: 0.5rem;
}
#popup-above-title,
#url-input-area {
font-size: 0.6rem;
}
.explanation {
font-size: 0.6rem;
margin-top: 0.5rem;
line-height: 1rem!important;
}
#popup-desc {
line-height: 1.2rem;
font-size: 0.64rem;
}
.changelog-subtitle, #popup-subtitle {
font-size: 0.8rem!important;
}
.category-title {
margin-bottom: 0.8rem;
}
.emoji {
height: 18px;
width: 18px;
}
.desc-padding {
padding-bottom: 0.8rem;
}
#logo {
font-size: 0.8rem;
}
.popup,
.popup.scrollable,
.popup.small {
height: 98%;
}
[type=checkbox] {
width: 15px;
height: 15px;
border: 0.12rem solid var(--accent);
}
[type=checkbox]:before {
transform: scaleY(.8)scaleX(.7)rotate(45deg);
left: 3.4px;
top: -2px;
}
}
@media screen and (max-width: 720px) {
#cobalt-main-box {
width: calc(100% - (0.7rem * 2));
@ -1124,10 +1056,20 @@ button:active,
padding-top: calc(env(safe-area-inset-bottom)/2 + 1rem);
}
.popup,
#popup-header,
#popup-tabs {
#popup-header .glass-bkg,
#popup-tabs .glass-bkg,
.glass-bkg.small {
border-radius: 0;
}
#popup-tabs .glass-bkg {
bottom: 0;
}
.switches {
overflow-x: scroll;
}
.checkbox {
margin-right: 0;
}
.popup.center {
top: unset;
left: unset;
@ -1141,11 +1083,13 @@ button:active,
left: 0;
transform: none;
position: absolute;
border: none;
border-top: var(--accent-highlight) solid 0.15rem;
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 1.7rem);
transform: translateY(30rem);
}
.glass-bkg.small {
border: none;
border-top: var(--accent-highlight) solid 0.15rem;
}
.popup.small.visible {
transform: none;
transition: transform 200ms cubic-bezier(0.075, 0.82, 0.165, 1), opacity 130ms ease-in-out;
@ -1173,6 +1117,7 @@ button:active,
width: 100%;
height: 100%;
max-height: 100%;
box-shadow: none;
}
#popup-tabs {
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 1.5rem);

View file

@ -1,3 +1,5 @@
const version = 37;
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
const isMobile = ua.match("android") || ua.match("iphone os");
@ -5,7 +7,6 @@ const isSafari = ua.match("safari/");
const isFirefox = ua.match("firefox/");
const isOldFirefox = ua.match("firefox/") && ua.split("firefox/")[1].split('.')[0] < 103;
const version = 34;
const regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/);
const notification = `<div class="notification-dot"></div>`;
@ -18,12 +19,24 @@ const switchers = {
"vimeoDash": ["false", "true"],
"audioMode": ["false", "true"]
};
const checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio", "reduceTransparency", "disableAnimations"];
const checkboxes = [
"alwaysVisibleButton",
"disableChangelog",
"downloadPopup",
"disableTikTokWatermark",
"fullTikTokAudio",
"muteAudio",
"reduceTransparency",
"disableAnimations",
"disableMetadata",
];
const exceptions = { // used for mobile devices
"vQuality": "720"
};
const bottomPopups = ["error", "download"]
const pageQuery = new URLSearchParams(window.location.search);
let store = {};
function changeAPI(url) {
@ -126,6 +139,7 @@ function changeTab(evnt, tabId, tabClass) {
evnt.currentTarget.dataset.enabled = "true";
eid(tabId).dataset.enabled = "true";
eid(tabId).parentElement.scrollTop = 0;
if (tabId === "tab-about-changelog" && sGet("changelogStatus") !== `${version}`) notificationCheck("changelog");
if (tabId === "tab-about-about" && !sGet("seenAbout")) notificationCheck("about");
@ -199,8 +213,8 @@ function popup(type, action, text) {
case "picker":
switch (text.type) {
case "images":
eid("picker-title").innerHTML = loc.pickerImages;
eid("picker-subtitle").innerHTML = loc.pickerImagesExpl;
eid("picker-title").innerHTML = loc.ImagePickerTitle;
eid("picker-subtitle").innerHTML = isMobile ? loc.ImagePickerExplanationPhone : loc.ImagePickerExplanationPC;
eid("picker-holder").classList.remove("various");
@ -217,8 +231,8 @@ function popup(type, action, text) {
}
break;
default:
eid("picker-title").innerHTML = loc.pickerDefault;
eid("picker-subtitle").innerHTML = loc.pickerDefaultExpl;
eid("picker-title").innerHTML = loc.MediaPickerTitle;
eid("picker-subtitle").innerHTML = isMobile ? loc.MediaPickerExplanationPhone : loc.MediaPickerExplanationPC;
eid("picker-holder").classList.add("various");
@ -288,10 +302,10 @@ function loadSettings() {
eid("downloadPopup").checked = true;
}
if (sGet("reduceTransparency") === "true" || isOldFirefox) {
eid("cobalt-body").classList.toggle('no-transparency');
eid("cobalt-body").classList.add('no-transparency');
}
if (sGet("disableAnimations") === "true") {
eid("cobalt-body").classList.toggle('no-animation');
eid("cobalt-body").classList.add('no-animation');
}
for (let i = 0; i < checkboxes.length; i++) {
if (sGet(checkboxes[i]) === "true") eid(checkboxes[i]).checked = true;
@ -326,7 +340,7 @@ function internetError() {
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
setTimeout(() => { changeButton(1); }, 2500);
popup("error", 1, loc.noInternet);
popup("error", 1, loc.ErrorNoInternet);
}
function resetSettings() {
localStorage.clear();
@ -340,13 +354,13 @@ async function pasteClipboard() {
download(eid("url-input-area").value);
}
} catch (e) {
let errorMessage = loc.featureErrorGeneric;
let errorMessage = loc.FeatureErrorGeneric;
let doError = true;
let error = String(e).toLowerCase();
if (error.includes("denied")) errorMessage = loc.clipboardErrorNoPermission;
if (error.includes("denied")) errorMessage = loc.ClipboardErrorNoPermission;
if (error.includes("dismissed") || isIOS) doError = false;
if (error.includes("function") && isFirefox) errorMessage = loc.clipboardErrorFirefox;
if (error.includes("function") && isFirefox) errorMessage = loc.ClipboardErrorFirefox;
if (doError) popup("error", 1, errorMessage);
}
@ -377,6 +391,8 @@ async function download(url) {
if ((url.includes("tiktok.com/") || url.includes("douyin.com/")) && sGet("disableTikTokWatermark") === "true") req.isNoTTWatermark = true;
}
if (sGet("disableMetadata") === "true") req.disableMetadata = true;
let j = await fetch(`${apiURL}/api/json`, {
method: "POST",
body: JSON.stringify(req),
@ -391,7 +407,7 @@ async function download(url) {
if (j.text && (!j.url || !j.picker)) {
if (j.status === "success") {
changeButton(2, j.text)
} else changeButton(0, loc.noURLReturned);
} else changeButton(0, loc.ErrorNoUrlReturned);
}
switch (j.status) {
case "redirect":
@ -409,7 +425,7 @@ async function download(url) {
popup('picker', 1, { arr: j.picker, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 2500);
} else {
changeButton(0, loc.noURLReturned);
changeButton(0, loc.ErrorNoUrlReturned);
}
break;
case "stream":
@ -431,7 +447,7 @@ async function download(url) {
changeButton(2, j.text);
break;
default:
changeButton(0, loc.unknownStatus);
changeButton(0, loc.ErrorUnknownStatus);
break;
}
} else if (j && j.text) {
@ -466,7 +482,7 @@ async function loadOnDemand(elementId, blockId) {
}).catch(() => { throw new Error() });
}
if (j.text) {
eid(elementId).innerHTML = `<button class="switch bottom-margin" onclick="restoreUpdateHistory()">${loc.collapseHistory}</button>${j.text}`;
eid(elementId).innerHTML = `<button class="switch bottom-margin" onclick="restoreUpdateHistory()">${loc.ChangelogPressToHide}</button>${j.text}`;
} else throw new Error()
} catch (e) {
eid(elementId).innerHTML = store.historyButton;
@ -476,26 +492,68 @@ async function loadOnDemand(elementId, blockId) {
function restoreUpdateHistory() {
eid("changelog-history").innerHTML = store.historyButton;
}
function unpackSettings(b64) {
let changed = null;
try {
let settingsToImport = JSON.parse(atob(b64));
let currentSettings = JSON.parse(JSON.stringify(localStorage));
for (let s in settingsToImport) {
if (checkboxes.includes(s) && (settingsToImport[s] === "true" || settingsToImport[s] === "false")
&& currentSettings[s] !== settingsToImport[s]) {
sSet(s, settingsToImport[s]);
changed = true
}
if (switchers[s] && switchers[s].includes(settingsToImport[s])
&& currentSettings[s] !== settingsToImport[s]) {
sSet(s, settingsToImport[s]);
changed = true
}
}
} catch (e) {
changed = false;
}
return changed
}
window.onload = () => {
loadCelebrationsEmoji();
loadSettings();
detectColorScheme();
changeDownloadButton(0, '>>');
notificationCheck();
loadCelebrationsEmoji();
eid("url-input-area").value = "";
if (isIOS) {
sSet("downloadPopup", "true");
eid("downloadPopup-chkbx").style.display = "none";
}
eid("url-input-area").value = "";
eid("home").style.visibility = 'visible';
eid("home").classList.toggle("visible");
let urlQuery = new URLSearchParams(window.location.search).get("u");
if (urlQuery !== null && regex.test(urlQuery)) {
eid("url-input-area").value = urlQuery;
button();
if (pageQuery.has("u") && regex.test(pageQuery.get("u"))) {
eid("url-input-area").value = pageQuery.get("u");
button()
}
if (pageQuery.has("migration")) {
if (pageQuery.has("settingsData") && !sGet("migrated")) {
let setUn = unpackSettings(pageQuery.get("settingsData"));
if (setUn !== null) {
if (setUn) {
sSet("migrated", "true")
eid("desc-migration").innerHTML += `<br/><br/>${loc.DataTransferSuccess}`
} else {
eid("desc-migration").innerHTML += `<br/><br/>${loc.DataTransferError}`
}
}
}
loadSettings();
detectColorScheme();
popup("migration", 1);
}
window.history.replaceState(null, '', window.location.pathname);
notificationCheck();
}
eid("url-input-area").addEventListener("keydown", (e) => {
button();

View file

@ -0,0 +1,345 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint0_linear_6893_5267)" />
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint1_linear_6893_5267)" />
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint2_linear_6893_5267)" />
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint3_linear_6893_5267)" />
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint4_linear_6893_5267)" />
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="url(#paint5_radial_6893_5267)" />
<g filter="url(#filter0_f_6893_5267)">
<rect x="5.05408" y="21.8203" width="2.72692" height="3.69801" rx="1.36346" fill="#D67908" />
</g>
<g filter="url(#filter1_f_6893_5267)">
<path d="M20.7508 10.2007C17.7675 10.3082 25.9773 15.1544 25.9773 15.1544L27.1023 6.09195C27.157 5.63101 26.7382 5.13844 25.8523 5.96097C25.8523 5.96097 22.0258 9.72898 21.8387 9.91319C21.6516 10.0974 21.4307 10.1621 21.1371 10.2007C20.9875 10.2203 20.9015 10.1952 20.7508 10.2007Z" fill="url(#paint6_linear_6893_5267)" />
</g>
<path d="M6.13258 13.5034L8.93258 10.9034C9.25258 10.6034 9.25258 10.1234 8.93258 9.83344L5.9179 7.15626C5.53508 6.82032 4.74258 6.9297 4.74258 7.76344V12.9634C4.74258 13.7969 5.62258 13.9834 6.13258 13.5034Z" fill="url(#paint7_linear_6893_5267)" />
<g filter="url(#filter2_f_6893_5267)">
<path d="M26.4909 5.65184C26.5488 5.50816 26.6882 5.41406 26.8431 5.41406V5.41406C27.0529 5.41406 27.2229 5.58411 27.2229 5.79388V8.03906L25.8055 7.35156L26.4909 5.65184Z" fill="url(#paint8_linear_6893_5267)" />
</g>
<g filter="url(#filter3_f_6893_5267)">
<path d="M5.44611 5.72656L8.82107 8.9461C9.57518 9.66548 9.58931 10.8646 8.85236 11.6016V11.6016" stroke="#FFDF70" stroke-linecap="round" />
</g>
<g filter="url(#filter4_f_6893_5267)">
<path d="M14.8054 20.6502L14.8697 20.6352C15.3433 20.5248 15.8366 20.5299 16.3077 20.6502V20.6502C16.8288 20.7484 17.1399 21.3457 16.6999 21.7757L16.1491 22.3773C15.8791 22.6473 15.2863 22.6473 15.0163 22.3773L14.4734 21.7757C14.0241 21.2679 14.3054 20.7601 14.8054 20.6502Z" fill="url(#paint9_linear_6893_5267)" />
</g>
<path d="M15.2265 20.2066L15.2908 20.1916C15.7644 20.0811 16.2577 20.0862 16.7289 20.2066V20.2066C17.25 20.3047 17.561 20.902 17.121 21.332L16.5703 21.9336C16.3003 22.2036 15.7075 22.2036 15.4375 21.9336L14.8945 21.332C14.4453 20.8242 14.7265 20.3164 15.2265 20.2066Z" fill="url(#paint10_radial_6893_5267)" />
<g filter="url(#filter5_f_6893_5267)">
<ellipse cx="16.2703" cy="20.7414" rx="0.761719" ry="0.274258" fill="url(#paint11_linear_6893_5267)" />
</g>
<g filter="url(#filter6_f_6893_5267)">
<path d="M24.7851 22.8923L28.5898 21.0798" stroke="url(#paint12_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter7_f_6893_5267)">
<path d="M24.7851 24.9235L26.7117 26.2891" stroke="url(#paint13_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.5663 22.5391L29.371 20.7266" stroke="url(#paint14_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter8_f_6893_5267)">
<path d="M25.5663 22.7109L29.371 20.8984" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter9_f_6893_5267)">
<ellipse cx="29.6059" cy="20.5806" rx="0.18384" ry="0.165827" transform="rotate(-22.7455 29.6059 20.5806)" fill="#FFDD86" />
</g>
<path d="M25.5636 24.6692L29.3652 26.4883" stroke="url(#paint15_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter10_f_6893_5267)">
<path d="M25.4984 24.5491L29.3 26.3682" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter11_f_6893_5267)">
<ellipse cx="29.5404" cy="26.3682" rx="0.18384" ry="0.165827" transform="rotate(28.2981 29.5404 26.3682)" fill="#FFDD86" />
</g>
<path d="M6.5281 24.681L2.72654 26.5" stroke="url(#paint16_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter12_f_6893_5267)">
<path d="M6.59334 24.5608L2.79179 26.3799" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter13_f_6893_5267)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.942776 -0.333427 -0.333427 0.942776 6.64614 24.5049)" fill="#FFDD86" />
</g>
<path d="M6.53973 22.5243L2.72615 20.7306" stroke="url(#paint17_linear_6893_5267)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter14_f_6893_5267)">
<path d="M6.67403 22.4988L2.86046 20.7051" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter15_f_6893_5267)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.338111 -0.941106 -0.941106 0.338111 6.75078 22.5043)" fill="#FFDD86" />
</g>
<path d="M20.4179 23.0547C20.4179 25.4947 18.436 27.3906 15.996 27.3906C13.556 27.3906 11.6835 25.4478 11.6835 23.0078C11.6835 23.0078 13.4882 25.1406 15.996 23.0078C18.3476 25.0859 20.4179 23.0547 20.4179 23.0547Z" fill="url(#paint18_linear_6893_5267)" />
<g filter="url(#filter16_f_6893_5267)">
<rect x="22.1024" y="10.1016" width="4.5851" height="14.1857" fill="url(#paint19_linear_6893_5267)" />
</g>
<path d="M25.8329 13.5034L23.0329 10.9034C22.7129 10.6034 22.7129 10.1234 23.0329 9.83344L26.0476 7.15626C26.4304 6.82032 27.2229 6.9297 27.2229 7.76344V12.9634C27.2229 13.7969 26.3429 13.9834 25.8329 13.5034Z" fill="#FFB915" />
<path d="M25.8329 13.5034L23.0329 10.9034C22.7129 10.6034 22.7129 10.1234 23.0329 9.83344L26.0476 7.15626C26.4304 6.82032 27.2229 6.9297 27.2229 7.76344V12.9634C27.2229 13.7969 26.3429 13.9834 25.8329 13.5034Z" fill="url(#paint20_linear_6893_5267)" />
<g filter="url(#filter17_f_6893_5267)">
<path d="M26.6336 7.36719L23.0086 10.4922L26.5555 13.3047L26.6336 7.36719Z" fill="url(#paint21_radial_6893_5267)" />
</g>
<g filter="url(#filter18_f_6893_5267)">
<path d="M23.7274 10.2891L26.2586 13.0234" stroke="url(#paint22_linear_6893_5267)" stroke-width="0.5" stroke-linecap="round" />
</g>
<g filter="url(#filter19_f_6893_5267)">
<path d="M5.22321 7.49927V13.407L8.69196 10.3176L5.22321 7.49927Z" fill="#FEB33E" />
</g>
<g filter="url(#filter20_f_6893_5267)">
<path d="M5.22321 7.72656V12.9141" stroke="#FFE7A3" stroke-width="0.15" stroke-linecap="round" />
</g>
<g filter="url(#filter21_f_6893_5267)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.9757 19.2763C10.8347 19.4617 10.7771 19.6974 10.7723 19.8424C10.7623 20.1459 10.5081 20.3839 10.2045 20.3739C9.90093 20.3639 9.66293 20.1097 9.67294 19.8061C9.68379 19.4771 9.79655 19.0096 10.1001 18.6105C10.4221 18.187 10.9408 17.868 11.6758 17.868C12.4182 17.868 12.9418 18.203 13.2658 18.6309C13.572 19.0351 13.6947 19.5116 13.7022 19.8512C13.7089 20.1549 13.4681 20.4065 13.1644 20.4132C12.8607 20.4198 12.6091 20.1791 12.6025 19.8754C12.5995 19.741 12.5403 19.4948 12.3889 19.295C12.2553 19.1186 12.0458 18.968 11.6758 18.968C11.2983 18.968 11.0982 19.1151 10.9757 19.2763Z" fill="url(#paint23_linear_6893_5267)" />
</g>
<g filter="url(#filter22_f_6893_5267)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.9445 19.2763C18.8035 19.4617 18.7459 19.6974 18.7411 19.8424C18.7311 20.1459 18.4769 20.3839 18.1733 20.3739C17.8697 20.3639 17.6317 20.1097 17.6417 19.8061C17.6526 19.4771 17.7653 19.0096 18.0688 18.6105C18.3909 18.187 18.9095 17.868 19.6445 17.868C20.387 17.868 20.9105 18.203 21.2346 18.6309C21.5407 19.0351 21.6635 19.5116 21.671 19.8512C21.6777 20.1549 21.4369 20.4065 21.1332 20.4132C20.8295 20.4198 20.5779 20.1791 20.5712 19.8754C20.5683 19.741 20.509 19.4948 20.3577 19.295C20.2241 19.1186 20.0146 18.968 19.6445 18.968C19.2671 18.968 19.067 19.1151 18.9445 19.2763Z" fill="url(#paint24_linear_6893_5267)" />
</g>
<path d="M10.5508 19.4141C10.5664 18.9401 10.8914 18.0078 12.0039 18.0078C13.1164 18.0078 13.4701 18.9792 13.4805 19.4531" stroke="url(#paint25_radial_6893_5267)" stroke-width="1.1" stroke-linecap="round" />
<path d="M10.5508 19.4141C10.5664 18.9401 10.8914 18.0078 12.0039 18.0078C13.1164 18.0078 13.4701 18.9792 13.4805 19.4531" stroke="url(#paint26_linear_6893_5267)" stroke-width="1.1" stroke-linecap="round" />
<path d="M18.5195 19.4141C18.5352 18.9401 18.8602 18.0078 19.9727 18.0078C21.0852 18.0078 21.4388 18.9792 21.4492 19.4531" stroke="url(#paint27_radial_6893_5267)" stroke-width="1.1" stroke-linecap="round" />
<path d="M18.5195 19.4141C18.5352 18.9401 18.8602 18.0078 19.9727 18.0078C21.0852 18.0078 21.4388 18.9792 21.4492 19.4531" stroke="url(#paint28_linear_6893_5267)" stroke-width="1.1" stroke-linecap="round" />
<g filter="url(#filter23_f_6893_5267)">
<ellipse cx="21.5391" cy="19.3672" rx="0.164062" ry="0.226562" fill="#715167" />
</g>
<g filter="url(#filter24_f_6893_5267)">
<ellipse cx="13.6445" cy="19.3672" rx="0.164062" ry="0.226562" fill="#715167" />
</g>
<defs>
<filter id="filter0_f_6893_5267" x="3.55408" y="20.3203" width="5.72693" height="6.698" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.75" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter1_f_6893_5267" x="19.098" y="4.5188" width="9.00903" height="11.6356" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter2_f_6893_5267" x="24.8055" y="4.41406" width="3.41745" height="4.625" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter3_f_6893_5267" x="2.94611" y="3.22656" width="8.94989" height="10.7286" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="1" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter4_f_6893_5267" x="13.7482" y="20.056" width="3.66141" height="3.02374" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter5_f_6893_5267" x="14.9086" y="19.8671" width="2.72344" height="1.74852" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.3" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter6_f_6893_5267" x="23.685" y="19.9796" width="6.00494" height="4.01282" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter7_f_6893_5267" x="23.6851" y="23.8235" width="4.12671" height="3.56561" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter8_f_6893_5267" x="24.9163" y="20.2484" width="5.10474" height="3.11255" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter9_f_6893_5267" x="28.9245" y="19.9119" width="1.36264" height="1.3374" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter10_f_6893_5267" x="24.8484" y="23.8991" width="5.10162" height="3.11908" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter11_f_6893_5267" x="28.8604" y="25.6981" width="1.35999" height="1.34009" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter12_f_6893_5267" x="2.14175" y="23.9108" width="5.10162" height="3.11908" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter13_f_6893_5267" x="5.96417" y="23.8369" width="1.36395" height="1.33594" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter14_f_6893_5267" x="2.21042" y="20.0551" width="5.11365" height="3.09381" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter15_f_6893_5267" x="6.08273" y="21.8224" width="1.33606" height="1.36389" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter16_f_6893_5267" x="18.1024" y="6.10156" width="12.5851" height="22.1857" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="2" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter17_f_6893_5267" x="22.4086" y="6.76719" width="4.825" height="7.1375" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.3" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter18_f_6893_5267" x="22.7274" y="9.28906" width="4.53125" height="4.73438" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.375" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter19_f_6893_5267" x="4.72321" y="6.99927" width="4.46875" height="6.90771" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter20_f_6893_5267" x="4.64819" y="7.15155" width="1.15002" height="6.33752" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter21_f_6893_5267" x="9.27264" y="17.468" width="4.82969" height="3.34529" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter22_f_6893_5267" x="17.2414" y="17.468" width="4.82969" height="3.34529" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter23_f_6893_5267" x="20.975" y="18.7406" width="1.12813" height="1.25313" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_6893_5267" />
</filter>
<filter id="filter24_f_6893_5267" x="13.0805" y="18.7406" width="1.12813" height="1.25313" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_6893_5267" />
</filter>
<linearGradient id="paint0_linear_6893_5267" x1="7.90668" y1="1.34978" x2="7.90668" y2="30.99" gradientUnits="userSpaceOnUse">
<stop offset="0.288159" stop-color="#F2CC26" />
<stop offset="0.762024" stop-color="#E99E20" />
<stop offset="0.9925" stop-color="#E3821D" />
<stop offset="1" stop-color="#E3801D" />
</linearGradient>
<linearGradient id="paint1_linear_6893_5267" x1="8.83102" y1="12.033" x2="1.16971" y2="12.033" gradientUnits="userSpaceOnUse">
<stop stop-color="#E99E20" stop-opacity="0" />
<stop offset="0.9925" stop-color="#E3821D" />
<stop offset="1" stop-color="#E3801D" />
</linearGradient>
<linearGradient id="paint2_linear_6893_5267" x1="26.7911" y1="12.033" x2="30.245" y2="12.033" gradientUnits="userSpaceOnUse">
<stop stop-color="#E99E20" stop-opacity="0" />
<stop offset="0.9925" stop-color="#E3821D" />
<stop offset="1" stop-color="#E3801D" />
</linearGradient>
<linearGradient id="paint3_linear_6893_5267" x1="16.001" y1="-1.59459" x2="16" y2="30.99" gradientUnits="userSpaceOnUse">
<stop offset="0.77079" stop-color="#F59639" stop-opacity="0" />
<stop offset="1" stop-color="#FF63C4" />
</linearGradient>
<linearGradient id="paint4_linear_6893_5267" x1="16" y1="11.2521" x2="16" y2="32.475" gradientUnits="userSpaceOnUse">
<stop offset="0.854227" stop-color="white" stop-opacity="0" />
<stop offset="0.985362" stop-color="white" />
</linearGradient>
<radialGradient id="paint5_radial_6893_5267" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.1755 4.47688) rotate(55.4547) scale(5.37104 10.6707)">
<stop stop-color="#FFA720" />
<stop offset="0.921158" stop-color="#FFA720" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint6_linear_6893_5267" x1="21.9081" y1="6.85159" x2="28.1649" y2="13.6953" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA720" />
<stop offset="1" stop-color="#FFA720" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint7_linear_6893_5267" x1="10.6023" y1="10.6172" x2="4.74258" y2="10.0703" gradientUnits="userSpaceOnUse">
<stop offset="0.437473" stop-color="#ED8C1B" />
<stop offset="1" stop-color="#FFB03A" />
</linearGradient>
<linearGradient id="paint8_linear_6893_5267" x1="27.0399" y1="4.96094" x2="26.4438" y2="7.30239" gradientUnits="userSpaceOnUse">
<stop offset="0.28598" stop-color="#FFE792" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint9_linear_6893_5267" x1="15.7508" y1="22.3512" x2="15.0789" y2="23.4449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3900E" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint10_radial_6893_5267" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.4461 21.1797) rotate(-153.246) scale(2.11729 2.12981)">
<stop stop-color="#EA088B" />
<stop offset="1" stop-color="#E61E27" />
</radialGradient>
<linearGradient id="paint11_linear_6893_5267" x1="17.2036" y1="20.7414" x2="15.6801" y2="20.7414" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.9" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint12_linear_6893_5267" x1="24.2898" y1="23.236" x2="28.0242" y2="21.3203" gradientUnits="userSpaceOnUse">
<stop stop-color="#E88105" />
<stop offset="1" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint13_linear_6893_5267" x1="24.2898" y1="24.5798" x2="28.0242" y2="26.4955" gradientUnits="userSpaceOnUse">
<stop offset="0.286458" stop-color="#DE7D07" />
<stop offset="0.817708" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint14_linear_6893_5267" x1="25.2898" y1="22.9453" x2="29.371" y2="21.0078" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint15_linear_6893_5267" x1="25.0739" y1="24.7096" x2="29.1465" y2="26.6651" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint16_linear_6893_5267" x1="7.01785" y1="24.7214" x2="2.94525" y2="26.6768" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint17_linear_6893_5267" x1="6.81823" y1="22.9292" x2="2.72754" y2="21.0119" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint18_linear_6893_5267" x1="16.0466" y1="27.5225" x2="16.0466" y2="23.1004" gradientUnits="userSpaceOnUse">
<stop stop-color="#E61E27" />
<stop offset="1" stop-color="#672A7A" />
</linearGradient>
<linearGradient id="paint19_linear_6893_5267" x1="23.3524" y1="10.8385" x2="22.9558" y2="22.8891" gradientUnits="userSpaceOnUse">
<stop offset="0.548257" stop-color="#FFDD65" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint20_linear_6893_5267" x1="26.5398" y1="15.5078" x2="25.2898" y2="12.0391" gradientUnits="userSpaceOnUse">
<stop stop-color="#EF8A47" />
<stop offset="1" stop-color="#EF8A47" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint21_radial_6893_5267" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.868 7.77344) rotate(55.7389) scale(7.82686 9.38424)">
<stop stop-color="#FFDF70" />
<stop offset="1" stop-color="#FFDF70" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint22_linear_6893_5267" x1="25.0555" y1="10.4766" x2="25.0555" y2="12.8203" gradientUnits="userSpaceOnUse">
<stop offset="0.432292" stop-color="#FFE7A3" />
<stop offset="1" stop-color="#FFDF83" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint23_linear_6893_5267" x1="14.9531" y1="19.7308" x2="15.2627" y2="22.9469" gradientUnits="userSpaceOnUse">
<stop stop-color="#EC9611" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint24_linear_6893_5267" x1="22.9219" y1="19.7308" x2="23.2315" y2="22.9469" gradientUnits="userSpaceOnUse">
<stop stop-color="#EC9611" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint25_radial_6893_5267" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12.0156 19.5886) rotate(-91.0929) scale(5.75971 5.75893)">
<stop offset="0.166667" stop-color="#482641" />
<stop offset="0.276042" stop-color="#594253" />
<stop offset="0.401042" stop-color="#483637" />
</radialGradient>
<linearGradient id="paint26_linear_6893_5267" x1="9.98438" y1="20.1406" x2="11.75" y2="18.8594" gradientUnits="userSpaceOnUse">
<stop stop-color="#271B27" />
<stop offset="1" stop-color="#483637" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint27_radial_6893_5267" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.9844 19.5886) rotate(-91.0929) scale(5.75971 5.75893)">
<stop offset="0.166667" stop-color="#482641" />
<stop offset="0.276042" stop-color="#594253" />
<stop offset="0.401042" stop-color="#483637" />
</radialGradient>
<linearGradient id="paint28_linear_6893_5267" x1="17.9531" y1="20.1406" x2="19.7188" y2="18.8594" gradientUnits="userSpaceOnUse">
<stop stop-color="#271B27" />
<stop offset="1" stop-color="#483637" stop-opacity="0" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,13 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 25.942C4 28.1739 5.76327 30 7.91837 30H24.0816C26.2367 30 28 28.0725 28 25.8406V6.4297C28 5.1297 26.4099 4.5297 25.5155 5.4297L20.9736 10H11.1617L6.5 5.4297C5.6 4.5297 4 5.1297 4 6.4297V25.942Z" fill="#FFB02E" />
<path d="M9.00005 10.9265L6.20005 13.5265C5.70005 14.0265 4.80005 13.6265 4.80005 12.9265V7.72648C4.80005 7.12648 5.70005 6.72648 6.20005 7.22648L9.00005 9.82648C9.30005 10.1265 9.30005 10.6265 9.00005 10.9265Z" fill="#FF822D" />
<path d="M23.05 10.9265L25.85 13.5265C26.35 14.0265 27.25 13.6265 27.25 12.9265V7.72648C27.25 7.12648 26.35 6.72648 25.85 7.22648L23.05 9.82648C22.75 10.1265 22.75 10.6265 23.05 10.9265Z" fill="#FF822D" />
<path d="M17.0429 20H14.9571C14.5117 20 14.2886 20.5386 14.6036 20.8536L15.6465 21.8964C15.8417 22.0917 16.1583 22.0917 16.3536 21.8964L17.3965 20.8536C17.7114 20.5386 17.4884 20 17.0429 20Z" fill="#F70A8D" />
<path d="M2.72372 20.0528C2.47673 19.9293 2.17639 20.0294 2.0529 20.2764C1.9294 20.5234 2.02951 20.8237 2.2765 20.9472L6.2765 22.9472C6.52349 23.0707 6.82383 22.9706 6.94732 22.7236C7.07082 22.4766 6.97071 22.1763 6.72372 22.0528L2.72372 20.0528Z" fill="#FF6723" />
<path d="M2.72372 26.9472C2.47673 27.0707 2.17639 26.9706 2.0529 26.7236C1.9294 26.4766 2.02951 26.1763 2.2765 26.0528L6.2765 24.0528C6.52349 23.9293 6.82383 24.0294 6.94732 24.2764C7.07082 24.5234 6.97071 24.8237 6.72372 24.9472L2.72372 26.9472Z" fill="#FF6723" />
<path d="M29.9473 20.2764C29.8238 20.0294 29.5235 19.9293 29.2765 20.0528L25.2765 22.0528C25.0295 22.1763 24.9294 22.4766 25.0529 22.7236C25.1764 22.9706 25.4767 23.0707 25.7237 22.9472L29.7237 20.9472C29.9707 20.8237 30.0708 20.5234 29.9473 20.2764Z" fill="#FF6723" />
<path d="M29.2765 26.9472C29.5235 27.0707 29.8238 26.9706 29.9473 26.7236C30.0708 26.4766 29.9707 26.1763 29.7237 26.0528L25.7237 24.0528C25.4767 23.9293 25.1764 24.0294 25.0529 24.2764C24.9294 24.5234 25.0295 24.8237 25.2765 24.9472L29.2765 26.9472Z" fill="#FF6723" />
<path d="M15.9999 23.106C15.4625 23.6449 14.5434 24 13.4999 24C12.4681 24 11.5579 23.6527 11.0181 23.1239C11.1384 23.8481 11.9461 27.5 15.9999 27.5C20.0538 27.5 20.8615 23.8481 20.9818 23.1239C20.4419 23.6527 19.5317 24 18.4999 24C17.4564 24 16.5374 23.6449 15.9999 23.106Z" fill="#BB1D80" />
<path d="M11 19.5C11 19.33 11.0551 19.0639 11.2058 18.8547C11.3381 18.6709 11.563 18.5 12 18.5C12.437 18.5 12.6619 18.6709 12.7942 18.8547C12.9449 19.0639 13 19.33 13 19.5C13 19.7761 13.2239 20 13.5 20C13.7761 20 14 19.7761 14 19.5C14 19.17 13.9051 18.6861 13.6058 18.2703C13.2881 17.8291 12.763 17.5 12 17.5C11.237 17.5 10.7119 17.8291 10.3942 18.2703C10.0949 18.6861 10 19.17 10 19.5C10 19.7761 10.2239 20 10.5 20C10.7761 20 11 19.7761 11 19.5Z" fill="#402A32" />
<path d="M19 19.5C19 19.33 19.0551 19.0639 19.2058 18.8547C19.3381 18.6709 19.563 18.5 20 18.5C20.437 18.5 20.6619 18.6709 20.7942 18.8547C20.9449 19.0639 21 19.33 21 19.5C21 19.7761 21.2239 20 21.5 20C21.7761 20 22 19.7761 22 19.5C22 19.17 21.9051 18.6861 21.6058 18.2703C21.2881 17.8291 20.763 17.5 20 17.5C19.237 17.5 18.7119 17.8291 18.3942 18.2703C18.0949 18.6861 18 19.17 18 19.5C18 19.7761 18.2239 20 18.5 20C18.7761 20 19 19.7761 19 19.5Z" fill="#402A32" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,5 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 7C4 5.89543 4.89543 5 6 5H29C30.1046 5 31 5.89543 31 7V29C31 30.1046 30.1046 31 29 31H4C2.34315 31 1 29.6569 1 28V10C1 8.34314 2.34315 7 4 7Z" fill="#B4ACBC" />
<path d="M28 10C28 8.89543 27.1046 8 26 8H4C2.89543 8 2 8.89543 2 10V28C2 29.1046 2.89543 30 4 30H29.5C28.6716 30 28 29.3284 28 28.5V10Z" fill="#F3EEF8" />
<path d="M4 11C4 10.4477 4.44772 10 5 10H25C25.5523 10 26 10.4477 26 11C26 11.5523 25.5523 12 25 12H5C4.44772 12 4 11.5523 4 11ZM4 14.5C4 14.2239 4.22386 14 4.5 14H25.5C25.7761 14 26 14.2239 26 14.5C26 14.7761 25.7761 15 25.5 15H4.5C4.22386 15 4 14.7761 4 14.5ZM19.5 17C19.2239 17 19 17.2239 19 17.5C19 17.7761 19.2239 18 19.5 18H25.5C25.7761 18 26 17.7761 26 17.5C26 17.2239 25.7761 17 25.5 17H19.5ZM19 20.5C19 20.2239 19.2239 20 19.5 20H25.5C25.7761 20 26 20.2239 26 20.5C26 20.7761 25.7761 21 25.5 21H19.5C19.2239 21 19 20.7761 19 20.5ZM19.5 23C19.2239 23 19 23.2239 19 23.5C19 23.7761 19.2239 24 19.5 24H25.5C25.7761 24 26 23.7761 26 23.5C26 23.2239 25.7761 23 25.5 23H19.5ZM19 26.5C19 26.2239 19.2239 26 19.5 26H25.5C25.7761 26 26 26.2239 26 26.5C26 26.7761 25.7761 27 25.5 27H19.5C19.2239 27 19 26.7761 19 26.5ZM6 17C4.89543 17 4 17.8954 4 19V25C4 26.1046 4.89543 27 6 27H15C16.1046 27 17 26.1046 17 25V19C17 17.8954 16.1046 17 15 17H6Z" fill="#998EA4" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -136,6 +136,14 @@
"KeyboardShortcutClosePopup": "close all popups",
"CollapseLegal": "legal stuff",
"FairUse": "cobalt is a tool for easing content downloads from internet and takes <span class=\"text-backdrop\">zero liability</span>. you are responsible for what you download, how you use and distribute that content.\n\ncobalt does not log any info about you, it's impossible for me to snitch on you, but please be mindful when using content of others and always credit original creators!\n\nwhen used in education purposes (lecture, homework, etc) please attach the source link.\n\nfair use and credits benefit everyone.",
"UrgentFeatureUpdate71": "more supported services!"
"UrgentFeatureUpdate71": "more supported services!",
"UrgentThanks": "thank you for support!",
"SettingsDisableMetadata": "don't add metadata",
"UrgentNewDomain": "new domain, same cobalt",
"NewDomainWelcomeTitle": "hey there!",
"NewDomainWelcome": "cobalt is moving! same features, same owner, simply a more rememberable domain. and still no ads.\n\n<span class=\"text-backdrop\">cobalt.tools</span> is the new main domain, aka where you are now. make sure to update your bookmarks and reinstall the web app!",
"DataTransferSuccess": "btw, your settings have been transferred automatically :)",
"DataTransferError": "something went wrong when transferring your preferences. you'll have to open settings and configure cobalt by hand.",
"SupportNotAffiliated": "cobalt is <span class=\"text-backdrop\">not affiliated</span> with any services listed above."
}
}

View file

@ -38,8 +38,8 @@
"SettingsFormatSubtitle": "формат",
"SettingsQualitySubtitle": "качество",
"SettingsThemeAuto": "авто",
"SettingsThemeLight": "светлая",
"SettingsThemeDark": "тёмная",
"SettingsThemeLight": "светлый",
"SettingsThemeDark": "тёмный",
"SettingsKeepDownloadButton": "всегда показывать &gt;&gt;",
"AccessibilityKeepDownloadButton": "всегда показывать кнопку скачивания на экране",
"SettingsEnableDownloadPopup": "выбор метода скачивания",
@ -52,7 +52,7 @@
"ClickToCopy": "нажми, чтобы скопировать",
"Download": "скачать",
"CopyURL": "скопировать",
"AboutTab": "о кобальте",
"AboutTab": "о сайте",
"ChangelogTab": "изменения",
"DonationsTab": "донаты",
"SettingsVideoTab": "видео",
@ -64,11 +64,11 @@
"SettingsAudioFormatBest": "лучший",
"SettingsAudioFormatDescription": "когда выбран \"лучший\", ты получишь аудио без каких-либо изменений. такое, какое оно есть на стороне сервиса. если же выбрано что-то другое, то аудио будет немного сжато.",
"Keyphrase": "сохраняй то, что любишь",
"SettingsRemoveWatermark": "убирать ватермарку",
"SettingsRemoveWatermark": "убрать ватермарку",
"ErrorPopupCloseButton": "ясно",
"ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат, чтобы обойти ограничения.",
"SettingsAudioFullTikTok": "полное аудио",
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео. без каких-либо изменений от автора поста.",
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео, без каких-либо изменений от автора поста.",
"ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, а если так и не получится, то {ContactLink}.",
"ErrorNoVideosInTweet": "я не смог найти никакого медиа контента в этом твите. попробуй другой!",
"ImagePickerTitle": "выбери картинки для скачивания",
@ -91,7 +91,7 @@
"TwitterSpaceWasntRecorded": "мне нечего скачать, так как этот twitter space не был записан. попробуй другой!",
"ErrorCantProcess": "я не смог обработать твой запрос :(\nты можешь попробовать ещё раз, но если не поможет, то {ContactLink}.",
"ChangelogPressToHide": "скрыть",
"Donate": "задонатить",
"Donate": "донаты",
"DonateSub": "ты можешь помочь!",
"DonateExplanation": "кобальт не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно для всех</span>. но разработка и поддержка медиа сервиса, которым пользуются более 350 тысяч людей, обходится довольно затратно. мне, как студенту, оплачивать такое в одиночку довольно трудно.\n\nесли кобальт тебе помог и ты хочешь, чтобы он продолжал работать и развиваться, то это можно сделать через донаты!\n\nделая донат ты помогаешь всем, кто пользуется кобальтом: преподавателям, студентам, музыкантам, художникам, контент-мейкерам и многим-многим другим!\n\nза последние несколько месяцев благодаря донатам я смог:\n*; повысить стабильность и аптайм почти до 100%.\n*; ускорить ВСЕ загрузки, особенно наиболее тяжёлые.\n*; открыть api кобальта для свободного публичного использования.\n*; выдержать несколько огромных наплывов пользователей без перебоев.\n*; перейти к надёжному поставщику облачной инфры.\n*; разделить фронтенд и api для обеспечения отказоустойчивости и децентрализации в будущем.\n\n<span class=\"text-backdrop\">каждый донат невероятно ценится</span> и помогает кобальту развиваться!",
"DonateVia": "открыть",
@ -102,11 +102,11 @@
"CollapseServices": "что поддерживается?",
"CollapseSupport": "поддержка и исходный код",
"CollapsePrivacy": "политика конфиденциальности",
"ServicesNote": "этот список далеко не финальный и постоянно пополняется. заглядывай сюда почаще, тогда точно будешь знать, что поддерживается!",
"FollowSupport": "оставайтесь на связи с кобальтом для новостей, поддержки, участия в опросах, и многого другого:",
"SupportNote": "так как я один занимаюсь разработкой и поддержкой в одиночку, время ожидания ответа может достигать нескольких часов. но я отвечаю всем, так что не стесняйся.",
"ServicesNote": "этот список далеко не финальный и постоянно пополняется, заглядывай сюда почаще!",
"FollowSupport": "подписывайся на соц.сети кобальта для новостей, поддержки, участия в опросах, и многого другого:",
"SupportNote": "так как я занимаюсь разработкой и поддержкой в одиночку, время ожидания ответа может достигать нескольких часов. но я отвечаю всем, так что не стесняйся.",
"SourceCode": "пиши о проблемах, шарься в исходнике, или же форкай репозиторий:",
"PrivacyPolicy": "политика конфиденциальности кобальта довольно проста: никакие данные о тебе никогда не собираются и не хранятся. нуль, ноль, нада, ничего.\nто, что ты скачиваешь, - твоё личное дело, а не чьё-либо ещё.\n\nесли твоей загрузке требуется живой рендер, то некоторые неотслеживаемые данные временно держатся в ОЗУ сервера. это необходимо для работы данной функции.\n\nв этом случае данные о запрошенном контенте хранятся в течение <span class=\"text-backdrop\">20 секунд</span>. по истечении этого времени всё стирается. ни у кого (даже у меня) нет доступа к временно хранящимся данным, так как официальная кодовая база кобальта не предусматривает возможности их чтения вне функций обработки.\n\nты всегда можешь посмотреть <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">исходный код кобальта</a> и убедиться, что всё так, как заявлено.",
"PrivacyPolicy": "политика конфиденциальности кобальта довольно проста: никакие данные о тебе никогда не собираются и не хранятся. нуль, ноль, нада, ничего.\nто, что ты скачиваешь, - твоё личное дело, а не чьё-либо ещё.\n\nесли твоей загрузке требуется лайв рендер, то некоторые неотслеживаемые данные временно держатся в ОЗУ сервера. это необходимо для работы данной функции.\n\nв этом случае данные о запрошенном контенте хранятся в течение <span class=\"text-backdrop\">20 секунд</span>. по истечении этого времени всё стирается. ни у кого (даже у меня) нет доступа к временно хранящимся данным, так как официальная кодовая база кобальта не предусматривает возможности их чтения вне функций обработки.\n\nты всегда можешь посмотреть <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">исходный код кобальта</a> и убедиться, что всё так, как заявлено.",
"ErrorYTUnavailable": "это видео недоступно, возможно оно ограничено по региону или доступу. попробуй другое!",
"ErrorYTTryOtherCodec": "я не нашёл того, что мог бы скачать с твоими настройками. попробуй другой кодек или качество!",
"SettingsCodecSubtitle": "кодек для видео с youtube",
@ -124,7 +124,7 @@
"PopupCloseDone": "готово",
"Accessibility": "общедоступность",
"SettingsReduceTransparency": "уменьшить прозрачность",
"SettingsDisableAnimations": "выключить анимации",
"SettingsDisableAnimations": "убрать анимации",
"FeatureErrorGeneric": "твой браузер не разрешает или не поддерживает эту функцию. проверь наличие обновлений и попробуй ещё раз!",
"ClipboardErrorFirefox": "ты используешь firefox в котором все функции чтения из буфера обмена отключены по умолчанию.\n\nно это можно исправить следуя шагам, описанным <a class=\"text-backdrop link\" href=\"{repo}/wiki/Troubleshooting#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">здесь</a>\n\n...или же ты можешь просто вставить ссылку вручную.",
"ClipboardErrorNoPermission": "кобальт не может прочитать последний элемент в буфере обмена без твоего разрешения.\n\nесли ты не хочешь давать доступ, просто вставь ссылку вручную.\n\nну а если хочешь, то открой настройки сайта и разреши доступ на чтение буфера обмена.",
@ -137,6 +137,15 @@
"KeyboardShortcutClosePopup": "закрыть все окна",
"CollapseLegal": "правовые штучки",
"FairUse": "кобальт - это инструмент для облегчения скачивания контента из интернета, и он <span class=\"text-backdrop\">не несёт никакой ответственности</span>. ты несёшь ответственность за то, что скачиваешь, как используешь и распространяешь скачанный контент.\n\nкобальт не собирает никакой информации о тебе, и не может донести на тебя, но, пожалуйста, будь сознателен при использовании чужого контента и всегда указывай авторов!\n\nпри использовании в образовательных целях (лекции, домашние задания и т.д.), пожалуйста, прикладывай ссылку на источник.\n\nчестное использование и указание авторства выгодно всем.",
"UrgentFeatureUpdate71": "расширение поддержки сервисов!"
"UrgentFeatureUpdate71": "расширение поддержки сервисов!",
"UrgentThanks": "спасибо за поддержку!",
"SettingsDisableMetadata": "не добавлять метаданные",
"UrgentNewDomain": "новый домен, тот же кобальт",
"NewDomainWelcomeTitle": "привет!",
"NewDomainWelcome": "кобальт переезжает! те же функции, тот же владелец, просто более запоминающийся домен. по-прежнему без рекламы.\n\n<span class=\"text-backdrop\">cobalt.tools</span> - новый основной домен, т.е. где ты сейчас находишься. не забудь обновить закладки и переустановить веб-приложение!",
"DataTransferSuccess": "кстати, твои настройки были перенесены автоматически :)",
"DataTransferError": "при переносе настроек что-то пошло не так. придётся зайти в настройки и настроить кобальт вручную.",
"SupportNotAffiliated": "кобальт <span class=\"text-backdrop\">не аффилирован</span> ни с одним из перечисленных выше сервисов.",
"SupportMetaNoticeRU": "деятельность meta platforms inc. (владелец instagram) запрещена на территории россии."
}
}

View file

@ -1,5 +1,36 @@
{
"current": {
"version": "7.5",
"date": "September 16, 2023",
"title": "support for twitch clips and rutube!",
"banner": {
"file": "twitchupdate.webp",
"width": 851,
"height": 640
},
"content": "hey! this update (finally) adds support for twitch clips and rutube, among other smaller changes.\n\nservice improvements:\n*; added support for twitch clips. no vods, they're unnecessary. just clip whatever you want to download!\n*; added support for rutube in case you ever wanted to download something russian.\n\ninterface improvements:\n*; added a note about cobalt not being affiliated with any supported services.\n*; added a note about meta (the company) in russian.\n*; better russian localization. will keep improving it to make it sound not so robotic over time.\n\nother improvements:\n*; all official servers are now using the docker package. and so should you!\n*; moved the load balancer to poland. requests should be slightly faster now.\n*; minor codebase clean up.\n\nif you're confused about the new domain, read the older changelog! just scroll lower and press \"expand\".\n\ni hope you find this update useful and have a wonderful day :)\n\nbtw, cobalt has a pretty active community server on discord. go to about > support & source code to join!"
},
"history": [{
"version": "7.4",
"date": "September 9, 2023",
"title": "new domain, what's coming in future, bug fixes, and more!",
"banner": {
"file": "newdomain.webp",
"width": 960,
"height": 540
},
"content": "cobalt is finally moving to its own domain! many of you have been anticipating this, and many kept forgetting the link due to how cryptic it was.\n\nwell, worry no more - <span class=\"text-backdrop\">cobalt.tools</span> is here.\n\nif you haven't yet, open <a class=\"text-backdrop link\" href=\"https://co.wukko.me\" target=\"_blank\">co.wukko.me</a> to transfer your settings here! no additional action from you is required. just open the old link and cobalt will do everything for you :)\n\nmake sure to <span class=\"text-backdrop\">update your bookmarks</span> and reinstall the web app!\n\nhere's what domain change means:\n*; still no ads, same owner, same features, same reliability. just a way more rememberable link (it's literally two words).\n*; cobalt.tools makes it clear that cobalt is a tool and that it's \"cobalt\", not \"wukko\".\n*; i can host various versions of cobalt on subdomains without links looking awkward.\n*; i can host cobalt-related websites without polluting my personal domain's dns (such as crowdin).\n*; i stand by same privacy policies (and in fact am using the same exact server as before).\n\nthe domain change is required for the future of cobalt.\n\nhere's what's coming soon:\n*; support for many top-requested sites, such as (but not limited to) twitch and niconico.\n*; education version of cobalt, as often requested by students and educators.\n*; major localization system upgrade, allowing for simpler community contributions.\n*; region-specific versions with 100% translations and tweaks.\n*; native clients for desktop and mobile (not sure about this one, i'm no superman).\n*; ...and more!\n\nnow, here's what's new in 7.4:\n*; tabs in popups now scroll to top on tab bar tap.\n*; padding across web app was tuned.\n*; (obviously) a migration agent. soon will be used for importing and exporting settings.\n*; some minor clean ups in codebase.\n\nif you want to help cobalt achieve goals listed above, consider donating! donations are the only way i can keep cobalt ad-less, powerful, (basically) limitless, and also 100% free.\n\nin fact, donations have helped me grow cobalt more than i've ever anticipated. just imagine how much better it will be in a year.\n\ngo to donations down below to find ways to donate!\n\nthank you for reading through all of this. i hope you enjoy this update and have a great day :D"
}, {
"version": "7.2 & 7.3",
"date": "September 6, 2023",
"title": "extended video length limit, metadata toggle, ui improvements, and more!",
"banner": {
"file": "meowthsnap.webp",
"width": 500,
"height": 280
},
"content": "this update gives cobalt a sharp look in chromium browsers and makes it even more useful than before. check out the full changelog below!\n\nservice improvements:\n*; increased video length limit from 3 hours to 5 hours. feel free to download lectures you need :)\n*; you can now disable file metadata in settings.\n*; fixed a bug which previously caused some downloads to end up being 0 bytes.\n\nui improvements:\n*; fixed clickable area for urgent notice (text on top).\n*; fixed blurry header in chrome.\n*; fixed blurry tab bar in chrome.\n*; fixed blurry switches in chrome.\n*; fixed weirdly rounded corners in popups.\n*; fixed 1px gap on edges of various elements in popup in chrome.\n*; fixed overscrolling in other settings tab on ios.\n*; fixed unexpected button highlight effect on phones.\n*; removed outdated fixes for tiny screens.\n\nother improvements:\n*; cobalt web & api start faster than before, additional preparation functions aren't unexpectedly run anymore.\n*; cobalt is now available as a docker package. check it out on <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pkgs/container/cobalt\" target=\"_blank\">github</a>.\n\nthank you for being here. i hope you have a great day :D"
}, {
"version": "7.1",
"date": "August 20, 2023",
"title": "instagram, streamable, video metadata, and more!",
@ -9,8 +40,7 @@
"height": 358
},
"content": "service improvements:\n*; extended instagram support: high quality photos, videos, reels. everything should work without any issues, enjoy! :)\n*; added support for streamable.com (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/179\" target=\"_blank\">#179</a>)\n*; added video metadata to youtube videos.\n*; fixed vk video downloads.\n*; vxtwitter links are now supported.\n*; fixed support for youtube audio dubs.\n\nui improvements:\n*; fixed picker popup: it's now scrollable in all cases and clickable areas don't overlap each other.\n\nbackend improvements:\n*; cobalt will now let you know if something goes wrong during video download instead of nuking the stream.\n*; added support for cookies (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/177\" target=\"_blank\">#177</a>)\n*; replaced got with undici (thanks to <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/pull/182\" target=\"_blank\">#182</a>). downloads should be slightly faster and clean of garbage in headers.\n\ninternal improvements:\n*; moved host overrides into its own module.\n*; minor clean ups.\n\neven more cool stuff is coming in future updates! thank you for using cobalt :D"
},
"history": [{
}, {
"version": "7.0",
"date": "August 15, 2023",
"title": "biggest ui refresh yet!",

View file

@ -33,7 +33,9 @@ const names = {
"🔗": "link",
"⌨": "keyboard",
"📑": "boring_document",
"🧮": "abacus"
"🧮": "abacus",
"😸": "cat_grin",
"📰": "newspaper"
}
let sizing = {
18: 0.8,

View file

@ -1,4 +1,4 @@
import { celebrations } from "../config.js";
import { authorInfo, celebrations } from "../config.js";
import emoji from "../emoji.js";
export const backButtonSVG = `<svg width="22" height="22" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -68,17 +68,19 @@ export function popup(obj) {
}
return `
${obj.standalone ? `<div id="popup-${obj.name}" class="popup center${!obj.buttonOnly ? " box": ''}${classes.length > 0 ? ' ' + classes.join(' ') : ''}">` : ''}
<div id="popup-header" class="popup-header${!obj.buttonOnly ? " glass-bkg": ''}">
<div id="popup-header" class="popup-header">
<div id="popup-header-contents">
${obj.buttonOnly ? obj.header.emoji : ``}
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
</div>
${!obj.buttonOnly ? `<div class="glass-bkg alone"></div>`: ''}
</div>
<div id="popup-content" class="popup-content-inner">
${body}${obj.buttonOnly ? `<button id="close-error" class="switch" onclick="popup('${obj.name}', 0)">${obj.buttonText}</button>` : ''}
</div>
${classes.includes("small") ? `<div class="glass-bkg small"></div>`: ''}
${obj.standalone ? `</div>` : ''}`
}
@ -97,14 +99,18 @@ export function multiPagePopup(obj) {
return `
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${obj.header ? `<div id="popup-header" class="popup-header glass-bkg">
${obj.header ? `<div id="popup-header" class="popup-header">
<div id="popup-header-contents">
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
</div>
<div class="glass-bkg alone"></div>
</div>` : ''}${tabContent}</div>
<div id="popup-tabs" class="switches popup-tabs glass-bkg"><div class="switches popup-tabs-child">${tabs}</div></div>
<div id="popup-tabs" class="switches popup-tabs">
<div class="switches popup-tabs-child">${tabs}</div>
<div class="glass-bkg alone"></div>
</div>
</div>`
}
export function collapsibleList(arr) {
@ -136,20 +142,34 @@ export function popupWithBottomButtons(obj) {
return `
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${obj.header ? `<div id="popup-header" class="popup-header glass-bkg">
${obj.header ? `<div id="popup-header" class="popup-header">
<div id="popup-header-contents">
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
${obj.header.explanation ? `<div class="explanation">${obj.header.explanation}</div>` : ''}
</div>
<div class="glass-bkg alone"></div>
</div>` : ''}${obj.content}</div>
<div id="popup-tabs" class="switches popup-tabs glass-bkg"><div id="picker-buttons" class="switches popup-tabs-child">${tabs}</div></div>
<div id="popup-tabs" class="switches popup-tabs">
<div id="picker-buttons" class="switches popup-tabs-child">${tabs}</div>
<div class="glass-bkg alone"></div>
</div>
</div>`
}
export function socialLink(emji, name, handle, url) {
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop link" href="${url}" target="_blank">${handle}</a></div>`
}
export function socialLinks(lang) {
let links = authorInfo.support[lang] ? authorInfo.support[lang] : authorInfo.support.default;
let r = ``;
for (let i in links) {
r += socialLink(
emoji(links[i].emoji), i, links[i].handle, links[i].url
)
}
return r
}
export function settingsCategory(obj) {
return `<div id="settings-${obj.name}" class="settings-category">
<div class="category-title">${obj.title ? obj.title : obj.name}</div>
@ -205,7 +225,9 @@ export function celebrationsEmoji() {
}
export function urgentNotice(obj) {
if (obj.visible) {
return `<div id="urgent-notice" class="urgent-notice explanation" onclick="${obj.action}">${emoji(obj.emoji, 18)} ${obj.text}</div>`
return `<div id="urgent-notice" class="urgent-notice explanation">` +
`<span class="urgent-text" onclick="${obj.action}">${emoji(obj.emoji, 18)} ${obj.text}</span>` +
`</div>`
}
return ``
}
@ -226,3 +248,10 @@ export function keyboardShortcuts(arr) {
return base;
}
export function webLoc(t, arr) {
let base = ``;
for (let i = 0; i < arr.length; i++) {
base += `${arr[i]}:` + "`" + t(arr[i]) + "`" + `,`
}
return `{${base}};`
}

View file

@ -1,4 +1,4 @@
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, urgentNotice, keyboardShortcuts } from "./elements.js";
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, socialLinks, urgentNotice, keyboardShortcuts, webLoc } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { getCommitInfo } from "../sub/currentCommit.js";
import loc from "../../localization/manager.js";
@ -33,7 +33,7 @@ export default function(obj) {
let isIOS = ua.match("iphone os");
let isMobile = ua.match("android") || ua.match("iphone os");
let platform = isMobile ? "m" : "p";
let platform = isMobile ? "m" : "d";
if (isMobile && isIOS) platform = "i";
audioFormats[0]["text"] = t('SettingsAudioFormatBest');
@ -71,11 +71,11 @@ export default function(obj) {
<link rel="stylesheet" href="fonts/notosansmono.css" rel="preload" />
<link rel="stylesheet" href="cobalt.css" />
<link rel="me" href="${authorInfo.support.mastodon.url}">
<link rel="me" href="${authorInfo.support.default.mastodon.url}">
<noscript><div style="margin: 2rem;">${t('NoScriptMessage')}</div></noscript>
</head>
<body id="cobalt-body" ${platform === "p" ? 'class="desktop"' : ''} data-nosnippet ontouchstart>
<body id="cobalt-body" ${platform === "d" ? 'class="desktop"' : ''} data-nosnippet ontouchstart>
<body id="notification-area"></div>
${multiPagePopup({
name: "about",
@ -99,7 +99,11 @@ export default function(obj) {
text: collapsibleList([{
name: "services",
title: `${emoji("🔗")} ${t("CollapseServices")}`,
body: `${enabledServices}<br/><br/>${t("ServicesNote")}`
body: `${enabledServices}`
+ `<div class="explanation embedded">${t("SupportNotAffiliated")}`
+ `${obj.lang === "ru" ? `<br>${t("SupportMetaNoticeRU")}` : ''}`
+ `</div>`
+ `${t("ServicesNote")}`
}, {
name: "keyboard",
title: `${emoji("⌨")} ${t("CollapseKeyboard")}`,
@ -143,19 +147,11 @@ export default function(obj) {
name: "support",
title: `${emoji("❤️‍🩹")} ${t("CollapseSupport")}`,
body:
`${t("SupportSelfTroubleshooting")}<br/><br/>
${t("FollowSupport")}<br/>
${socialLink(
emoji("🐦"), "twitter", authorInfo.support.twitter.handle, authorInfo.support.twitter.url
)}
${socialLink(
emoji("👾"), "discord", authorInfo.support.discord.handle, authorInfo.support.discord.url
)}
${socialLink(
emoji("🐘"), "mastodon", authorInfo.support.mastodon.handle, authorInfo.support.mastodon.url
)}<br/>
${t("SourceCode")}<br/>
${socialLink(
`${t("SupportSelfTroubleshooting")}<br/><br/>`
+ `${t("FollowSupport")}<br/>`
+ `${socialLinks(obj.lang)}<br/>`
+ `${t("SourceCode")}<br/>`
+ `${socialLink(
emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo
)}<br/>
${t("SupportNote")}`
@ -440,16 +436,19 @@ export default function(obj) {
name: "miscellaneous",
title: t('Miscellaneous'),
body: checkbox([{
action: "disableChangelog",
name: t("SettingsDisableNotifications")
}, {
action: "downloadPopup",
name: t("SettingsEnableDownloadPopup"),
padding: "no-margin",
aria: t("AccessibilityEnableDownloadPopup")
}, {
action: "disableMetadata",
name: t("SettingsDisableMetadata")
}, {
action: "disableChangelog",
name: t("SettingsDisableNotifications"),
padding: "no-margin"
}])
})
}],
}]
})}
${popupWithBottomButtons({
name: "picker",
@ -466,7 +465,7 @@ export default function(obj) {
name: "download",
standalone: true,
buttonOnly: true,
classes: ["small", "glass-bkg"],
classes: ["small"],
header: {
closeAria: t('AccessibilityGoBack'),
emoji: emoji("🐱", 78, 1, 1),
@ -487,20 +486,34 @@ export default function(obj) {
name: "error",
standalone: true,
buttonOnly: true,
classes: ["small", "glass-bkg"],
classes: ["small"],
header: {
closeAria: t('AccessibilityGoBack'),
title: t('TitlePopupError'),
emoji: emoji("😿", 78, 1, 1),
},
body: `<div id="desc-error" class="desc-padding subtext"></div>`,
body: `<div id="desc-error" class="desc-padding subtext desc-error"></div>`,
buttonText: t('ErrorPopupCloseButton')
})}
</div>
<div id="popup-migration-container" class="popup-from-bottom">
${popup({
name: "migration",
standalone: true,
buttonOnly: true,
classes: ["small"],
header: {
title: t('NewDomainWelcomeTitle'),
emoji: emoji("😸", 78, 1, 1),
},
body: `<div id="desc-migration" class="desc-padding subtext desc-error">${t('NewDomainWelcome')}</div>`,
buttonText: t('ErrorPopupCloseButton')
})}
<div id="popup-backdrop-message" onclick="popup('message', 0)"></div>
</div>
<div id="popup-backdrop" onclick="hideAllPopups()"></div>
<div id="home" style="visibility:hidden">
${urgentNotice({
emoji: "🔗",
emoji: "👾",
text: t("UrgentFeatureUpdate71"),
visible: true,
action: "popup('about', 1, 'changelog')"
@ -551,20 +564,25 @@ export default function(obj) {
</div>
</body>
<script type="text/javascript">
const loc = {
noInternet: ` + "`" + t('ErrorNoInternet') + "`" + `,
noURLReturned: ` + "`" + t('ErrorNoUrlReturned') + "`" + `,
unknownStatus: ` + "`" + t('ErrorUnknownStatus') + "`" + `,
collapseHistory: ` + "`" + t('ChangelogPressToHide') + "`" + `,
pickerDefault: ` + "`" + t('MediaPickerTitle') + "`" + `,
pickerImages: ` + "`" + t('ImagePickerTitle') + "`" + `,
pickerImagesExpl: ` + "`" + t(`ImagePickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
pickerDefaultExpl: ` + "`" + t(`MediaPickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
featureErrorGeneric: ` + "`" + t('FeatureErrorGeneric') + "`" + `,
clipboardErrorNoPermission: ` + "`" + t('ClipboardErrorNoPermission') + "`" + `,
clipboardErrorFirefox: ` + "`" + t('ClipboardErrorFirefox') + "`" + `,
};
let apiURL = '${process.env.apiURL ? process.env.apiURL.slice(0, -1) : ''}';
const loc = ${webLoc(t,
[
'ErrorNoInternet',
'ErrorNoUrlReturned',
'ErrorUnknownStatus',
'ChangelogPressToHide',
'MediaPickerTitle',
'MediaPickerExplanationPhone',
'MediaPickerExplanationPC',
'ImagePickerTitle',
'ImagePickerExplanationPhone',
'ImagePickerExplanationPC',
'FeatureErrorGeneric',
'ClipboardErrorNoPermission',
'ClipboardErrorFirefox',
'DataTransferSuccess',
'DataTransferError'
])}
</script>
<script type="text/javascript" src="cobalt.js"></script>
</html>

View file

@ -8,6 +8,9 @@ export default function (inHost, inURL) {
url = url.split("?")[0].replace("www.", "");
url = `https://youtube.com/watch?v=${url.replace("https://youtube.com/live/", "")}`
}
if (url.includes('youtube.com/shorts/')) {
url = url.split('?')[0].replace('shorts/', 'watch?v=');
}
break;
case "youtu":
if (url.startsWith("https://youtu.be/")) {
@ -32,6 +35,11 @@ export default function (inHost, inURL) {
url = url.replace(url.split('/')[5], '')
}
break;
case "twitch":
if (url.includes('clips.twitch.tv')) {
url = url.split('?')[0].replace('clips.twitch.tv/', 'twitch.tv/_/clip/');
}
break;
}
return {
host: host,

View file

@ -19,10 +19,12 @@ import instagram from "./services/instagram.js";
import vine from "./services/vine.js";
import pinterest from "./services/pinterest.js";
import streamable from "./services/streamable.js";
import twitch from "./services/twitch.js";
import rutube from "./services/rutube.js";
export default async function (host, patternMatch, url, lang, obj) {
try {
let r, isAudioOnly = !!obj.isAudioOnly;
let r, isAudioOnly = !!obj.isAudioOnly, disableMetadata = !!obj.disableMetadata;
if (!testers[host]) return apiJSON(0, { t: errorUnsupported(lang) });
if (!(testers[host](patternMatch))) return apiJSON(0, { t: brokenLink(lang, host) });
@ -125,6 +127,20 @@ export default async function (host, patternMatch, url, lang, obj) {
isAudioOnly: isAudioOnly,
});
break;
case "twitch":
r = await twitch({
clipId: patternMatch["clip"] ? patternMatch["clip"] : false,
quality: obj.vQuality,
isAudioOnly: obj.isAudioOnly
});
break;
case "rutube":
r = await rutube({
id: patternMatch["id"],
quality: obj.vQuality,
isAudioOnly: isAudioOnly
});
break;
default:
return apiJSON(0, { t: errorUnsupported(lang) });
}
@ -134,7 +150,7 @@ export default async function (host, patternMatch, url, lang, obj) {
if (r.error) return apiJSON(0, { t: Array.isArray(r.error) ? loc(lang, r.error[0], r.error[1]) : loc(lang, r.error) });
return matchActionDecider(r, host, obj.aFormat, isAudioOnly, lang, isAudioMuted);
return matchActionDecider(r, host, obj.aFormat, isAudioOnly, lang, isAudioMuted, disableMetadata);
} catch (e) {
return apiJSON(0, { t: genericError(lang, host) })
}

View file

@ -2,17 +2,17 @@ import { audioIgnore, services, supportedAudio } from "../config.js";
import { apiJSON } from "../sub/utils.js";
import loc from "../../localization/manager.js";
export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, disableMetadata) {
let action,
responseType = 2,
defaultParams = {
u: r.urls,
service: host,
filename: r.filename,
fileMetadata: r.fileMetadata ? r.fileMetadata : false
fileMetadata: !disableMetadata ? r.fileMetadata : false
},
params = {}
if (r.isPhoto) action = "photo";
else if (r.picker) action = "picker"
else if (isAudioMuted) action = "muteVideo";
@ -55,7 +55,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
case "tiktok":
params = { type: "bridge" };
break;
case "vine":
case "instagram":
case "tumblr":

View file

@ -0,0 +1,30 @@
import HLS from 'hls-parser';
import { maxVideoDuration } from "../../config.js";
export default async function(obj) {
let quality = obj.quality === "max" ? "9000" : obj.quality;
let play = await fetch(`https://rutube.ru/api/play/options/${obj.id}/?no_404=true&referer&pver=v2`).then((r) => { return r.json() }).catch(() => { return false });
if (!play) return { error: 'ErrorCouldntFetch' };
if ("hls" in play.live_streams) return { error: 'ErrorLiveVideo' };
if (!play.video_balancer || play.detail) return { error: 'ErrorEmptyDownload' };
if (play.duration > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let m3u8 = await fetch(play.video_balancer.m3u8).then((r) => { return r.text() }).catch(() => { return false });
if (!m3u8) return { error: 'ErrorCouldntFetch' };
m3u8 = HLS.parse(m3u8).variants.sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
let bestQuality = m3u8[0];
if (Number(quality) < bestQuality.resolution.height) {
bestQuality = m3u8.find((i) => (Number(quality) === i["resolution"].height));
}
return {
urls: bestQuality.uri,
isM3U8: true,
audioFilename: `rutube_${play.id}_audio`,
filename: `rutube_${play.id}_${bestQuality.resolution.width}x${bestQuality.resolution.height}.mp4`
}
}

View file

@ -0,0 +1,76 @@
import { maxVideoDuration } from "../../config.js";
const gqlURL = "https://gql.twitch.tv/gql";
const clientIdHead = { "client-id": "kimne78kx3ncx6brgo4mv6wki5h1ko" };
export default async function (obj) {
let req_metadata = await fetch(gqlURL, {
method: "POST",
headers: clientIdHead,
body: JSON.stringify({
query: `{
clip(slug: "${obj.clipId}") {
broadcaster {
login
}
createdAt
curator {
login
}
durationSeconds
id
medium: thumbnailURL(width: 480, height: 272)
title
videoQualities {
quality
sourceURL
}
}
}`
})
}).then((r) => { return r.status === 200 ? r.json() : false; }).catch(() => { return false });
if (!req_metadata) return { error: 'ErrorCouldntFetch' };
let clipMetadata = req_metadata.data.clip;
if (clipMetadata.durationSeconds > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
if (!clipMetadata.videoQualities || !clipMetadata.broadcaster) return { error: 'ErrorEmptyDownload' };
let req_token = await fetch(gqlURL, {
method: "POST",
headers: clientIdHead,
body: JSON.stringify([
{
"operationName": "VideoAccessToken_Clip",
"variables": {
"slug": obj.clipId
},
"extensions": {
"persistedQuery": {
"version": 1,
"sha256Hash": "36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11"
}
}
}
])
}).then((r) => { return r.status === 200 ? r.json() : false; }).catch(() => { return false });
if (!req_token) return { error: 'ErrorCouldntFetch' };
let formats = clipMetadata.videoQualities;
let format = formats.find(f => f.quality === obj.quality) || formats[0];
return {
type: "bridge",
urls: `${format.sourceURL}?${new URLSearchParams({
sig: req_token[0].data.clip.playbackAccessToken.signature,
token: req_token[0].data.clip.playbackAccessToken.value
})}`,
fileMetadata: {
title: clipMetadata.title,
artist: `Twitch Clip by @${clipMetadata.broadcaster.login}, clipped by @${clipMetadata.curator.login}`,
},
filename: `twitchclip_${clipMetadata.id}_${format.quality}p.mp4`,
audioFilename: `twitchclip_${clipMetadata.id}_audio`
}
}

View file

@ -22,7 +22,7 @@
"enabled": true
},
"youtube": {
"alias": "youtube videos & shorts & music",
"alias": "youtube videos, shorts & music",
"patterns": ["watch?v=:id", "embed/:id"],
"bestAudio": "opus",
"enabled": true
@ -32,7 +32,7 @@
"enabled": true
},
"tiktok": {
"alias": "tiktok videos & photos & audio",
"alias": "tiktok videos, photos & audio",
"patterns": [":user/video/:postId", ":id", "t/:id"],
"audioFormats": ["best", "m4a", "mp3"],
"enabled": true
@ -75,6 +75,18 @@
"alias": "streamable.com",
"patterns": [":id", "o/:id", "e/:id", "s/:id"],
"enabled": true
},
"twitch": {
"alias": "twitch clips",
"tld": "tv",
"patterns": [":channel/clip/:clip"],
"enabled": true
},
"rutube": {
"alias": "rutube videos",
"tld": "ru",
"patterns": ["video/:id", "play/embed/:id"],
"enabled": true
}
}
}
}

View file

@ -22,16 +22,20 @@ export const testers = {
|| (patternMatch["id"] && patternMatch["id"].length < 21 && patternMatch["user"] && patternMatch["user"].length <= 32)),
"vimeo": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length <= 11)),
"soundcloud": (patternMatch) => (patternMatch["author"]?.length <= 25 && patternMatch["song"]?.length <= 255)
|| (patternMatch["shortLink"] && patternMatch["shortLink"].length <= 32),
"instagram": (patternMatch) => (patternMatch.postId?.length <= 12)
|| (patternMatch.username?.length <= 30 && patternMatch.storyId?.length <= 24),
"vine": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 12),
"pinterest": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 128),
"streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length === 6)
"streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length === 6),
"twitch": (patternMatch) => ((patternMatch["channel"] && patternMatch["clip"] && patternMatch["clip"].length <= 100)),
"rutube": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length === 32)),
}

View file

@ -70,7 +70,7 @@ function setup() {
})
break;
case 'web':
console.log(Bright("\nAwesome! What's the domain this web app instance will be running on? (localhost)\nExample: co.wukko.me"));
console.log(Bright("\nAwesome! What's the domain this web app instance will be running on? (localhost)\nExample: cobalt.tools"));
rl.question(q, webURL => {
ob['webURL'] = `http://localhost:9001/`;

View file

@ -16,7 +16,8 @@ export async function streamDefault(streamInfo, res) {
res.setHeader('Content-disposition', `attachment; filename="${streamInfo.isAudioOnly ? `${streamInfo.filename}.${streamInfo.audioFormat}` : regFilename}"`);
const { body: stream, headers } = await request(streamInfo.urls, {
headers: { 'user-agent': genericUserAgent }
headers: { 'user-agent': genericUserAgent },
maxRedirections: 16
});
res.setHeader('content-type', headers['content-type']);
@ -33,7 +34,9 @@ export async function streamLiveRender(streamInfo, res) {
try {
if (streamInfo.urls.length !== 2) return fail(res);
let { body: audio } = await request(streamInfo.urls[1]);
let { body: audio } = await request(streamInfo.urls[1], {
maxRedirections: 16
});
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1],
args = [
@ -149,7 +152,7 @@ export function streamVideoOnly(streamInfo, res) {
'-c', 'copy'
]
if (streamInfo.mute) args.push('-an');
if (streamInfo.service === "vimeo") args.push('-bsf:a', 'aac_adtstoasc');
if (streamInfo.service === "vimeo" || streamInfo.service === "rutube") args.push('-bsf:a', 'aac_adtstoasc');
if (format === "mp4") args.push('-movflags', 'faststart+frag_keyframe+empty_moov');
args.push('-f', format, 'pipe:3');
const ffmpegProcess = spawn(ffmpeg, args, {

View file

@ -6,7 +6,7 @@ const apiVar = {
vQuality: ["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"],
aFormat: ["best", "mp3", "ogg", "wav", "opus"]
},
booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash"]
booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash", "disableMetadata"]
}
const forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '=='];
const forbiddenCharsString = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '='];
@ -50,7 +50,7 @@ export function metadataManager(obj) {
return commands;
}
export function cleanURL(url, host) {
switch(host) {
switch (host) {
case "vk":
url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0];
break;
@ -70,9 +70,6 @@ export function cleanURL(url, host) {
url = url.replaceAll(forbiddenChars[i], '')
}
url = url.replace('https//', 'https://')
if (url.includes('youtube.com/shorts/')) {
url = url.split('?')[0].replace('shorts/', 'watch?v=');
}
return url.slice(0, 128)
}
export function cleanString(string) {
@ -101,13 +98,14 @@ export function checkJSONPost(obj) {
isNoTTWatermark: false,
isTTFullAudio: false,
isAudioMuted: false,
disableMetadata: false,
dubLang: false,
vimeoDash: false
}
try {
let objKeys = Object.keys(obj);
if (!(objKeys.length <= 9 && obj.url)) return false;
let defKeys = Object.keys(def);
if (objKeys.length > defKeys.length + 1 || !obj.url) return false;
for (let i in objKeys) {
if (String(objKeys[i]) !== "url" && defKeys.includes(objKeys[i])) {

View file

@ -97,7 +97,7 @@
}
}, {
"name": "retweeted video",
"url": "https://twitter.com/hugekiwinuts/status/1618671150829309953?s=46&t=gItGzgwGQQJJaJrO6qc1Pg",
"url": "https://twitter.com/uwukko/status/1696901469633421344",
"params": {},
"expected": {
"code": 200,
@ -1070,5 +1070,89 @@
"code": 400,
"status": "error"
}
}],
"twitch": [{
"name": "clip",
"url": "https://twitch.tv/rtgame/clip/TubularInventiveSardineCorgiDerp-PM47mJQQ2vsL5B5G",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "clip (isAudioOnly)",
"url": "https://twitch.tv/rtgame/clip/TubularInventiveSardineCorgiDerp-PM47mJQQ2vsL5B5G",
"params": {
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "clip (isAudioMuted)",
"url": "https://twitch.tv/rtgame/clip/TubularInventiveSardineCorgiDerp-PM47mJQQ2vsL5B5G",
"params": {
"isAudioMuted": true
},
"expected": {
"code": 200,
"status": "stream"
}
}],
"rutube": [{
"name": "regular video",
"url": "https://rutube.ru/video/b2f6c27649907c2fde0af411b03825eb/",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "vertical video (isAudioMuted)",
"url": "https://rutube.ru/video/18a281399b96f9184c647455a86f6724/",
"params": {
"isAudioMuted": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "russian region lock",
"url": "https://rutube.ru/video/b521653b4f71ece57b8ff54e57ca9b82/",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "vertical video",
"url": "https://rutube.ru/video/18a281399b96f9184c647455a86f6724/",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "vertical video (isAudioOnly)",
"url": "https://rutube.ru/video/18a281399b96f9184c647455a86f6724/",
"params": {
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "vertical video (isAudioMuted)",
"url": "https://rutube.ru/video/18a281399b96f9184c647455a86f6724/",
"params": {
"isAudioMuted": true
},
"expected": {
"code": 200,
"status": "stream"
}
}]
}
}