Merge branch 'current' into feat/twitch

This commit is contained in:
wukko 2023-09-16 15:40:09 +06:00 committed by GitHub
commit ff9d48740d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
84 changed files with 4498 additions and 2136 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: https://boosty.to/wukko
custom: https://boosty.to/wukko/donate

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 }}

13
.gitignore vendored
View file

@ -5,11 +5,18 @@ package-lock.json
# secrets
.env
# esbuild
min
# page build
min
build
# stuff i already made but delayed
future
# docker
docker-compose.yml
# vscode
.vscode
# cookie file
cookies.json

View file

@ -1,13 +1,13 @@
# cobalt
Best way to save what you love.
Main instance: [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")
[![Crowdin](https://badges.crowdin.net/cobalt/localized.svg)](https://crowdin.com/project/cobalt) [![DeepSource](https://deepsource.io/gh/wukko/cobalt.svg/?label=active+issues&token=MsmsJ9zUOKwcQor0yaiFot84)](https://deepsource.io/gh/wukko/cobalt/?ref=repository-badge) [![DeepSource](https://deepsource.io/gh/wukko/cobalt.svg/?label=resolved+issues&token=MsmsJ9zUOKwcQor0yaiFot84)](https://deepsource.io/gh/wukko/cobalt/?ref=repository-badge)
[![DeepSource](https://deepsource.io/gh/wukko/cobalt.svg/?label=active+issues&token=MsmsJ9zUOKwcQor0yaiFot84)](https://deepsource.io/gh/wukko/cobalt/?ref=repository-badge)
## What's cobalt?
cobalt is a social and media platform downloader that doesn't piss you off.
cobalt is social and media platform downloader that doesn't piss you off.
It's fast, friendly, and doesn't have any bullshit that modern web is filled with: no ads, trackers, or analytics.
Paste the link, get the video, move on. It's that simple. Just how it should be.
@ -16,40 +16,31 @@ Paste the link, get the video, move on. It's that simple. Just how it should be.
| Service | Video + Audio | Only audio | Only video | Additional notes or features |
| -------- | :---: | :---: | :---: | :----- |
| bilibili.com | ✅ | ✅ | ✅ | |
| Instagram | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media posts. |
| Instagram | ✅ | ✅ | ✅ | Supports photos and videos, lets you pick what to save from multi-media posts. |
| Instagram Reels | ✅ | ✅ | ✅ | |
| Pinterest | ✅ | ✅ | ✅ | Support for videos and stories. |
| Reddit | ✅ | ✅ | ✅ | Support for GIFs and videos. |
| 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 | ✅ | ✅ | ✅ | |
| Twitter | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media tweets. |
| Twitter Spaces | | ✅ | | Audio metadata with all participants and other info. |
| Twitch | ✅ | ✅ | ✅ | |
| Tumblr | ✅ | ✅ | ✅ | Support for audio file downloads. |
| Twitch Clips/VODs | ✅ | ✅ | ✅ | |
| Twitter/X * | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media tweets. |
| Vimeo | ✅ | ✅ | ✅ | Audio downloads are only available for dash files. |
| Vine Archive | ✅ | ✅ | ✅ | |
| VK Videos | ✅ | ❌ | ❌ | |
| VK Clips | ✅ | ❌ | ❌ | |
| YouTube Videos & Shorts | ✅ | ✅ | ✅ | Support for 8K, 4K, HDR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. |
| YouTube Videos & Shorts | ✅ | ✅ | ✅ | Support for 8K, 4K, HDR, VR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. |
| YouTube Music | | ✅ | | Audio metadata. |
This list is not final and keeps expanding over time, make sure to check it once in a while!
*Reliability of downloads from Twitter is questionable due to its current management.
## cobalt API
cobalt has an open API that you can use in your projects for **free**.
It's easy and straightforward to use, [check out the docs](https://github.com/wukko/cobalt/blob/current/docs/API.md) and see for yourself.
## How to contribute translations
You can translate cobalt to any language you want on [cobalt's Crowdin](https://crowdin-co.wukko.me/). Feel free to ignore QA errors if you think you know better. If you don't see a language you want to translate cobalt to, open an issue, and I'll add it to Crowdin.
### Translation guidelines:
- Text is **ALWAYS** stylized as **lowercase** unless it's STRESSED LIKE THIS or is an internal value like `{ContactLink}` or `{appName}`.
- Example: "`this is a live video, i am yet to learn how to look into future. wait for the stream to finish and try again!`".
*Notice how **everything is lowercase**, no matter the punctuation marks? Yes, that's cobalt's style and you have to follow it.*
- Avoid extremely formal language, leave it for big and classy tech companies. Use informal language wherever possible.
- You can (and should) rephrase sentences as long as they keep the same sense and send the same message as original.
- Do **NOT** use offensive or explicit vocabulary.
- Check if there are issues in UI with your localization and optimize it accordingly. If impossible, open an issue.
- Be nice.
It's easy and straightforward to use, [check out the docs](https://github.com/wukko/cobalt/blob/current/docs/API.md) and see for yourself.
Feel free to use the main API instance ([co.wuk.sh](https://co.wuk.sh/)) in your projects.
## Host an instance yourself
### Requirements
@ -63,6 +54,8 @@ Setup script installs all needed `npm` dependencies, but you have to install `No
3. Run cobalt via `npm start`
4. Done.
You need to host API and web app separately since v.6.0. Setup script will help you with that!
### Ubuntu 22.04+ workaround
`nscd` needs to be installed and running so that the `ffmpeg-static` binary can resolve DNS ([#101](https://github.com/wukko/cobalt/issues/101#issuecomment-1494822258)):
@ -72,13 +65,8 @@ sudo service nscd start
```
### Docker
It's also possible to run cobalt via Docker, but you **need** to set all environment variables yourself:
| Variable | Description | Example |
| -------- | :--- | :--- |
| `selfURL` | Instance URL | `http://localhost:9000/` or `https://co.wukko.me/` or etc |
| `port` | Instance port | `9000` |
| `cors` | CORS toggle | `0` |
It's also possible to run cobalt via Docker. I *highly* recommend using Docker compose.
Check out the [example compose file](https://github.com/wukko/cobalt/blob/current/docker-compose.example.yml) and alter it for your needs.
## Disclaimer
cobalt is my passion project, so update schedule depends solely on my free time, motivation, and mood.

View file

@ -0,0 +1,66 @@
version: '3.5'
services:
cobalt-api:
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
ports:
- 9000:9000/tcp
# if you're using a reverse proxy, uncomment the next line:
#- 127.0.0.1:9000:9000
environment:
- apiPort=9000
# replace apiURL with your instance's target url in same format
- apiURL=https://co.wuk.sh/
# 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
# 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:
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
ports:
- 9001:9001/tcp
# if you're using a reverse proxy, uncomment the next line:
#- 127.0.0.1:9001:9001
environment:
- webPort=9001
# replace webURL with your instance's target url in same format
- 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

@ -1,5 +1,12 @@
# cobalt API Documentation
This document provides info about methods and acceptable variables for all cobalt API requests.<br>
```
⚠️ Main API instance has moved to https://co.wuk.sh/
Make sure your projects use the correct API domain.
```
## POST: ``/api/json``
Main processing endpoint.<br>
@ -7,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 |
@ -57,7 +65,22 @@ On-demand website element loading. Currently used only for older changelogs.<br>
| blockId | ``0`` | Block ID to be rendered on the server. |
### Response Body Variables
| key | type | variables |
|:-----------|:-------|:-----------------------------|
| status | string | ``error / success`` |
| text | string | Error text or rendered block |
| key | type | variables |
|:-------|:-------|:-----------------------------|
| status | string | ``error / success`` |
| text | string | Error text or rendered block |
## GET: ``/api/serverInfo``
Returns current basic server info.<br>
Response Body Type: ``application/json``
### Response Body Variables
| key | type | variables |
|:----------|:-------|:------------------|
| version | string | cobalt version |
| commit | string | Git commit |
| branch | string | Git branch |
| name | string | Server name |
| url | string | Server url |
| cors | string | CORS status |
| startTime | string | Server start time |

View file

@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
"version": "5.7",
"version": "7.4",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
@ -11,7 +11,8 @@
"scripts": {
"start": "node src/cobalt",
"setup": "node src/modules/setup",
"test": "node src/test/test"
"test": "node src/test/test",
"build": "node src/modules/buildStatic"
},
"repository": {
"type": "git",
@ -29,11 +30,11 @@
"express": "^4.18.1",
"express-rate-limit": "^6.3.0",
"ffmpeg-static": "^5.1.0",
"got": "^12.1.0",
"nanoid": "^4.0.2",
"node-cache": "^5.1.2",
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",
"xml-js": "^1.6.11",
"youtubei.js": "^5.1.0"
"youtubei.js": "^5.4.0"
}
}

View file

@ -1,199 +1,35 @@
import "dotenv/config";
import express from "express";
import cors from "cors";
import rateLimit from "express-rate-limit";
import { randomBytes } from "crypto";
const ipSalt = randomBytes(64).toString('hex');
import { Bright, Green, Red } from "./modules/sub/consoleText.js";
import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js";
import { loadLoc } from "./localization/manager.js";
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const gitCommit = shortCommit();
const gitBranch = getCurrentBranch();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename).slice(0, -4); // go up another level (get rid of src/)
const __dirname = path.dirname(__filename).slice(0, -4);
import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js";
import { appName, genericUserAgent, version } from "./modules/config.js";
import { getJSON } from "./modules/api.js";
import { apiJSON, checkJSONPost, getIP, languageCode } from "./modules/sub/utils.js";
import { Bright, Cyan, Green, Red } from "./modules/sub/consoleText.js";
import stream from "./modules/stream/stream.js";
import loc from "./localization/manager.js";
import { buildFront } from "./modules/build.js";
import { changelogHistory } from "./modules/pageRender/onDemand.js";
import { sha256 } from "./modules/sub/crypto.js";
import findRendered from "./modules/pageRender/findRendered.js";
import { celebrationsEmoji } from "./modules/pageRender/elements.js";
app.disable('x-powered-by');
if (process.env.selfURL && process.env.port) {
const commitHash = shortCommit();
const branch = getCurrentBranch();
const app = express();
await loadLoc();
app.disable('x-powered-by');
const apiMode = process.env.apiURL && process.env.apiPort && !((process.env.webURL && process.env.webPort) || (process.env.selfURL && process.env.port));
const webMode = process.env.webURL && process.env.webPort && !((process.env.apiURL && process.env.apiPort) || (process.env.selfURL && process.env.port));
const corsConfig = process.env.cors === '0' ? { origin: process.env.selfURL, optionsSuccessStatus: 200 } : {};
const apiLimiter = rateLimit({
windowMs: 60000,
max: 25,
standardHeaders: false,
legacyHeaders: false,
keyGenerator: (req, res) => sha256(getIP(req), ipSalt),
handler: (req, res, next, opt) => {
res.status(429).json({ "status": "error", "text": loc(languageCode(req), 'ErrorRateLimit') });
return;
}
});
const apiLimiterStream = rateLimit({
windowMs: 60000,
max: 28,
standardHeaders: false,
legacyHeaders: false,
keyGenerator: (req, res) => sha256(getIP(req), ipSalt),
handler: (req, res, next, opt) => {
res.status(429).json({ "status": "error", "text": loc(languageCode(req), 'ErrorRateLimit') });
return;
}
});
await buildFront(commitHash, branch);
app.use('/api/:type', cors(corsConfig));
app.use('/api/json', apiLimiter);
app.use('/api/stream', apiLimiterStream);
app.use('/api/onDemand', apiLimiter);
app.use('/', express.static('./min'));
app.use('/', express.static('./src/front'));
app.use((req, res, next) => {
try { decodeURIComponent(req.path) } catch (e) { return res.redirect('/') }
next();
});
app.use((req, res, next) => {
if (req.header("user-agent") && req.header("user-agent").includes("Trident")) res.destroy();
next();
});
app.use('/api/json', express.json({
verify: (req, res, buf) => {
try {
JSON.parse(buf);
if (buf.length > 720) throw new Error();
if (String(req.header('Content-Type')) !== "application/json") {
res.status(400).json({ 'status': 'error', 'text': 'invalid content type header' });
return;
}
if (String(req.header('Accept')) !== "application/json") {
res.status(400).json({ 'status': 'error', 'text': 'invalid accept header' });
return;
}
} catch(e) {
res.status(400).json({ 'status': 'error', 'text': 'invalid json body.' });
return;
}
}
}));
app.post('/api/json', async (req, res) => {
try {
let ip = sha256(getIP(req), ipSalt);
let lang = languageCode(req);
let j = apiJSON(0, { t: "Bad request" });
try {
let request = req.body;
if (request.url) {
request.dubLang = request.dubLang ? lang : false;
let chck = checkJSONPost(request);
if (chck) chck["ip"] = ip;
j = chck ? await getJSON(chck["url"], lang, chck) : apiJSON(0, { t: loc(lang, 'ErrorCouldntFetch') });
} else {
j = apiJSON(0, { t: loc(lang, 'ErrorNoLink') });
}
} catch (e) {
j = apiJSON(0, { t: loc(lang, 'ErrorCantProcess') });
}
res.status(j.status).json(j.body);
return;
} catch (e) {
res.destroy();
return
}
});
app.get('/api/:type', (req, res) => {
try {
let ip = sha256(getIP(req), ipSalt);
switch (req.params.type) {
case 'stream':
if (req.query.p) {
res.status(200).json({ "status": "continue" });
return;
} else if (req.query.t && req.query.h && req.query.e) {
stream(res, ip, req.query.t, req.query.h, req.query.e);
} else {
let j = apiJSON(0, { t: "no stream id" })
res.status(j.status).json(j.body);
return;
}
break;
case 'onDemand':
if (req.query.blockId) {
let blockId = req.query.blockId.slice(0, 3);
let r, j;
switch(blockId) {
case "0": // changelog history
r = changelogHistory();
j = r ? apiJSON(3, { t: r }) : apiJSON(0, { t: "couldn't render this block" })
break;
case "1": // celebrations emoji
r = celebrationsEmoji();
j = r ? apiJSON(3, { t: r }) : false
break;
default:
j = apiJSON(0, { t: "couldn't find a block with this id" })
break;
}
if (j.body) {
res.status(j.status).json(j.body)
} else {
res.status(204).end()
}
} else {
let j = apiJSON(0, { t: "no block id" });
res.status(j.status).json(j.body)
}
break;
default:
let j = apiJSON(0, { t: "unknown response type" })
res.status(j.status).json(j.body);
break;
}
} catch (e) {
res.status(500).json({ 'status': 'error', 'text': loc(languageCode(req), 'ErrorCantProcess') });
return;
}
});
app.get("/api", (req, res) => {
res.redirect('/api/json')
});
app.get("/status", (req, res) => {
res.status(200).end()
});
app.get("/", (req, res) => {
res.sendFile(`${__dirname}/${findRendered(languageCode(req), req.header('user-agent') ? req.header('user-agent') : genericUserAgent)}`);
});
app.get("/favicon.ico", (req, res) => {
res.redirect('/icons/favicon.ico');
});
app.get("/*", (req, res) => {
res.redirect('/')
});
app.listen(process.env.port, () => {
let startTime = new Date();
console.log(`\n${Cyan(appName)} ${Bright(`v.${version}-${commitHash} (${branch})`)}\nStart time: ${Bright(`${startTime.toUTCString()} (${Math.floor(new Date().getTime())})`)}\n\nURL: ${Cyan(`${process.env.selfURL}`)}\nPort: ${process.env.port}\n`)
})
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 hasn't been configured yet or configuration is invalid.\n`) + Bright(`please run the setup script to fix this: `) + Green(`npm run setup`));
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,7 +1,7 @@
{
"streamLifespan": 120000,
"maxVideoDuration": 10800000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
"streamLifespan": 20000,
"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/",
@ -14,6 +14,10 @@
"mastodon": {
"url": "https://wetdry.world/@cobalt",
"handle": "@cobalt@wetdry.world"
},
"discord": {
"url": "https://discord.gg/pQPt8HBUPu",
"handle": "cobalt community server"
}
}
},
@ -21,14 +25,15 @@
"crypto": {
"bitcoin": "bc1q59jyyjvrzj4c22rkk3ljeecq6jmpyscgz9spnd",
"ethereum": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
"litecoin": "ltc1qvp0xhrk2m7pa6p6z844qcslfyxv4p3vf95rhna"
"litecoin": "ltc1qvp0xhrk2m7pa6p6z844qcslfyxv4p3vf95rhna",
"monero": "4B1SNB6s8Pq1hxjNeKPEe8Qa8EP3zdL16Sqsa7QDoJcUecKQzEj9BMxWnEnTGu12doKLJBKRDUqnn6V9qfSdXpXi3Nw5Uod"
},
"links": {
"boosty": "https://boosty.to/wukko"
"boosty": "https://boosty.to/wukko/donate"
}
},
"links": {
"saveToGalleryShortcut": "https://www.icloud.com/shortcuts/6d4fe6e5bade4150b8759ce20720c7a3"
"saveToGalleryShortcut": "https://www.icloud.com/shortcuts/b401917928fd407daf1db0fd07eb7e78"
},
"celebrations": {
"01-01": "🎄",

178
src/core/api.js Normal file
View file

@ -0,0 +1,178 @@
import cors from "cors";
import rateLimit from "express-rate-limit";
import { randomBytes } from "crypto";
const ipSalt = randomBytes(64).toString('hex');
import { version } from "../modules/config.js";
import { getJSON } from "../modules/api.js";
import { apiJSON, checkJSONPost, getIP, languageCode } from "../modules/sub/utils.js";
import { Bright, Cyan } from "../modules/sub/consoleText.js";
import stream from "../modules/stream/stream.js";
import loc from "../localization/manager.js";
import { sha256 } from "../modules/sub/crypto.js";
import { verifyStream } from "../modules/stream/manage.js";
export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
const corsConfig = process.env.cors === '0' ? {
origin: process.env.webURL,
optionsSuccessStatus: 200
} : {};
const apiLimiter = rateLimit({
windowMs: 60000,
max: 20,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => sha256(getIP(req), ipSalt),
handler: (req, res, next, opt) => {
return res.status(429).json({
"status": "rate-limit",
"text": loc(languageCode(req), 'ErrorRateLimit')
});
}
});
const apiLimiterStream = rateLimit({
windowMs: 60000,
max: 25,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => sha256(getIP(req), ipSalt),
handler: (req, res, next, opt) => {
return res.status(429).json({
"status": "rate-limit",
"text": loc(languageCode(req), 'ErrorRateLimit')
});
}
});
const startTime = new Date();
const startTimestamp = Math.floor(startTime.getTime());
app.use('/api/:type', cors(corsConfig));
app.use('/api/json', apiLimiter);
app.use('/api/stream', apiLimiterStream);
app.use('/api/onDemand', apiLimiter);
app.use((req, res, next) => {
try { decodeURIComponent(req.path) } catch (e) { return res.redirect('/') }
next();
});
app.use('/api/json', express.json({
verify: (req, res, buf) => {
let acceptCon = String(req.header('Accept')) === "application/json";
if (acceptCon) {
if (buf.length > 720) throw new Error();
JSON.parse(buf);
} else {
throw new Error();
}
}
}));
// handle express.json errors properly (https://github.com/expressjs/express/issues/4065)
app.use('/api/json', (err, req, res, next) => {
let errorText = "invalid json body";
let acceptCon = String(req.header('Accept')) !== "application/json";
if (err || acceptCon) {
if (acceptCon) errorText = "invalid accept header";
return res.status(400).json({
status: "error",
text: errorText
});
} else {
next();
}
});
app.post('/api/json', async (req, res) => {
try {
let lang = languageCode(req);
let j = apiJSON(0, { t: "bad request" });
try {
let contentCon = String(req.header('Content-Type')) === "application/json";
let request = req.body;
if (contentCon && request.url) {
request.dubLang = request.dubLang ? lang : false;
let chck = checkJSONPost(request);
if (!chck) throw new Error();
j = await getJSON(chck["url"], lang, chck);
} else {
j = apiJSON(0, {
t: !contentCon ? "invalid content type header" : loc(lang, 'ErrorNoLink')
});
}
} catch (e) {
j = apiJSON(0, { t: loc(lang, 'ErrorCantProcess') });
}
return res.status(j.status).json(j.body);
} catch (e) {
return res.destroy();
}
});
app.get('/api/:type', (req, res) => {
try {
switch (req.params.type) {
case 'stream':
if (req.query.t && req.query.h && req.query.e && req.query.t.toString().length === 21
&& req.query.h.toString().length === 64 && req.query.e.toString().length === 13) {
let streamInfo = verifyStream(req.query.t, req.query.h, req.query.e);
if (streamInfo.error) {
return res.status(streamInfo.status).json(apiJSON(0, { t: streamInfo.error }).body);
}
if (req.query.p) {
return res.status(200).json({
status: "continue"
});
}
return stream(res, streamInfo);
} else {
let j = apiJSON(0, {
t: "stream token, hmac, or expiry timestamp is missing"
})
return res.status(j.status).json(j.body);
}
case 'serverInfo':
return res.status(200).json({
version: version,
commit: gitCommit,
branch: gitBranch,
name: process.env.apiName ? process.env.apiName : "unknown",
url: process.env.apiURL,
cors: process.env.cors && process.env.cors === "0" ? 0 : 1,
startTime: `${startTimestamp}`
});
default:
let j = apiJSON(0, {
t: "unknown response type"
})
return res.status(j.status).json(j.body);
}
} catch (e) {
return res.status(500).json({
status: "error",
text: loc(languageCode(req), 'ErrorCantProcess')
});
}
});
app.get('/api/status', (req, res) => {
res.status(200).end()
});
app.get('/favicon.ico', (req, res) => {
res.sendFile(`${__dirname}/src/front/icons/favicon.ico`)
});
app.get('/*', (req, res) => {
res.redirect('/api/json')
});
app.listen(process.env.apiPort, () => {
console.log(`\n` +
`${Cyan("cobalt")} API ${Bright(`v.${version}-${gitCommit} (${gitBranch})`)}\n` +
`Start time: ${Bright(`${startTime.toUTCString()} (${startTimestamp})`)}\n\n` +
`URL: ${Cyan(`${process.env.apiURL}`)}\n` +
`Port: ${process.env.apiPort}\n`
)
});
}

87
src/core/web.js Normal file
View file

@ -0,0 +1,87 @@
import { genericUserAgent, version } from "../modules/config.js";
import { apiJSON, languageCode } from "../modules/sub/utils.js";
import { Bright, Cyan } from "../modules/sub/consoleText.js";
import { buildFront } from "../modules/build.js";
import findRendered from "../modules/pageRender/findRendered.js";
import { celebrationsEmoji } from "../modules/pageRender/elements.js";
import { changelogHistory } from "../modules/pageRender/onDemand.js";
export async function runWeb(express, app, gitCommit, gitBranch, __dirname) {
const startTime = new Date();
const startTimestamp = Math.floor(startTime.getTime());
await buildFront(gitCommit, gitBranch);
app.use('/', express.static('./build/min'));
app.use('/', express.static('./src/front'));
app.use((req, res, next) => {
try { decodeURIComponent(req.path) } catch (e) { return res.redirect('/') }
next();
});
app.get('/onDemand', (req, res) => {
try {
if (req.query.blockId) {
let blockId = req.query.blockId.slice(0, 3);
let r, j;
switch(blockId) {
// changelog history
case "0":
r = changelogHistory();
j = r ? apiJSON(3, { t: r }) : apiJSON(0, {
t: "couldn't render this block, please try again!"
})
break;
// celebrations emoji
case "1":
r = celebrationsEmoji();
j = r ? apiJSON(3, { t: r }) : false
break;
default:
j = apiJSON(0, {
t: "couldn't find a block with this id"
})
break;
}
if (j.body) {
return res.status(j.status).json(j.body);
} else {
return res.status(204).end();
}
} else {
return res.status(400).json({
status: "error",
text: "couldn't render this block, please try again!"
});
}
} catch (e) {
return res.status(400).json({
status: "error",
text: "couldn't render this block, please try again!"
})
}
});
app.get("/status", (req, res) => {
return res.status(200).end()
});
app.get("/", (req, res) => {
return res.sendFile(`${__dirname}/${findRendered(languageCode(req), req.header('user-agent') ? req.header('user-agent') : genericUserAgent)}`)
});
app.get("/favicon.ico", (req, res) => {
return res.sendFile(`${__dirname}/src/front/icons/favicon.ico`)
});
app.get("/*", (req, res) => {
return res.redirect('/')
});
app.listen(process.env.webPort, () => {
console.log(`\n` +
`${Cyan("cobalt")} WEB ${Bright(`v.${version}-${gitCommit} (${gitBranch})`)}\n` +
`Start time: ${Bright(`${startTime.toUTCString()} (${startTimestamp})`)}\n\n` +
`URL: ${Cyan(`${process.env.webURL}`)}\n` +
`Port: ${process.env.webPort}\n`
)
})
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,12 @@
const version = 36;
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
const isMobile = ua.match("android") || ua.match("iphone os");
const version = 26;
const isSafari = ua.match("safari/");
const isFirefox = ua.match("firefox/");
const isOldFirefox = ua.match("firefox/") && ua.split("firefox/")[1].split('.')[0] < 103;
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>`;
@ -14,15 +19,30 @@ const switchers = {
"vimeoDash": ["false", "true"],
"audioMode": ["false", "true"]
};
const checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio"];
const checkboxes = [
"alwaysVisibleButton",
"disableChangelog",
"downloadPopup",
"disableTikTokWatermark",
"fullTikTokAudio",
"muteAudio",
"reduceTransparency",
"disableAnimations",
"disableMetadata",
];
const exceptions = { // used for mobile devices
"vQuality": "720"
};
const bottomPopups = ["error", "download"]
const apiURL = '';
const pageQuery = new URLSearchParams(window.location.search);
let store = {};
function changeAPI(url) {
apiURL = url;
return true
}
function eid(id) {
return document.getElementById(id)
}
@ -109,14 +129,18 @@ function detectColorScheme() {
function changeTab(evnt, tabId, tabClass) {
let tabcontent = document.getElementsByClassName(`tab-content-${tabClass}`);
let tablinks = document.getElementsByClassName(`tab-${tabClass}`);
for (let i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
tabcontent[i].dataset.enabled = "false";
}
for (let i = 0; i < tablinks.length; i++) {
tablinks[i].dataset.enabled = "false";
}
eid(tabId).style.display = "block";
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");
}
@ -154,16 +178,20 @@ function notificationCheck(type) {
function hideAllPopups() {
let filter = document.getElementsByClassName('popup');
for (let i = 0; i < filter.length; i++) {
filter[i].style.visibility = "hidden";
filter[i].classList.remove("visible");
}
eid("popup-backdrop").classList.remove("visible");
store.isPopupOpen = false;
// clear the picker
eid("picker-holder").innerHTML = '';
eid("picker-download").href = '/';
eid("picker-download").style.visibility = "hidden";
eid("popup-backdrop").style.visibility = "hidden";
eid("picker-download").classList.remove("visible");
}
function popup(type, action, text) {
if (action === 1) {
hideAllPopups(); // hide the previous popup before showing a new one
store.isPopupOpen = true;
switch (type) {
case "about":
let tabId = sGet("seenAbout") ? "changelog" : "about";
@ -185,31 +213,40 @@ function popup(type, action, text) {
case "picker":
switch (text.type) {
case "images":
eid("picker-title").innerHTML = loc.pickerImages;
eid("picker-subtitle").innerHTML = loc.pickerImagesExpl;
if (!eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.add("scrollable");
if (eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.remove("various");
eid("picker-title").innerHTML = loc.ImagePickerTitle;
eid("picker-subtitle").innerHTML = isMobile ? loc.ImagePickerExplanationPhone : loc.ImagePickerExplanationPC;
eid("picker-holder").classList.remove("various");
eid("picker-download").href = text.audio;
eid("picker-download").style.visibility = "visible"
eid("picker-download").classList.add("visible");
for (let i in text.arr) {
eid("picker-holder").innerHTML += `<a class="picker-image-container"><img class="picker-image" src="${text.arr[i]["url"]}" onerror="this.parentNode.style.display='none'"></img></a>`
eid("picker-holder").innerHTML +=
`<a class="picker-image-container" ${
isIOS ? `onClick="share('${text.arr[i]["url"]}')"` : `href="${text.arr[i]["url"]}" target="_blank"`
}>` +
`<img class="picker-image" src="${text.arr[i]["url"]}" onerror="this.parentNode.style.display='none'"></img>` +
`</a>`
}
break;
default:
eid("picker-title").innerHTML = loc.pickerDefault;
eid("picker-subtitle").innerHTML = loc.pickerDefaultExpl;
if (eid("popup-picker").classList.contains("scrollable")) eid("popup-picker").classList.remove("scrollable");
if (!eid("picker-holder").classList.contains("various")) eid("picker-holder").classList.add("various");
eid("picker-title").innerHTML = loc.MediaPickerTitle;
eid("picker-subtitle").innerHTML = isMobile ? loc.MediaPickerExplanationPhone : loc.MediaPickerExplanationPC;
eid("picker-holder").classList.add("various");
for (let i in text.arr) {
let s = text.arr[i], item;
switch (s.type) {
case "video":
item = `<a class="picker-various-container" href="${text.arr[i]["url"]}" target="_blank"><div class="picker-element-name">VIDEO ${Number(i)+1}</div><div class="imageBlock"></div><img class="picker-image" src="${text.arr[i]["thumb"]}" onerror="this.style.display='none'"></img></a>`
break;
}
eid("picker-holder").innerHTML += item
eid("picker-holder").innerHTML +=
`<a class="picker-image-container" ${
isIOS ? `onClick="share('${text.arr[i]["url"]}')"` : `href="${text.arr[i]["url"]}" target="_blank"`
}>` +
`<div class="picker-element-name">${text.arr[i].type}</div>` +
`<div class="imageBlock"></div>` +
`<img class="picker-image" src="${text.arr[i]["thumb"]}" onerror="this.style.display='none'"></img>` +
`</a>`
}
eid("picker-download").style.visibility = "hidden";
eid("picker-download").classList.remove("visible");
break;
}
break;
@ -217,14 +254,17 @@ function popup(type, action, text) {
break;
}
} else {
store.isPopupOpen = false;
if (type === "picker") {
eid("picker-download").href = '/';
eid("picker-download").style.visibility = "hidden"
eid("picker-download").classList.remove("visible");
eid("picker-holder").innerHTML = ''
}
}
eid("popup-backdrop").style.visibility = vis(action);
eid(`popup-${type}`).style.visibility = vis(action);
if (bottomPopups.includes(type)) eid(`popup-${type}-container`).classList.toggle("visible");
eid("popup-backdrop").classList.toggle("visible");
eid(`popup-${type}`).classList.toggle("visible");
eid(`popup-${type}`).focus();
}
function changeSwitcher(li, b) {
if (b) {
@ -243,24 +283,16 @@ function changeSwitcher(li, b) {
}
}
}
function internetError() {
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
popup("error", 1, loc.noInternet);
}
function checkbox(action) {
sSet(action, !!eid(action).checked);
switch(action) {
case "alwaysVisibleButton": button(); break;
case "reduceTransparency": eid("cobalt-body").classList.toggle('no-transparency'); break;
case "disableAnimations": eid("cobalt-body").classList.toggle('no-animation'); break;
}
action === "disableChangelog" && sGet(action) === "true" ? notificationCheck("disable") : notificationCheck();
}
function loadSettings() {
try {
if (typeof(navigator.clipboard.readText) == "undefined") throw new Error();
} catch (err) {
eid("paste").style.display = "none";
}
if (sGet("alwaysVisibleButton") === "true") {
eid("alwaysVisibleButton").checked = true;
eid("download-button").value = '>>'
@ -269,6 +301,12 @@ function loadSettings() {
if (sGet("downloadPopup") === "true" && !isIOS) {
eid("downloadPopup").checked = true;
}
if (sGet("reduceTransparency") === "true" || isOldFirefox) {
eid("cobalt-body").classList.add('no-transparency');
}
if (sGet("disableAnimations") === "true") {
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;
}
@ -281,8 +319,9 @@ function changeButton(type, text) {
case 0: //error
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
changeDownloadButton(2, '!!')
changeDownloadButton(2, '!!');
popup("error", 1, text);
setTimeout(() => { changeButton(1); }, 2500);
break;
case 1: //enable back
changeDownloadButton(1, '>>');
@ -297,6 +336,12 @@ function changeButton(type, text) {
break;
}
}
function internetError() {
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
setTimeout(() => { changeButton(1); }, 2500);
popup("error", 1, loc.ErrorNoInternet);
}
function resetSettings() {
localStorage.clear();
window.location.reload();
@ -308,7 +353,17 @@ async function pasteClipboard() {
eid("url-input-area").value = t;
download(eid("url-input-area").value);
}
} catch (e) {}
} catch (e) {
let errorMessage = loc.FeatureErrorGeneric;
let doError = true;
let error = String(e).toLowerCase();
if (error.includes("denied")) errorMessage = loc.ClipboardErrorNoPermission;
if (error.includes("dismissed") || isIOS) doError = false;
if (error.includes("function") && isFirefox) errorMessage = loc.ClipboardErrorFirefox;
if (doError) popup("error", 1, errorMessage);
}
}
async function download(url) {
changeDownloadButton(2, '...');
@ -336,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),
@ -350,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":
@ -360,23 +417,15 @@ async function download(url) {
break;
case "picker":
if (j.audio && j.picker) {
changeDownloadButton(2, '?..')
fetch(`${j.audio}&p=1`).then(async (res) => {
let jp = await res.json();
if (jp.status === "continue") {
changeDownloadButton(2, '>>>');
popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 2500);
} else {
changeButton(0, jp.text);
}
}).catch((error) => internetError());
changeDownloadButton(2, '>>>');
popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 2500);
} else if (j.picker) {
changeDownloadButton(2, '>>>');
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":
@ -384,7 +433,10 @@ async function download(url) {
fetch(`${j.url}&p=1`).then(async (res) => {
let jp = await res.json();
if (jp.status === "continue") {
changeDownloadButton(2, '>>>'); window.location.href = j.url;
changeDownloadButton(2, '>>>');
if (isMobile || isSafari) {
window.location.href = j.url;
} else window.open(j.url, '_blank');
setTimeout(() => { changeButton(1) }, 2500);
} else {
changeButton(0, jp.text);
@ -395,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) {
@ -405,9 +457,9 @@ async function download(url) {
async function loadCelebrationsEmoji() {
let bac = eid("about-footer").innerHTML;
try {
let j = await fetch(`${apiURL}/api/onDemand?blockId=1`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
let j = await fetch(`/onDemand?blockId=1`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
if (j && j.status === "success" && j.text) {
eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('<img class="emoji" draggable="false" height="22" width="22" alt="🐲" src="emoji/dragon_face.svg">', j.text);
eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('<img class="emoji" draggable="false" height="22" width="22" alt="🐲" src="emoji/dragon_face.svg" loading="lazy">', j.text);
}
} catch (e) {
eid("about-footer").innerHTML = bac;
@ -416,13 +468,13 @@ async function loadCelebrationsEmoji() {
async function loadOnDemand(elementId, blockId) {
let j = {};
store.historyButton = eid(elementId).innerHTML;
eid(elementId).innerHTML = "...";
eid(elementId).innerHTML = `<div class="loader">...</div>`;
try {
if (store.historyContent) {
j = store.historyContent;
} else {
await fetch(`${apiURL}/api/onDemand?blockId=${blockId}`).then(async(r) => {
await fetch(`/onDemand?blockId=${blockId}`).then(async(r) => {
j = await r.json();
if (j && j.status === "success") {
store.historyContent = j;
@ -430,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;
@ -440,30 +492,91 @@ 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, '>>');
eid("cobalt-main-box").style.visibility = 'visible';
eid("footer").style.visibility = 'visible';
eid("url-input-area").value = "";
notificationCheck();
loadCelebrationsEmoji();
if (isIOS) sSet("downloadPopup", "true");
let urlQuery = new URLSearchParams(window.location.search).get("u");
if (urlQuery !== null && regex.test(urlQuery)) {
eid("url-input-area").value = urlQuery;
button();
if (isIOS) {
sSet("downloadPopup", "true");
eid("downloadPopup-chkbx").style.display = "none";
}
eid("home").style.visibility = 'visible';
eid("home").classList.toggle("visible");
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", (event) => {
if (event.key === 'Escape') eid("url-input-area").value = '';
eid("url-input-area").addEventListener("keydown", (e) => {
button();
})
eid("url-input-area").addEventListener("keyup", (event) => {
if (event.key === 'Enter') eid("download-button").click();
eid("url-input-area").addEventListener("keyup", (e) => {
if (e.key === 'Enter') eid("download-button").click();
})
document.onkeydown = (event) => {
if (event.key === "Tab" || event.ctrlKey) eid("url-input-area").focus();
if (event.key === 'Escape') hideAllPopups();
document.onkeydown = (e) => {
if (!store.isPopupOpen) {
if (e.ctrlKey || e.key === "/") eid("url-input-area").focus();
if (e.key === "Escape" || e.key === "Clear") clearInput();
// top buttons
if (e.key === "D") pasteClipboard();
if (e.key === "K") changeSwitcher('audioMode', 'false');
if (e.key === "L") changeSwitcher('audioMode', 'true');
// popups
if (e.key === "B") popup('about', 1, 'about'); // open about
if (e.key === "N") popup('about', 1, 'changelog'); // open changelog
if (e.key === "M") popup('settings', 1);
} else {
if (e.key === "Escape") hideAllPopups();
}
}

View file

@ -0,0 +1,382 @@
<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_6905_5272)" />
<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_6905_5272)" />
<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_6905_5272)" />
<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_6905_5272)" />
<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_6905_5272)" />
<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_6905_5272)" />
<g filter="url(#filter0_f_6905_5272)">
<rect x="5.09634" y="21.1274" width="2.72692" height="3.69801" rx="1.36346" fill="#D67908" />
</g>
<g filter="url(#filter1_f_6905_5272)">
<path d="M26.0196 14.4614L27.0477 5.82727C27.1024 5.36633 26.3337 4.89183 25.4479 5.71436C25.4479 5.71436 22.068 9.03597 21.881 9.22018C19.5769 11.489 22.6425 12.468 26.0196 14.4614Z" fill="url(#paint6_linear_6905_5272)" />
</g>
<path d="M6.17485 12.8104L8.97485 10.2104C9.29485 9.91045 9.29485 9.43045 8.97485 9.14045L5.96016 6.46326C5.57735 6.12733 4.78485 6.2367 4.78485 7.07045V12.2704C4.78485 13.1039 5.66485 13.2904 6.17485 12.8104Z" fill="url(#paint7_linear_6905_5272)" />
<g filter="url(#filter2_f_6905_5272)">
<path d="M26.5331 5.30772C26.591 5.16404 26.7304 5.06995 26.8853 5.06995V5.06995C27.0951 5.06995 27.2652 5.23999 27.2652 5.44976V7.69495L25.8477 7.00745L26.5331 5.30772Z" fill="url(#paint8_linear_6905_5272)" />
</g>
<g filter="url(#filter3_f_6905_5272)">
<path d="M5.26547 5.62744L8.64043 8.84698C9.39455 9.56636 9.40868 10.7655 8.67172 11.5024V11.5024" stroke="#FFDF70" stroke-linecap="round" />
</g>
<g filter="url(#filter4_f_6905_5272)">
<path d="M14.8477 20.7882L14.912 20.7732C15.3856 20.6628 15.8788 20.6679 16.35 20.7882V20.7882C16.8711 20.8864 17.1822 21.4837 16.7422 21.9137L16.1914 22.5153C15.9214 22.7853 15.3286 22.7853 15.0586 22.5153L14.5156 21.9137C14.0664 21.4059 14.3477 20.8981 14.8477 20.7882Z" fill="url(#paint9_linear_6905_5272)" />
</g>
<g filter="url(#filter5_f_6905_5272)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.654 22.274C16.0177 22.274 16.3126 22.5203 16.3126 22.824V25.574C16.3126 25.8778 16.0177 26.124 15.654 26.124C15.2903 26.124 14.9954 25.8778 14.9954 25.574V22.824C14.9954 22.5203 15.2903 22.274 15.654 22.274Z" fill="#DC8400" />
</g>
<path d="M13.1406 26.8846C14.0625 25.9731 16.3438 24.7361 18.875 26.8846" stroke="url(#paint10_radial_6905_5272)" stroke-width="1.1" stroke-linecap="round" />
<path d="M16 25.408V22.658" stroke="url(#paint11_linear_6905_5272)" stroke-width="1.1" stroke-linecap="round" />
<path d="M15.2688 20.3445L15.3331 20.3295C15.8067 20.219 16.2999 20.2242 16.7711 20.3445V20.3445C17.2922 20.4426 17.6033 21.04 17.1633 21.47L16.6125 22.0715C16.3425 22.3415 15.7497 22.3415 15.4797 22.0715L14.9367 21.47C14.4875 20.9622 14.7688 20.4543 15.2688 20.3445Z" fill="url(#paint12_radial_6905_5272)" />
<g filter="url(#filter6_f_6905_5272)">
<ellipse cx="16.3126" cy="20.8794" rx="0.761719" ry="0.274258" fill="url(#paint13_linear_6905_5272)" />
</g>
<g filter="url(#filter7_f_6905_5272)">
<path d="M24.8274 22.1993L28.632 20.3868" stroke="url(#paint14_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter8_f_6905_5272)">
<path d="M24.8274 24.2306L26.754 25.5962" stroke="url(#paint15_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.6059 23.9764L29.4074 25.7954" stroke="url(#paint16_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter9_f_6905_5272)">
<path d="M26.2454 24.182L29.3422 25.6752" stroke="#FF8485" stroke-width="0.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter10_f_6905_5272)">
<ellipse cx="29.5827" cy="25.6753" rx="0.18384" ry="0.165827" transform="rotate(28.2981 29.5827 25.6753)" fill="#FFDD86" />
</g>
<g filter="url(#filter11_f_6905_5272)">
<path d="M9.90707 22.2647C9.90707 21.3498 9.1351 20.6246 8.22198 20.6817V20.6817C7.38474 20.7341 6.73308 21.4294 6.73498 22.2683L6.7415 25.1442C6.74349 26.0254 7.4648 26.7353 8.34593 26.7233V26.7233C9.21156 26.7114 9.90707 26.0064 9.90707 25.1406L9.90707 22.2647Z" fill="#CD7B00" />
</g>
<path d="M6.57037 23.9881L2.76881 25.8071" stroke="url(#paint17_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter12_f_6905_5272)">
<path d="M6.63558 23.868L2.83402 25.687" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter13_f_6905_5272)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.942776 -0.333427 -0.333427 0.942776 6.68841 23.812)" fill="#FFDD86" />
</g>
<path d="M6.58199 21.8314L2.76842 20.0377" stroke="url(#paint18_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter14_f_6905_5272)">
<path d="M6.71627 21.8059L2.90269 20.0122" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter15_f_6905_5272)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.338111 -0.941106 -0.941106 0.338111 6.79301 21.8114)" fill="#FFDD86" />
</g>
<g filter="url(#filter16_f_6905_5272)">
<rect x="22.1446" y="9.40869" width="4.5851" height="14.1857" fill="url(#paint19_linear_6905_5272)" />
</g>
<path d="M25.6086 21.8462L29.2923 20.1118" stroke="url(#paint20_linear_6905_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter17_f_6905_5272)">
<path d="M25.6086 22.0181L29.4133 20.2056" stroke="#FF5B5D" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.8752 12.8104L23.0752 10.2104C22.7552 9.91045 22.7552 9.43045 23.0752 9.14045L26.0899 6.46326C26.4727 6.12733 27.2652 6.2367 27.2652 7.07045V12.2704C27.2652 13.1039 26.3852 13.2904 25.8752 12.8104Z" fill="#FFB915" />
<path d="M25.8752 12.8104L23.0752 10.2104C22.7552 9.91045 22.7552 9.43045 23.0752 9.14045L26.0899 6.46326C26.4727 6.12733 27.2652 6.2367 27.2652 7.07045V12.2704C27.2652 13.1039 26.3852 13.2904 25.8752 12.8104Z" fill="url(#paint21_linear_6905_5272)" />
<g filter="url(#filter18_f_6905_5272)">
<path d="M26.6758 6.67432L23.0508 9.79932L26.5977 12.6118L26.6758 6.67432Z" fill="url(#paint22_radial_6905_5272)" />
</g>
<g filter="url(#filter19_f_6905_5272)">
<path d="M23.7696 9.59619L26.3008 12.3306" stroke="url(#paint23_linear_6905_5272)" stroke-width="0.5" stroke-linecap="round" />
</g>
<g filter="url(#filter20_f_6905_5272)">
<path d="M5.26547 6.8064V12.7141L8.73422 9.62476L5.26547 6.8064Z" fill="#FEB33E" />
</g>
<g filter="url(#filter21_f_6905_5272)">
<path d="M5.26547 7.03369V12.2212" stroke="#FFE7A3" stroke-width="0.15" stroke-linecap="round" />
</g>
<g filter="url(#filter22_f_6905_5272)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.922231 -0.386639 -0.386639 0.922231 2.85551 19.8876)" fill="#FFDD86" />
</g>
<g filter="url(#filter23_f_6905_5272)">
<ellipse cx="29.6481" cy="19.8876" rx="0.18384" ry="0.165827" transform="rotate(-22.7455 29.6481 19.8876)" fill="#FFDD86" />
</g>
<g filter="url(#filter24_f_6905_5272)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.6151 18.5322C20.0849 19.5789 21.1066 19.653 21.5454 19.4797C21.8324 19.3663 22.1569 19.5071 22.2702 19.794C22.3836 20.081 22.2428 20.4055 21.9559 20.5188C21.0781 20.8655 19.3584 20.6886 18.5958 18.9898C18.4695 18.7083 18.5952 18.3777 18.8767 18.2514C19.1581 18.125 19.4887 18.2508 19.6151 18.5322Z" fill="url(#paint24_linear_6905_5272)" />
</g>
<g filter="url(#filter25_f_6905_5272)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2811 18.5689C11.8113 19.6155 10.7896 19.6896 10.3507 19.5163C10.0638 19.4029 9.73929 19.5437 9.62595 19.8306C9.51261 20.1176 9.65336 20.4421 9.94031 20.5554C10.8181 20.9021 12.5378 20.7252 13.3004 19.0264C13.4267 18.7449 13.301 18.4143 13.0195 18.288C12.738 18.1616 12.4074 18.2874 12.2811 18.5689Z" fill="url(#paint25_linear_6905_5272)" />
</g>
<path d="M12.8139 18.2284C12.5404 18.9471 11.392 19.9159 9.99355 19.3299" stroke="url(#paint26_radial_6905_5272)" stroke-width="1.1" stroke-linecap="round" />
<path d="M19.2706 18.2284C19.5441 18.9471 20.6925 19.9159 22.091 19.3299" stroke="url(#paint27_radial_6905_5272)" stroke-width="1.1" stroke-linecap="round" />
<path d="M11.5692 22.0531C11.5692 20.9995 10.6802 20.1644 9.62863 20.2302V20.2302C8.66448 20.2905 7.91404 21.0912 7.91623 22.0572L7.92373 25.3691C7.92603 26.3839 8.75668 27.2014 9.77137 27.1875V27.1875C10.7682 27.1739 11.5692 26.3619 11.5692 25.365L11.5692 22.0531Z" fill="url(#paint28_radial_6905_5272)" />
<path d="M11.5692 22.0531C11.5692 20.9995 10.6802 20.1644 9.62863 20.2302V20.2302C8.66448 20.2905 7.91404 21.0912 7.91623 22.0572L7.92373 25.3691C7.92603 26.3839 8.75668 27.2014 9.77137 27.1875V27.1875C10.7682 27.1739 11.5692 26.3619 11.5692 25.365L11.5692 22.0531Z" fill="url(#paint29_radial_6905_5272)" />
<path d="M11.5692 22.0531C11.5692 20.9995 10.6802 20.1644 9.62863 20.2302V20.2302C8.66448 20.2905 7.91404 21.0912 7.91623 22.0572L7.92373 25.3691C7.92603 26.3839 8.75668 27.2014 9.77137 27.1875V27.1875C10.7682 27.1739 11.5692 26.3619 11.5692 25.365L11.5692 22.0531Z" fill="url(#paint30_linear_6905_5272)" />
<g filter="url(#filter26_f_6905_5272)">
<path d="M10.3125 21.5879L10.3847 25.2051" stroke="#6BAFFF" stroke-linecap="round" />
</g>
<g filter="url(#filter27_f_6905_5272)">
<circle r="0.592704" transform="matrix(-0.724367 0.689414 0.724367 0.689414 9.75781 21.2884)" fill="#6BAFFF" />
</g>
<defs>
<filter id="filter0_f_6905_5272" x="3.59634" y="19.6274" 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_6905_5272" />
</filter>
<filter id="filter1_f_6905_5272" x="20.58" y="4.76599" width="6.97055" height="10.1954" 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_6905_5272" />
</filter>
<filter id="filter2_f_6905_5272" x="24.8477" y="4.06995" 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_6905_5272" />
</filter>
<filter id="filter3_f_6905_5272" x="2.76547" y="3.12744" width="8.94989" height="10.7285" 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_6905_5272" />
</filter>
<filter id="filter4_f_6905_5272" x="13.7905" y="20.194" width="3.66141" height="3.0238" 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_6905_5272" />
</filter>
<filter id="filter5_f_6905_5272" x="14.4954" y="21.774" width="2.3172" height="4.84998" 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_6905_5272" />
</filter>
<filter id="filter6_f_6905_5272" x="14.9508" y="20.0051" width="2.72344" height="1.74846" 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_6905_5272" />
</filter>
<filter id="filter7_f_6905_5272" x="23.7272" y="19.2867" width="6.00494" height="4.0127" 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_6905_5272" />
</filter>
<filter id="filter8_f_6905_5272" x="23.7273" y="23.1306" width="4.12671" height="3.56567" 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_6905_5272" />
</filter>
<filter id="filter9_f_6905_5272" x="25.6454" y="23.582" width="4.29688" height="2.69324" 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_6905_5272" />
</filter>
<filter id="filter10_f_6905_5272" x="28.9027" y="25.0052" 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_6905_5272" />
</filter>
<filter id="filter11_f_6905_5272" x="5.73499" y="19.6786" width="5.17209" height="8.0448" 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_6905_5272" />
</filter>
<filter id="filter12_f_6905_5272" x="2.18399" y="23.2179" width="5.10162" height="3.11914" 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_6905_5272" />
</filter>
<filter id="filter13_f_6905_5272" x="6.00644" y="23.144" 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_6905_5272" />
</filter>
<filter id="filter14_f_6905_5272" x="2.25266" y="19.3622" width="5.11365" height="3.09375" 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_6905_5272" />
</filter>
<filter id="filter15_f_6905_5272" x="6.12497" y="21.1295" width="1.33606" height="1.36377" 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_6905_5272" />
</filter>
<filter id="filter16_f_6905_5272" x="18.1446" y="5.40869" 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_6905_5272" />
</filter>
<filter id="filter17_f_6905_5272" x="24.9586" y="19.5555" 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_6905_5272" />
</filter>
<filter id="filter18_f_6905_5272" x="22.4508" y="6.07432" 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_6905_5272" />
</filter>
<filter id="filter19_f_6905_5272" x="22.7696" y="8.59619" 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_6905_5272" />
</filter>
<filter id="filter20_f_6905_5272" x="4.76547" y="6.3064" 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_6905_5272" />
</filter>
<filter id="filter21_f_6905_5272" x="4.69046" y="6.45874" width="1.15002" height="6.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_6905_5272" />
</filter>
<filter id="filter22_f_6905_5272" x="2.17419" y="19.219" width="1.36264" height="1.33728" 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_6905_5272" />
</filter>
<filter id="filter23_f_6905_5272" x="28.9668" y="19.219" width="1.36264" height="1.33728" 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_6905_5272" />
</filter>
<filter id="filter24_f_6905_5272" x="18.0467" y="17.7023" width="4.76276" height="3.46326" 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_6905_5272" />
</filter>
<filter id="filter25_f_6905_5272" x="9.08673" y="17.7389" width="4.76276" height="3.46326" 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_6905_5272" />
</filter>
<filter id="filter26_f_6905_5272" x="7.80084" y="19.0999" width="5.09555" height="8.59326" 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_6905_5272" />
</filter>
<filter id="filter27_f_6905_5272" x="7.15063" y="18.7106" width="5.21436" height="5.15576" 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_6905_5272" />
</filter>
<linearGradient id="paint0_linear_6905_5272" 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_6905_5272" 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_6905_5272" 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_6905_5272" 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_6905_5272" 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_6905_5272" 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_6905_5272" x1="21.9503" y1="6.1586" x2="28.2071" y2="13.0023" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA720" />
<stop offset="1" stop-color="#FFA720" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint7_linear_6905_5272" x1="10.6446" y1="9.9242" x2="4.78485" y2="9.37733" gradientUnits="userSpaceOnUse">
<stop offset="0.437473" stop-color="#ED8C1B" />
<stop offset="1" stop-color="#FFB03A" />
</linearGradient>
<linearGradient id="paint8_linear_6905_5272" x1="27.0821" y1="4.61682" x2="26.486" y2="6.95827" gradientUnits="userSpaceOnUse">
<stop offset="0.28598" stop-color="#FFE792" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint9_linear_6905_5272" x1="15.793" y1="22.4892" x2="15.1212" y2="23.5829" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3900E" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint10_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.1985 27.2752) rotate(-90.1815) scale(5.21878 10.1877)">
<stop offset="0.191719" stop-color="#482641" />
<stop offset="0.311545" stop-color="#594253" />
<stop offset="0.386385" stop-color="#483334" />
</radialGradient>
<linearGradient id="paint11_linear_6905_5272" x1="18.1829" y1="24.447" x2="10.886" y2="24.447" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#483637" />
<stop offset="0.276042" stop-color="#594253" />
<stop offset="0.411458" stop-color="#482641" />
</linearGradient>
<radialGradient id="paint12_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.4883 21.3176) rotate(-153.246) scale(2.11729 2.12981)">
<stop stop-color="#EA088B" />
<stop offset="1" stop-color="#E61E27" />
</radialGradient>
<linearGradient id="paint13_linear_6905_5272" x1="17.2458" y1="20.8794" x2="15.7224" y2="20.8794" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.9" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint14_linear_6905_5272" x1="24.3321" y1="22.5431" x2="28.0665" y2="20.6274" gradientUnits="userSpaceOnUse">
<stop stop-color="#E88105" />
<stop offset="1" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint15_linear_6905_5272" x1="24.3321" y1="23.8869" x2="28.0665" y2="25.8026" gradientUnits="userSpaceOnUse">
<stop offset="0.286458" stop-color="#DE7D07" />
<stop offset="0.817708" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint16_linear_6905_5272" x1="25.1161" y1="24.0168" x2="29.1887" y2="25.9722" gradientUnits="userSpaceOnUse">
<stop stop-color="#B9230F" />
<stop offset="0.364583" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint17_linear_6905_5272" x1="7.06012" y1="24.0285" x2="2.98751" y2="25.984" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint18_linear_6905_5272" x1="6.8605" y1="22.2363" x2="2.7698" y2="20.3189" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint19_linear_6905_5272" x1="23.3946" y1="10.1457" x2="22.998" y2="22.1962" gradientUnits="userSpaceOnUse">
<stop offset="0.548257" stop-color="#FFDD65" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint20_linear_6905_5272" x1="25.3321" y1="22.2524" x2="29.4133" y2="20.3149" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#C83F2D" />
<stop offset="1" stop-color="#E7671C" />
</linearGradient>
<linearGradient id="paint21_linear_6905_5272" x1="26.5821" y1="14.8148" x2="25.3321" y2="11.3461" gradientUnits="userSpaceOnUse">
<stop stop-color="#EF8A47" />
<stop offset="1" stop-color="#EF8A47" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint22_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.9102 7.08057) rotate(55.7389) scale(7.82686 9.38424)">
<stop stop-color="#FFDF70" />
<stop offset="1" stop-color="#FFDF70" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint23_linear_6905_5272" x1="25.0977" y1="9.78369" x2="25.0977" y2="12.1274" gradientUnits="userSpaceOnUse">
<stop offset="0.432292" stop-color="#FFE7A3" />
<stop offset="1" stop-color="#FFDF83" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint24_linear_6905_5272" x1="17.5468" y1="17.8246" x2="18.3832" y2="15.8011" gradientUnits="userSpaceOnUse">
<stop stop-color="#DE8D0F" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint25_linear_6905_5272" x1="14.3494" y1="17.8613" x2="13.5129" y2="15.8378" gradientUnits="userSpaceOnUse">
<stop stop-color="#E69518" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint26_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.2201 18.4471) rotate(73.807) scale(3.48338 6.75247)">
<stop offset="0.135417" stop-color="#483637" />
<stop offset="0.276042" stop-color="#594253" />
<stop offset="0.411458" stop-color="#482641" />
</radialGradient>
<radialGradient id="paint27_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.8644 18.4471) rotate(106.193) scale(3.48338 6.75247)">
<stop offset="0.135417" stop-color="#483637" />
<stop offset="0.276042" stop-color="#594253" />
<stop offset="0.411458" stop-color="#482641" />
</radialGradient>
<radialGradient id="paint28_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.01037 27.3063) rotate(-48.5801) scale(8.69623 14.7041)">
<stop stop-color="#3D5BF1" />
<stop offset="1" stop-color="#5C98FF" />
</radialGradient>
<radialGradient id="paint29_radial_6905_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.504 20.0466) rotate(111.857) scale(9.25859 6.48471)">
<stop offset="0.731957" stop-color="#1F2D75" stop-opacity="0" />
<stop offset="0.956392" stop-color="#1F2D75" stop-opacity="0.75" />
</radialGradient>
<linearGradient id="paint30_linear_6905_5272" x1="13.1496" y1="24.2645" x2="6.10698" y2="24.151" gradientUnits="userSpaceOnUse">
<stop offset="0.585153" stop-color="#1F2D75" stop-opacity="0" />
<stop offset="1" stop-color="#1F2D75" stop-opacity="0.75" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,507 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint0_linear_6903_5272)" />
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint1_linear_6903_5272)" />
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint2_linear_6903_5272)" />
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint3_linear_6903_5272)" />
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint4_linear_6903_5272)" />
<path d="M27.9617 25.9948V6.35147C27.9617 4.91397 26.6257 4.21085 25.532 5.22647L21.5184 9.17869C21.3313 9.36291 21.0793 9.46616 20.8167 9.46616H11.2204C10.9578 9.46616 10.7058 9.36291 10.5188 9.17869L6.50513 5.22647C5.41138 4.21085 4.07544 4.91397 4.07544 6.35147V25.9948C4.07544 28.2066 5.83537 29.9999 8.00597 29.9999H24.0312C26.2018 29.9999 27.9617 28.2066 27.9617 25.9948Z" fill="url(#paint5_radial_6903_5272)" />
<g filter="url(#filter0_f_6903_5272)">
<rect x="5.05408" y="21.75" width="2.72692" height="3.69801" rx="1.36346" fill="#D67908" />
</g>
<g filter="url(#filter1_f_6903_5272)">
<path d="M20.5855 10.165C17.6023 10.2726 25.812 15.1188 25.812 15.1188L26.937 6.0563C26.9917 5.59537 26.5729 5.10279 25.6871 5.92532C25.6871 5.92532 21.8605 9.69333 21.6734 9.87754C21.4864 10.0618 21.2654 10.1265 20.9718 10.165C20.8222 10.1846 20.7363 10.1596 20.5855 10.165Z" fill="url(#paint6_linear_6903_5272)" />
</g>
<path d="M6.13255 13.4331L8.93255 10.8331C9.25255 10.5331 9.25255 10.0531 8.93255 9.76313L5.91787 7.08594C5.53505 6.75001 4.74255 6.85938 4.74255 7.69313V12.8931C4.74255 13.7266 5.62255 13.9131 6.13255 13.4331Z" fill="url(#paint7_linear_6903_5272)" />
<g filter="url(#filter2_f_6903_5272)">
<path d="M26.4909 5.58153C26.5488 5.43784 26.6882 5.34375 26.8431 5.34375V5.34375C27.0529 5.34375 27.2229 5.5138 27.2229 5.72356V7.96875L25.8055 7.28125L26.4909 5.58153Z" fill="url(#paint8_linear_6903_5272)" />
</g>
<g filter="url(#filter3_f_6903_5272)">
<path d="M5.40601 6.0625L8.78097 9.28203C9.53508 10.0014 9.54921 11.2005 8.81226 11.9375V11.9375" stroke="#FFDF70" stroke-linecap="round" />
</g>
<g filter="url(#filter4_f_6903_5272)">
<path d="M14.8054 19.5626L14.8697 19.5476C15.3433 19.4372 15.8365 19.4423 16.3077 19.5626V19.5626C16.8288 19.6608 17.1399 20.2581 16.6999 20.6881L16.1491 21.2897C15.8791 21.5597 15.2863 21.5597 15.0163 21.2897L14.4733 20.6881C14.0241 20.1803 14.3054 19.6725 14.8054 19.5626Z" fill="url(#paint9_linear_6903_5272)" />
</g>
<path d="M15.2265 19.1189L15.2908 19.1039C15.7644 18.9935 16.2577 18.9986 16.7289 19.1189V19.1189C17.25 19.217 17.561 19.8144 17.121 20.2444L16.5703 20.8459C16.3003 21.1159 15.7075 21.1159 15.4375 20.8459L14.8945 20.2444C14.4453 19.7366 14.7265 19.2288 15.2265 19.1189Z" fill="url(#paint10_radial_6903_5272)" />
<g filter="url(#filter5_f_6903_5272)">
<ellipse cx="16.2703" cy="19.6538" rx="0.761719" ry="0.274258" fill="url(#paint11_linear_6903_5272)" />
</g>
<g filter="url(#filter6_f_6903_5272)">
<path d="M24.7851 22.8219L28.5898 21.0094" stroke="url(#paint12_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter7_f_6903_5272)">
<path d="M24.7851 24.8532L26.7117 26.2188" stroke="url(#paint13_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.5663 22.4688L29.371 20.6562" stroke="url(#paint14_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter8_f_6903_5272)">
<path d="M25.5663 22.6406L29.371 20.8281" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.5636 24.5989L29.3652 26.418" stroke="url(#paint15_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter9_f_6903_5272)">
<path d="M25.4984 24.4788L29.3 26.2979" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M6.5281 24.6106L2.72654 26.4297" stroke="url(#paint16_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter10_f_6903_5272)">
<path d="M6.59334 24.4905L2.79179 26.3096" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter11_f_6903_5272)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.942776 -0.333427 -0.333427 0.942776 6.64614 24.4345)" fill="#FFDD86" />
</g>
<path d="M6.53969 22.454L2.72612 20.6602" stroke="url(#paint17_linear_6903_5272)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter12_f_6903_5272)">
<path d="M6.67403 22.4284L2.86046 20.6347" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter13_f_6903_5272)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.338111 -0.941106 -0.941106 0.338111 6.75078 22.434)" fill="#FFDD86" />
</g>
<g filter="url(#filter14_f_6903_5272)">
<rect x="22.1024" y="10.0312" width="4.5851" height="14.1857" fill="url(#paint18_linear_6903_5272)" />
</g>
<path d="M25.8329 13.4331L23.0329 10.8331C22.7129 10.5331 22.7129 10.0531 23.0329 9.76313L26.0476 7.08594C26.4304 6.75001 27.2229 6.85938 27.2229 7.69313V12.8931C27.2229 13.7266 26.3429 13.9131 25.8329 13.4331Z" fill="#FFB915" />
<path d="M25.8329 13.4331L23.0329 10.8331C22.7129 10.5331 22.7129 10.0531 23.0329 9.76313L26.0476 7.08594C26.4304 6.75001 27.2229 6.85938 27.2229 7.69313V12.8931C27.2229 13.7266 26.3429 13.9131 25.8329 13.4331Z" fill="url(#paint19_linear_6903_5272)" />
<g filter="url(#filter15_f_6903_5272)">
<path d="M26.6336 7.29688L23.0086 10.4219L26.5555 13.2344L26.6336 7.29688Z" fill="url(#paint20_radial_6903_5272)" />
</g>
<g filter="url(#filter16_f_6903_5272)">
<path d="M23.7274 10.2188L26.2586 12.9531" stroke="url(#paint21_linear_6903_5272)" stroke-width="0.5" stroke-linecap="round" />
</g>
<g filter="url(#filter17_f_6903_5272)">
<path d="M5.22321 7.42896V13.3367L8.69196 10.2473L5.22321 7.42896Z" fill="#FEB33E" />
</g>
<g filter="url(#filter18_f_6903_5272)">
<path d="M5.22321 7.65625V12.8438" stroke="#FFE7A3" stroke-width="0.15" stroke-linecap="round" />
</g>
<g filter="url(#filter19_f_6903_5272)">
<circle cx="22.3076" cy="24.9852" r="3.19081" fill="url(#paint22_linear_6903_5272)" />
</g>
<path d="M20.9883 23.7969C20.9883 21.5964 22.7721 19.8125 24.9727 19.8125V19.8125C27.1732 19.8125 28.957 21.5964 28.957 23.7969V30H20.9883V23.7969Z" fill="url(#paint23_linear_6903_5272)" />
<path d="M20.9883 23.7969C20.9883 21.5964 22.7721 19.8125 24.9727 19.8125V19.8125C27.1732 19.8125 28.957 21.5964 28.957 23.7969V30H20.9883V23.7969Z" fill="url(#paint24_linear_6903_5272)" />
<path d="M20.9883 23.7969C20.9883 21.5964 22.7721 19.8125 24.9727 19.8125V19.8125C27.1732 19.8125 28.957 21.5964 28.957 23.7969V30H20.9883V23.7969Z" fill="url(#paint25_linear_6903_5272)" />
<g filter="url(#filter20_f_6903_5272)">
<path d="M26.0507 20.3399C26.0507 20.2298 26.1399 20.1406 26.25 20.1406V20.1406C26.36 20.1406 26.4492 20.2298 26.4492 20.3399V23.0586C26.4492 23.1686 26.36 23.2578 26.25 23.2578V23.2578C26.1399 23.2578 26.0507 23.1686 26.0507 23.0586V20.3399Z" fill="#DA7600" fill-opacity="0.5" />
</g>
<g filter="url(#filter21_f_6903_5272)">
<path d="M22.9727 20.3437C22.9727 20.2316 23.0636 20.1406 23.1758 20.1406V20.1406C23.288 20.1406 23.3789 20.2316 23.3789 20.3438V22.9531C23.3789 23.0653 23.288 23.1563 23.1758 23.1563V23.1563C23.0636 23.1563 22.9727 23.0653 22.9727 22.9531V20.3437Z" fill="#DA7600" fill-opacity="0.75" />
</g>
<g filter="url(#filter22_f_6903_5272)">
<ellipse cx="27.1216" cy="22.2576" rx="0.846269" ry="1.35723" transform="rotate(-27.2088 27.1216 22.2576)" fill="#FDD659" />
</g>
<path d="M23.1133 20.0547C23.1133 19.8692 23.2637 19.7188 23.4492 19.7188V19.7188C23.6348 19.7188 23.7852 19.8692 23.7852 20.0547V22.8203C23.7852 23.0058 23.6348 23.1562 23.4492 23.1562V23.1562C23.2637 23.1562 23.1133 23.0058 23.1133 22.8203V20.0547Z" fill="url(#paint26_linear_6903_5272)" />
<path d="M23.1133 20.0547C23.1133 19.8692 23.2637 19.7188 23.4492 19.7188V19.7188C23.6348 19.7188 23.7852 19.8692 23.7852 20.0547V22.8203C23.7852 23.0058 23.6348 23.1562 23.4492 23.1562V23.1562C23.2637 23.1562 23.1133 23.0058 23.1133 22.8203V20.0547Z" fill="url(#paint27_linear_6903_5272)" />
<path d="M23.1133 20.0547C23.1133 19.8692 23.2637 19.7188 23.4492 19.7188V19.7188C23.6348 19.7188 23.7852 19.8692 23.7852 20.0547V22.8203C23.7852 23.0058 23.6348 23.1562 23.4492 23.1562V23.1562C23.2637 23.1562 23.1133 23.0058 23.1133 22.8203V20.0547Z" fill="url(#paint28_linear_6903_5272)" />
<path d="M26.1602 20.0547C26.1602 19.8692 26.3106 19.7188 26.4961 19.7188V19.7188C26.6816 19.7188 26.832 19.8692 26.832 20.0547V22.8203C26.832 23.0058 26.6816 23.1562 26.4961 23.1562V23.1562C26.3106 23.1562 26.1602 23.0058 26.1602 22.8203V20.0547Z" fill="url(#paint29_linear_6903_5272)" />
<path d="M26.1602 20.0547C26.1602 19.8692 26.3106 19.7188 26.4961 19.7188V19.7188C26.6816 19.7188 26.832 19.8692 26.832 20.0547V22.8203C26.832 23.0058 26.6816 23.1562 26.4961 23.1562V23.1562C26.3106 23.1562 26.1602 23.0058 26.1602 22.8203V20.0547Z" fill="url(#paint30_linear_6903_5272)" />
<path d="M26.1602 20.0547C26.1602 19.8692 26.3106 19.7188 26.4961 19.7188V19.7188C26.6816 19.7188 26.832 19.8692 26.832 20.0547V22.8203C26.832 23.0058 26.6816 23.1562 26.4961 23.1562V23.1562C26.3106 23.1562 26.1602 23.0058 26.1602 22.8203V20.0547Z" fill="url(#paint31_linear_6903_5272)" />
<g filter="url(#filter23_f_6903_5272)">
<path d="M26.6679 22.6797V20.4688" stroke="url(#paint32_linear_6903_5272)" stroke-width="0.2" stroke-linecap="round" />
</g>
<g filter="url(#filter24_f_6903_5272)">
<path d="M23.621 22.6797V20.4688" stroke="url(#paint33_linear_6903_5272)" stroke-width="0.2" stroke-linecap="round" />
</g>
<g filter="url(#filter25_f_6903_5272)">
<path d="M23.621 22.6797V20.4688" stroke="url(#paint34_linear_6903_5272)" stroke-width="0.2" stroke-linecap="round" />
</g>
<g filter="url(#filter26_f_6903_5272)">
<circle cx="11.082" cy="15.9766" r="3.49219" fill="url(#paint35_linear_6903_5272)" />
</g>
<g filter="url(#filter27_f_6903_5272)">
<circle cx="20.0898" cy="16.0904" r="3.28912" fill="url(#paint36_linear_6903_5272)" />
</g>
<circle cx="20.4844" cy="15.5781" r="3.49219" fill="white" />
<circle cx="11.5156" cy="15.5781" r="3.49219" fill="white" />
<rect x="13.8281" y="21.8359" width="4.34375" height="5.45312" rx="2.17188" fill="url(#paint37_radial_6903_5272)" />
<path d="M3.04297 23.7969C3.04297 21.5964 4.82683 19.8125 7.02734 19.8125V19.8125C9.22785 19.8125 11.0117 21.5964 11.0117 23.7969V30H3.04297V23.7969Z" fill="url(#paint38_linear_6903_5272)" />
<path d="M3.04297 23.7969C3.04297 21.5964 4.82683 19.8125 7.02734 19.8125V19.8125C9.22785 19.8125 11.0117 21.5964 11.0117 23.7969V30H3.04297V23.7969Z" fill="url(#paint39_linear_6903_5272)" />
<path d="M3.04297 23.7969C3.04297 21.5964 4.82683 19.8125 7.02734 19.8125V19.8125C9.22785 19.8125 11.0117 21.5964 11.0117 23.7969V30H3.04297V23.7969Z" fill="url(#paint40_linear_6903_5272)" />
<g filter="url(#filter28_f_6903_5272)">
<ellipse cx="9.1763" cy="22.2576" rx="0.846269" ry="1.35723" transform="rotate(-27.2088 9.1763 22.2576)" fill="#FDD659" />
</g>
<g filter="url(#filter29_f_6903_5272)">
<path d="M8.21484 20.3437C8.21484 20.2316 8.30579 20.1406 8.41797 20.1406V20.1406C8.53015 20.1406 8.62109 20.2316 8.62109 20.3438V22.9531C8.62109 23.0653 8.53015 23.1563 8.41797 23.1563V23.1563C8.30579 23.1563 8.21484 23.0653 8.21484 22.9531V20.3437Z" fill="#DA7600" fill-opacity="0.5" />
</g>
<g filter="url(#filter30_f_6903_5272)">
<path d="M5.09766 20.3437C5.09766 20.2316 5.1886 20.1406 5.30078 20.1406V20.1406C5.41296 20.1406 5.50391 20.2316 5.50391 20.3438V22.9531C5.50391 23.0653 5.41296 23.1563 5.30078 23.1563V23.1563C5.1886 23.1563 5.09766 23.0653 5.09766 22.9531V20.3437Z" fill="#DA7600" fill-opacity="0.75" />
</g>
<path d="M5.16797 20.0547C5.16797 19.8692 5.31837 19.7188 5.50391 19.7188V19.7188C5.68944 19.7188 5.83984 19.8692 5.83984 20.0547V22.8203C5.83984 23.0058 5.68944 23.1562 5.50391 23.1562V23.1562C5.31837 23.1562 5.16797 23.0058 5.16797 22.8203V20.0547Z" fill="url(#paint41_linear_6903_5272)" />
<path d="M5.16797 20.0547C5.16797 19.8692 5.31837 19.7188 5.50391 19.7188V19.7188C5.68944 19.7188 5.83984 19.8692 5.83984 20.0547V22.8203C5.83984 23.0058 5.68944 23.1562 5.50391 23.1562V23.1562C5.31837 23.1562 5.16797 23.0058 5.16797 22.8203V20.0547Z" fill="url(#paint42_linear_6903_5272)" />
<path d="M5.16797 20.0547C5.16797 19.8692 5.31837 19.7188 5.50391 19.7188V19.7188C5.68944 19.7188 5.83984 19.8692 5.83984 20.0547V22.8203C5.83984 23.0058 5.68944 23.1562 5.50391 23.1562V23.1562C5.31837 23.1562 5.16797 23.0058 5.16797 22.8203V20.0547Z" fill="url(#paint43_linear_6903_5272)" />
<path d="M8.21484 20.0547C8.21484 19.8692 8.36525 19.7188 8.55078 19.7188V19.7188C8.73631 19.7188 8.88672 19.8692 8.88672 20.0547V22.8203C8.88672 23.0058 8.73631 23.1562 8.55078 23.1562V23.1562C8.36525 23.1562 8.21484 23.0058 8.21484 22.8203V20.0547Z" fill="url(#paint44_linear_6903_5272)" />
<path d="M8.21484 20.0547C8.21484 19.8692 8.36525 19.7188 8.55078 19.7188V19.7188C8.73631 19.7188 8.88672 19.8692 8.88672 20.0547V22.8203C8.88672 23.0058 8.73631 23.1562 8.55078 23.1562V23.1562C8.36525 23.1562 8.21484 23.0058 8.21484 22.8203V20.0547Z" fill="url(#paint45_linear_6903_5272)" />
<path d="M8.21484 20.0547C8.21484 19.8692 8.36525 19.7188 8.55078 19.7188V19.7188C8.73631 19.7188 8.88672 19.8692 8.88672 20.0547V22.8203C8.88672 23.0058 8.73631 23.1562 8.55078 23.1562V23.1562C8.36525 23.1562 8.21484 23.0058 8.21484 22.8203V20.0547Z" fill="url(#paint46_linear_6903_5272)" />
<g filter="url(#filter31_f_6903_5272)">
<path d="M8.7226 22.6797V20.4688" stroke="url(#paint47_linear_6903_5272)" stroke-width="0.2" stroke-linecap="round" />
</g>
<g filter="url(#filter32_f_6903_5272)">
<path d="M5.67572 22.6797V20.4688" stroke="url(#paint48_linear_6903_5272)" stroke-width="0.2" stroke-linecap="round" />
</g>
<defs>
<filter id="filter0_f_6903_5272" x="3.55408" y="20.25" 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_6903_5272" />
</filter>
<filter id="filter1_f_6903_5272" x="18.9327" y="4.48315" 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_6903_5272" />
</filter>
<filter id="filter2_f_6903_5272" x="24.8055" y="4.34375" 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_6903_5272" />
</filter>
<filter id="filter3_f_6903_5272" x="2.90601" y="3.5625" width="8.94989" height="10.7285" 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_6903_5272" />
</filter>
<filter id="filter4_f_6903_5272" x="13.7482" y="18.9684" width="3.66141" height="3.0238" 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_6903_5272" />
</filter>
<filter id="filter5_f_6903_5272" x="14.9086" y="18.7795" width="2.72344" height="1.74846" 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_6903_5272" />
</filter>
<filter id="filter6_f_6903_5272" x="23.685" y="19.9093" width="6.00494" height="4.0127" 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_6903_5272" />
</filter>
<filter id="filter7_f_6903_5272" x="23.6851" y="23.7532" width="4.12671" height="3.56567" 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_6903_5272" />
</filter>
<filter id="filter8_f_6903_5272" x="24.9163" y="20.1781" 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_6903_5272" />
</filter>
<filter id="filter9_f_6903_5272" x="24.8484" y="23.8287" width="5.10162" height="3.11914" 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_6903_5272" />
</filter>
<filter id="filter10_f_6903_5272" x="2.14175" y="23.8405" width="5.10162" height="3.11914" 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_6903_5272" />
</filter>
<filter id="filter11_f_6903_5272" x="5.96417" y="23.7666" 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_6903_5272" />
</filter>
<filter id="filter12_f_6903_5272" x="2.21042" y="19.9847" width="5.11365" height="3.09375" 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_6903_5272" />
</filter>
<filter id="filter13_f_6903_5272" x="6.08273" y="21.7521" width="1.33606" height="1.36377" 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_6903_5272" />
</filter>
<filter id="filter14_f_6903_5272" x="18.1024" y="6.03125" 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_6903_5272" />
</filter>
<filter id="filter15_f_6903_5272" x="22.4086" y="6.69687" 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_6903_5272" />
</filter>
<filter id="filter16_f_6903_5272" x="22.7274" y="9.21875" 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_6903_5272" />
</filter>
<filter id="filter17_f_6903_5272" x="4.72321" y="6.92896" 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_6903_5272" />
</filter>
<filter id="filter18_f_6903_5272" x="4.64819" y="7.0813" width="1.15002" height="6.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_6903_5272" />
</filter>
<filter id="filter19_f_6903_5272" x="17.1168" y="19.7944" width="10.3816" height="10.3816" 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_6903_5272" />
</filter>
<filter id="filter20_f_6903_5272" x="25.5507" y="19.6406" width="1.3985" height="4.11719" 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_6903_5272" />
</filter>
<filter id="filter21_f_6903_5272" x="22.4727" y="19.6406" width="1.40625" height="4.01562" 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_6903_5272" />
</filter>
<filter id="filter22_f_6903_5272" x="24.6461" y="19.4897" width="4.95099" height="5.53577" 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_6903_5272" />
</filter>
<filter id="filter23_f_6903_5272" x="26.3679" y="20.1688" width="0.600012" height="2.81089" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.1" result="effect1_foregroundBlur_6903_5272" />
</filter>
<filter id="filter24_f_6903_5272" x="23.321" y="20.1688" width="0.600012" height="2.81089" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.1" result="effect1_foregroundBlur_6903_5272" />
</filter>
<filter id="filter25_f_6903_5272" x="23.321" y="20.1688" width="0.600012" height="2.81089" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.1" result="effect1_foregroundBlur_6903_5272" />
</filter>
<filter id="filter26_f_6903_5272" x="6.58978" y="11.4844" width="8.98438" height="8.98438" 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_6903_5272" />
</filter>
<filter id="filter27_f_6903_5272" x="15.8007" y="11.8013" width="8.57825" height="8.57825" 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_6903_5272" />
</filter>
<filter id="filter28_f_6903_5272" x="6.70081" y="19.4897" width="4.95099" height="5.53577" 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_6903_5272" />
</filter>
<filter id="filter29_f_6903_5272" x="7.71484" y="19.6406" width="1.40625" height="4.01562" 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_6903_5272" />
</filter>
<filter id="filter30_f_6903_5272" x="4.59766" y="19.6406" width="1.40625" height="4.01562" 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_6903_5272" />
</filter>
<filter id="filter31_f_6903_5272" x="8.42259" y="20.1688" width="0.600012" height="2.81089" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.1" result="effect1_foregroundBlur_6903_5272" />
</filter>
<filter id="filter32_f_6903_5272" x="5.37571" y="20.1688" width="0.600012" height="2.81089" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.1" result="effect1_foregroundBlur_6903_5272" />
</filter>
<linearGradient id="paint0_linear_6903_5272" x1="7.96361" y1="1.05993" x2="7.96361" y2="30.9999" 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_6903_5272" x1="8.88356" y1="11.8512" x2="1.25856" y2="11.8512" 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_6903_5272" x1="26.7586" y1="11.8512" x2="30.1961" y2="11.8512" 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_6903_5272" x1="16.0195" y1="-1.91422" x2="16.0186" y2="30.5233" gradientUnits="userSpaceOnUse">
<stop offset="0.77079" stop-color="#F59639" stop-opacity="0" />
<stop offset="1" stop-color="#FF63C4" />
</linearGradient>
<linearGradient id="paint4_linear_6903_5272" x1="16.0186" y1="11.0624" x2="16.0186" y2="32.3358" gradientUnits="userSpaceOnUse">
<stop offset="0.854227" stop-color="white" stop-opacity="0" />
<stop offset="0.985362" stop-color="white" />
</linearGradient>
<radialGradient id="paint5_radial_6903_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.1648 4.21866) rotate(55.8501) scale(5.39983 10.6704)">
<stop stop-color="#FFA720" />
<stop offset="0.921158" stop-color="#FFA720" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint6_linear_6903_5272" x1="23.4373" y1="8.96438" x2="27.9996" y2="13.6597" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA720" />
<stop offset="1" stop-color="#FFA720" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint7_linear_6903_5272" x1="10.6023" y1="10.5469" x2="4.74255" y2="10" gradientUnits="userSpaceOnUse">
<stop offset="0.437473" stop-color="#ED8C1B" />
<stop offset="1" stop-color="#FFB03A" />
</linearGradient>
<linearGradient id="paint8_linear_6903_5272" x1="27.0399" y1="4.89063" x2="26.4438" y2="7.23208" gradientUnits="userSpaceOnUse">
<stop offset="0.28598" stop-color="#FFE792" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint9_linear_6903_5272" x1="15.7507" y1="21.2636" x2="15.0789" y2="22.3573" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3900E" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint10_radial_6903_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.4461 20.092) rotate(-153.246) scale(2.11729 2.12981)">
<stop stop-color="#EA088B" />
<stop offset="1" stop-color="#E61E27" />
</radialGradient>
<linearGradient id="paint11_linear_6903_5272" x1="17.2036" y1="19.6538" x2="15.6801" y2="19.6538" gradientUnits="userSpaceOnUse">
<stop stop-color="white" stop-opacity="0.9" />
<stop offset="1" stop-color="white" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint12_linear_6903_5272" x1="24.2898" y1="23.1656" x2="28.0242" y2="21.2499" gradientUnits="userSpaceOnUse">
<stop stop-color="#E88105" />
<stop offset="1" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint13_linear_6903_5272" x1="24.2898" y1="24.5095" x2="28.0242" y2="26.4252" gradientUnits="userSpaceOnUse">
<stop offset="0.286458" stop-color="#DE7D07" />
<stop offset="0.817708" stop-color="#E37D02" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint14_linear_6903_5272" x1="25.2898" y1="22.875" x2="29.5273" y2="20.7422" gradientUnits="userSpaceOnUse">
<stop offset="0.75086" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint15_linear_6903_5272" x1="25.0739" y1="24.6393" x2="29.8242" y2="26.9765" gradientUnits="userSpaceOnUse">
<stop offset="0.792349" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint16_linear_6903_5272" x1="7.01785" y1="24.651" x2="2.02727" y2="27.1484" gradientUnits="userSpaceOnUse">
<stop offset="0.75156" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint17_linear_6903_5272" x1="6.8182" y1="22.8588" x2="2.72751" y2="20.9415" gradientUnits="userSpaceOnUse">
<stop offset="0.664576" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint18_linear_6903_5272" x1="23.3524" y1="10.7682" x2="22.9558" y2="22.8188" gradientUnits="userSpaceOnUse">
<stop offset="0.548257" stop-color="#FFDD65" />
<stop offset="1" stop-color="#FFDD65" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint19_linear_6903_5272" x1="26.5398" y1="15.4375" x2="25.2898" y2="11.9688" gradientUnits="userSpaceOnUse">
<stop stop-color="#EF8A47" />
<stop offset="1" stop-color="#EF8A47" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint20_radial_6903_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.868 7.70313) rotate(55.7389) scale(7.82686 9.38424)">
<stop stop-color="#FFDF70" />
<stop offset="1" stop-color="#FFDF70" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint21_linear_6903_5272" x1="25.0555" y1="10.4063" x2="25.0555" y2="12.75" gradientUnits="userSpaceOnUse">
<stop offset="0.432292" stop-color="#FFE7A3" />
<stop offset="1" stop-color="#FFDF83" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint22_linear_6903_5272" x1="22.3076" y1="26.9378" x2="15.2054" y2="30.4582" gradientUnits="userSpaceOnUse">
<stop stop-color="#DA7600" />
<stop offset="1" stop-color="#DA8400" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint23_linear_6903_5272" x1="28.957" y1="30.4034" x2="20.9883" y2="30.4034" gradientUnits="userSpaceOnUse">
<stop offset="0.160663" stop-color="#F2CC26" />
<stop offset="0.642165" stop-color="#E99E20" />
<stop offset="0.9925" stop-color="#E3821D" />
<stop offset="1" stop-color="#E3801D" />
</linearGradient>
<linearGradient id="paint24_linear_6903_5272" x1="28.957" y1="30.4034" x2="20.9883" y2="30.5625" gradientUnits="userSpaceOnUse">
<stop offset="0.865694" stop-color="#E3821D" stop-opacity="0" />
<stop offset="1" stop-color="#D37516" />
</linearGradient>
<linearGradient id="paint25_linear_6903_5272" x1="26.621" y1="30.4034" x2="34.8085" y2="30.4034" gradientUnits="userSpaceOnUse">
<stop stop-color="#E99E20" stop-opacity="0" />
<stop offset="1" stop-color="#E99E20" />
</linearGradient>
<linearGradient id="paint26_linear_6903_5272" x1="23.7852" y1="22" x2="22.8242" y2="22" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB445" />
<stop offset="1" stop-color="#E9A239" stop-opacity="0.921875" />
</linearGradient>
<linearGradient id="paint27_linear_6903_5272" x1="22.3398" y1="22.1094" x2="23.621" y2="22.0312" gradientUnits="userSpaceOnUse">
<stop offset="0.366667" stop-color="#D6850E" />
<stop offset="1" stop-color="#CC861F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint28_linear_6903_5272" x1="23.4492" y1="24.2188" x2="23.4492" y2="22.6484" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF91B9" />
<stop offset="1" stop-color="#FD679E" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint29_linear_6903_5272" x1="26.832" y1="22" x2="25.871" y2="22" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB445" />
<stop offset="1" stop-color="#E9A239" stop-opacity="0.921875" />
</linearGradient>
<linearGradient id="paint30_linear_6903_5272" x1="25.4335" y1="22.0469" x2="26.371" y2="22.0469" gradientUnits="userSpaceOnUse">
<stop offset="0.366667" stop-color="#D6850E" />
<stop offset="1" stop-color="#CC861F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint31_linear_6903_5272" x1="26.4961" y1="24.2188" x2="26.4961" y2="22.6484" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF91B9" />
<stop offset="1" stop-color="#FD679E" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint32_linear_6903_5272" x1="27.082" y1="23.3047" x2="26.5405" y2="18.9978" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC270" />
<stop offset="0.769643" stop-color="#FFE2BB" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint33_linear_6903_5272" x1="24.0351" y1="23.3047" x2="23.4936" y2="18.9978" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC270" />
<stop offset="0.769643" stop-color="#FFE2BB" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint34_linear_6903_5272" x1="24.0351" y1="23.3047" x2="23.4936" y2="18.9978" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC270" />
<stop offset="0.769643" stop-color="#FFE2BB" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint35_linear_6903_5272" x1="16.7421" y1="17.596" x2="18.0686" y2="26.3006" gradientUnits="userSpaceOnUse">
<stop stop-color="#CF8209" />
<stop offset="1" stop-color="#C88216" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint36_linear_6903_5272" x1="25.4208" y1="17.6157" x2="26.6701" y2="25.8141" gradientUnits="userSpaceOnUse">
<stop stop-color="#CF8209" />
<stop offset="1" stop-color="#C88216" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint37_radial_6903_5272" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16 28.5009) rotate(-90) scale(5.91493 9.37069)">
<stop stop-color="#F70A8D" />
<stop offset="1" stop-color="#89029C" />
</radialGradient>
<linearGradient id="paint38_linear_6903_5272" x1="11.0117" y1="30.4034" x2="3.04297" y2="30.4034" gradientUnits="userSpaceOnUse">
<stop offset="0.160663" stop-color="#F2CC26" />
<stop offset="0.642165" stop-color="#E99E20" />
<stop offset="0.9925" stop-color="#E3821D" />
<stop offset="1" stop-color="#E3801D" />
</linearGradient>
<linearGradient id="paint39_linear_6903_5272" x1="11.0117" y1="30.4034" x2="3.04297" y2="30.5625" gradientUnits="userSpaceOnUse">
<stop offset="0.865694" stop-color="#E3821D" stop-opacity="0" />
<stop offset="1" stop-color="#D37516" />
</linearGradient>
<linearGradient id="paint40_linear_6903_5272" x1="8.67572" y1="30.4034" x2="16.8632" y2="30.4034" gradientUnits="userSpaceOnUse">
<stop stop-color="#E99E20" stop-opacity="0" />
<stop offset="1" stop-color="#E99E20" />
</linearGradient>
<linearGradient id="paint41_linear_6903_5272" x1="5.83984" y1="22" x2="4.87885" y2="22" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB445" />
<stop offset="1" stop-color="#E9A239" stop-opacity="0.921875" />
</linearGradient>
<linearGradient id="paint42_linear_6903_5272" x1="4.39447" y1="22.1094" x2="5.67572" y2="22.0312" gradientUnits="userSpaceOnUse">
<stop offset="0.366667" stop-color="#D6850E" />
<stop offset="1" stop-color="#CC861F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint43_linear_6903_5272" x1="5.50391" y1="24.2188" x2="5.50391" y2="22.6484" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF91B9" />
<stop offset="1" stop-color="#FD679E" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint44_linear_6903_5272" x1="8.88672" y1="22" x2="7.92572" y2="22" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB445" />
<stop offset="1" stop-color="#E9A239" stop-opacity="0.921875" />
</linearGradient>
<linearGradient id="paint45_linear_6903_5272" x1="7.48822" y1="22.0469" x2="8.42572" y2="22.0469" gradientUnits="userSpaceOnUse">
<stop offset="0.366667" stop-color="#D6850E" />
<stop offset="1" stop-color="#CC861F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint46_linear_6903_5272" x1="8.55078" y1="24.2188" x2="8.55078" y2="22.6484" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF91B9" />
<stop offset="1" stop-color="#FD679E" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint47_linear_6903_5272" x1="9.13666" y1="23.3047" x2="8.59516" y2="18.9978" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC270" />
<stop offset="0.769643" stop-color="#FFE2BB" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint48_linear_6903_5272" x1="6.08978" y1="23.3047" x2="5.54828" y2="18.9978" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC270" />
<stop offset="0.769643" stop-color="#FFE2BB" stop-opacity="0" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 42 KiB

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,345 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint0_linear_28327_2053)" />
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint1_linear_28327_2053)" />
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint2_linear_28327_2053)" />
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint3_linear_28327_2053)" />
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint4_linear_28327_2053)" />
<path d="M27.9617 26.0652V6.42191C27.9617 4.98441 26.6257 4.28128 25.532 5.29691L21.5184 9.24913C21.3313 9.43334 21.0793 9.53659 20.8167 9.53659H11.2204C10.9578 9.53659 10.7058 9.43334 10.5188 9.24913L6.50513 5.29691C5.41138 4.28128 4.07544 4.98441 4.07544 6.42191V26.0652C4.07544 28.277 5.83537 30.0703 8.00597 30.0703H24.0312C26.2018 30.0703 27.9617 28.277 27.9617 26.0652Z" fill="url(#paint5_radial_28327_2053)" />
<g filter="url(#filter0_f_28327_2053)">
<rect x="5.05408" y="21.8203" width="2.72692" height="3.69801" rx="1.36346" fill="#D67908" />
</g>
<g filter="url(#filter1_f_28327_2053)">
<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_28327_2053)" />
</g>
<path d="M6.13261 13.5034L8.93261 10.9034C9.25261 10.6034 9.25261 10.1234 8.93261 9.83344L5.91793 7.15626C5.53511 6.82032 4.74261 6.9297 4.74261 7.76344V12.9634C4.74261 13.7969 5.62261 13.9834 6.13261 13.5034Z" fill="url(#paint7_linear_28327_2053)" />
<g filter="url(#filter2_f_28327_2053)">
<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_28327_2053)" />
</g>
<g filter="url(#filter3_f_28327_2053)">
<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_28327_2053)">
<path d="M14.8054 20.6503L14.8697 20.6353C15.3433 20.5248 15.8365 20.53 16.3077 20.6503V20.6503C16.8288 20.7484 17.1399 21.3458 16.6999 21.7758L16.1491 22.3773C15.8791 22.6473 15.2863 22.6473 15.0163 22.3773L14.4733 21.7758C14.0241 21.2679 14.3054 20.7601 14.8054 20.6503Z" fill="url(#paint9_linear_28327_2053)" />
</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_28327_2053)" />
<g filter="url(#filter5_f_28327_2053)">
<ellipse cx="16.2703" cy="20.7414" rx="0.761719" ry="0.274258" fill="url(#paint11_linear_28327_2053)" />
</g>
<g filter="url(#filter6_f_28327_2053)">
<path d="M24.7851 22.8922L28.5898 21.0797" stroke="url(#paint12_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter7_f_28327_2053)">
<path d="M24.7851 24.9235L26.7117 26.2891" stroke="url(#paint13_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<path d="M25.5663 22.5391L29.371 20.7266" stroke="url(#paint14_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter8_f_28327_2053)">
<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_28327_2053)">
<ellipse cx="29.6059" cy="20.5805" rx="0.18384" ry="0.165827" transform="rotate(-22.7455 29.6059 20.5805)" fill="#FFDD86" />
</g>
<path d="M25.5636 24.6692L29.3652 26.4883" stroke="url(#paint15_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter10_f_28327_2053)">
<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_28327_2053)">
<ellipse cx="29.5405" cy="26.3682" rx="0.18384" ry="0.165827" transform="rotate(28.2981 29.5405 26.3682)" fill="#FFDD86" />
</g>
<path d="M6.5281 24.681L2.72654 26.5" stroke="url(#paint16_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter12_f_28327_2053)">
<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_28327_2053)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.942776 -0.333427 -0.333427 0.942776 6.64614 24.505)" fill="#FFDD86" />
</g>
<path d="M6.53969 22.5243L2.72612 20.7305" stroke="url(#paint17_linear_28327_2053)" stroke-width="1.2" stroke-miterlimit="10" stroke-linecap="round" />
<g filter="url(#filter14_f_28327_2053)">
<path d="M6.67403 22.4989L2.86046 20.7052" stroke="#FF8485" stroke-width="0.3" stroke-miterlimit="10" stroke-linecap="round" />
</g>
<g filter="url(#filter15_f_28327_2053)">
<ellipse rx="0.18384" ry="0.165827" transform="matrix(-0.338111 -0.941106 -0.941106 0.338111 6.75078 22.5044)" 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_28327_2053)" />
<g filter="url(#filter16_f_28327_2053)">
<path d="M19.4316 20.4682C19.0065 20.4682 18.6563 20.1251 18.6563 19.7086V18.2872C18.6563 17.8707 19.0065 17.5276 19.4316 17.5276C19.8568 17.5276 20.2069 17.8707 20.2069 18.2872V19.7086C20.2069 20.1374 19.8568 20.4682 19.4316 20.4682Z" fill="url(#paint19_linear_28327_2053)" />
</g>
<path d="M19.9707 20.0859C19.5456 20.0859 19.1954 19.7428 19.1954 19.3262V17.9049C19.1954 17.4883 19.5456 17.1453 19.9707 17.1453C20.3959 17.1453 20.7461 17.4883 20.7461 17.9049V19.3262C20.7461 19.7551 20.3959 20.0859 19.9707 20.0859Z" fill="url(#paint20_linear_28327_2053)" />
<path d="M19.9707 20.0859C19.5456 20.0859 19.1954 19.7428 19.1954 19.3262V17.9049C19.1954 17.4883 19.5456 17.1453 19.9707 17.1453C20.3959 17.1453 20.7461 17.4883 20.7461 17.9049V19.3262C20.7461 19.7551 20.3959 20.0859 19.9707 20.0859Z" fill="url(#paint21_linear_28327_2053)" />
<g filter="url(#filter17_f_28327_2053)">
<path d="M11.6054 20.4682C11.1802 20.4682 10.8301 20.1251 10.8301 19.7086V18.2872C10.8301 17.8707 11.1802 17.5276 11.6054 17.5276C12.0306 17.5276 12.3807 17.8707 12.3807 18.2872V19.7086C12.3807 20.1374 12.0306 20.4682 11.6054 20.4682Z" fill="url(#paint22_linear_28327_2053)" />
</g>
<path d="M12.029 20.0859C11.6038 20.0859 11.2537 19.7428 11.2537 19.3262V17.9049C11.2537 17.4883 11.6038 17.1453 12.029 17.1453C12.4541 17.1453 12.8043 17.4883 12.8043 17.9049V19.3262C12.8043 19.7551 12.4541 20.0859 12.029 20.0859Z" fill="url(#paint23_linear_28327_2053)" />
<path d="M12.029 20.0859C11.6038 20.0859 11.2537 19.7428 11.2537 19.3262V17.9049C11.2537 17.4883 11.6038 17.1453 12.029 17.1453C12.4541 17.1453 12.8043 17.4883 12.8043 17.9049V19.3262C12.8043 19.7551 12.4541 20.0859 12.029 20.0859Z" fill="url(#paint24_linear_28327_2053)" />
<g filter="url(#filter18_f_28327_2053)">
<circle cx="20.2069" cy="17.7638" r="0.236228" fill="#775B70" />
</g>
<g filter="url(#filter19_f_28327_2053)">
<circle cx="12.2652" cy="17.7638" r="0.236228" fill="#775B70" />
</g>
<g filter="url(#filter20_f_28327_2053)">
<rect x="22.1024" y="10.1016" width="4.5851" height="14.1857" fill="url(#paint25_linear_28327_2053)" />
</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(#paint26_linear_28327_2053)" />
<g filter="url(#filter21_f_28327_2053)">
<path d="M26.6336 7.36719L23.0086 10.4922L26.5555 13.3047L26.6336 7.36719Z" fill="url(#paint27_radial_28327_2053)" />
</g>
<g filter="url(#filter22_f_28327_2053)">
<path d="M23.7274 10.2891L26.2586 13.0234" stroke="url(#paint28_linear_28327_2053)" stroke-width="0.5" stroke-linecap="round" />
</g>
<g filter="url(#filter23_f_28327_2053)">
<path d="M5.22321 7.49927V13.407L8.69196 10.3176L5.22321 7.49927Z" fill="#FEB33E" />
</g>
<g filter="url(#filter24_f_28327_2053)">
<path d="M5.22321 7.72656V12.9141" stroke="#FFE7A3" stroke-width="0.15" stroke-linecap="round" />
</g>
<defs>
<filter id="filter0_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter1_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter2_f_28327_2053" x="24.8055" y="4.41406" width="3.41742" 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_28327_2053" />
</filter>
<filter id="filter3_f_28327_2053" x="2.94611" y="3.22656" width="8.94989" height="10.7285" 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_28327_2053" />
</filter>
<filter id="filter4_f_28327_2053" x="13.7482" y="20.056" width="3.66144" height="3.0238" 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_28327_2053" />
</filter>
<filter id="filter5_f_28327_2053" x="14.9086" y="19.8672" width="2.72344" height="1.74846" 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_28327_2053" />
</filter>
<filter id="filter6_f_28327_2053" x="23.6849" y="19.9796" width="6.005" height="4.0127" 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_28327_2053" />
</filter>
<filter id="filter7_f_28327_2053" x="23.6851" y="23.8235" width="4.12671" height="3.56567" 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_28327_2053" />
</filter>
<filter id="filter8_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter9_f_28327_2053" x="28.9246" y="19.9119" width="1.36261" height="1.33728" 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_28327_2053" />
</filter>
<filter id="filter10_f_28327_2053" x="24.8483" y="23.899" width="5.10168" height="3.11914" 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_28327_2053" />
</filter>
<filter id="filter11_f_28327_2053" x="28.8605" 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_28327_2053" />
</filter>
<filter id="filter12_f_28327_2053" x="2.14172" y="23.9108" width="5.10168" height="3.11914" 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_28327_2053" />
</filter>
<filter id="filter13_f_28327_2053" x="5.96417" y="23.837" 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_28327_2053" />
</filter>
<filter id="filter14_f_28327_2053" x="2.21045" y="20.0552" width="5.11365" height="3.09375" 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_28327_2053" />
</filter>
<filter id="filter15_f_28327_2053" x="6.08276" y="21.8225" width="1.33606" height="1.36377" 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_28327_2053" />
</filter>
<filter id="filter16_f_28327_2053" x="18.1563" y="17.0276" width="2.5506" height="3.94067" 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_28327_2053" />
</filter>
<filter id="filter17_f_28327_2053" x="10.3301" y="17.0276" width="2.5506" height="3.94067" 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_28327_2053" />
</filter>
<filter id="filter18_f_28327_2053" x="19.4707" y="17.0276" width="1.47247" height="1.47241" 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_28327_2053" />
</filter>
<filter id="filter19_f_28327_2053" x="11.529" y="17.0276" width="1.47247" height="1.47241" 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_28327_2053" />
</filter>
<filter id="filter20_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter21_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter22_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter23_f_28327_2053" 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_28327_2053" />
</filter>
<filter id="filter24_f_28327_2053" x="4.64819" y="7.15161" width="1.15002" height="6.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_28327_2053" />
</filter>
<linearGradient id="paint0_linear_28327_2053" x1="7.96361" y1="1.13036" x2="7.96361" y2="31.0703" 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_28327_2053" x1="8.88356" y1="11.9216" x2="1.25856" y2="11.9216" 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_28327_2053" x1="26.7586" y1="11.9216" x2="30.1961" y2="11.9216" 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_28327_2053" x1="16.0195" y1="-1.84378" x2="16.0186" y2="31.0703" gradientUnits="userSpaceOnUse">
<stop offset="0.77079" stop-color="#F59639" stop-opacity="0" />
<stop offset="1" stop-color="#FF63C4" />
</linearGradient>
<linearGradient id="paint4_linear_28327_2053" x1="16.0186" y1="11.1328" x2="16.0186" y2="32.5703" gradientUnits="userSpaceOnUse">
<stop offset="0.854227" stop-color="white" stop-opacity="0" />
<stop offset="0.985362" stop-color="white" />
</linearGradient>
<radialGradient id="paint5_radial_28327_2053" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.1648 4.28909) rotate(55.8501) scale(5.39983 10.6704)">
<stop stop-color="#FFA720" />
<stop offset="0.921158" stop-color="#FFA720" stop-opacity="0" />
</radialGradient>
<linearGradient id="paint6_linear_28327_2053" 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_28327_2053" x1="10.6024" y1="10.6172" x2="4.74261" y2="10.0703" gradientUnits="userSpaceOnUse">
<stop offset="0.437473" stop-color="#ED8C1B" />
<stop offset="1" stop-color="#FFB03A" />
</linearGradient>
<linearGradient id="paint8_linear_28327_2053" 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_28327_2053" x1="15.7507" y1="22.3512" x2="15.0789" y2="23.445" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3900E" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<radialGradient id="paint10_radial_28327_2053" 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_28327_2053" 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_28327_2053" 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_28327_2053" 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_28327_2053" 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_28327_2053" 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_28327_2053" 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_28327_2053" x1="6.8182" y1="22.9292" x2="2.72751" y2="21.0118" gradientUnits="userSpaceOnUse">
<stop offset="0.15625" stop-color="#D64A38" />
<stop offset="1" stop-color="#FB7425" />
</linearGradient>
<linearGradient id="paint18_linear_28327_2053" 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_28327_2053" x1="20.6882" y1="19.6797" x2="21.6884" y2="23.1407" gradientUnits="userSpaceOnUse">
<stop stop-color="#EC9611" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint20_linear_28327_2053" x1="22.8992" y1="18.2109" x2="18.4305" y2="18.2109" gradientUnits="userSpaceOnUse">
<stop stop-color="#432A3D" />
<stop offset="0.567708" stop-color="#684E61" />
<stop offset="0.765625" stop-color="#43253D" />
</linearGradient>
<linearGradient id="paint21_linear_28327_2053" x1="20.3211" y1="18.0546" x2="20.3211" y2="20.3515" gradientUnits="userSpaceOnUse">
<stop offset="0.326531" stop-color="#43253D" stop-opacity="0" />
<stop offset="1" stop-color="#43253D" />
</linearGradient>
<linearGradient id="paint22_linear_28327_2053" x1="12.862" y1="19.6797" x2="13.8622" y2="23.1407" gradientUnits="userSpaceOnUse">
<stop stop-color="#E89311" />
<stop offset="1" stop-color="#EA9D26" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint23_linear_28327_2053" x1="14.9575" y1="18.2109" x2="10.4887" y2="18.2109" gradientUnits="userSpaceOnUse">
<stop stop-color="#432A3D" />
<stop offset="0.567708" stop-color="#684E61" />
<stop offset="0.765625" stop-color="#43253D" />
</linearGradient>
<linearGradient id="paint24_linear_28327_2053" x1="12.3793" y1="18.0546" x2="12.3793" y2="20.3515" gradientUnits="userSpaceOnUse">
<stop offset="0.326531" stop-color="#43253D" stop-opacity="0" />
<stop offset="1" stop-color="#43253D" />
</linearGradient>
<linearGradient id="paint25_linear_28327_2053" 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="paint26_linear_28327_2053" 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="paint27_radial_28327_2053" 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="paint28_linear_28327_2053" 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>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,8 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29 8H3V9H29V8ZM29 13H3V14H29V13ZM3 18H29V19H3V18ZM29 23H3V24H29V23Z" fill="#D3D3D3" />
<path d="M8 2C4.68629 2 2 4.68629 2 8V24C2 27.3137 4.68629 30 8 30H24C27.3137 30 30 27.3137 30 24V8C30 4.68629 27.3137 2 24 2H8ZM8 4H24C26.2091 4 28 5.79086 28 8V24C28 26.2091 26.2091 28 24 28H8C5.79086 28 4 26.2091 4 24V8C4 5.79086 5.79086 4 8 4Z" fill="#FF6723" />
<path d="M6 8C6 7.17157 6.67157 6.5 7.5 6.5C8.32843 6.5 9 7.17157 9 8C9 7.17157 9.67157 6.5 10.5 6.5C11.3284 6.5 12 7.17157 12 8C12 7.17157 12.6716 6.5 13.5 6.5C14.3284 6.5 15 7.17157 15 8C15 7.17157 15.6716 6.5 16.5 6.5C17.3284 6.5 18 7.17157 18 8V9C18 9.82843 17.3284 10.5 16.5 10.5C15.6716 10.5 15 9.82843 15 9C15 9.82843 14.3284 10.5 13.5 10.5C12.6716 10.5 12 9.82843 12 9C12 9.82843 11.3284 10.5 10.5 10.5C9.67157 10.5 9 9.82843 9 9C9 9.82843 8.32843 10.5 7.5 10.5C6.67157 10.5 6 9.82843 6 9V8ZM24.5 6.5C23.6716 6.5 23 7.17157 23 8V9C23 9.82843 23.6716 10.5 24.5 10.5C25.3284 10.5 26 9.82843 26 9V8C26 7.17157 25.3284 6.5 24.5 6.5Z" fill="#F70A8D" />
<path d="M6 13C6 12.1716 6.67157 11.5 7.5 11.5C8.32843 11.5 9 12.1716 9 13C9 12.1716 9.67157 11.5 10.5 11.5C11.3284 11.5 12 12.1716 12 13C12 12.1716 12.6716 11.5 13.5 11.5C14.3284 11.5 15 12.1716 15 13C15 12.1716 15.6716 11.5 16.5 11.5C17.3284 11.5 18 12.1716 18 13V14C18 14.8284 17.3284 15.5 16.5 15.5C15.6716 15.5 15 14.8284 15 14C15 14.8284 14.3284 15.5 13.5 15.5C12.6716 15.5 12 14.8284 12 14C12 14.8284 11.3284 15.5 10.5 15.5C9.67157 15.5 9 14.8284 9 14C9 14.8284 8.32843 15.5 7.5 15.5C6.67157 15.5 6 14.8284 6 14V13ZM24.5 11.5C23.6716 11.5 23 12.1716 23 13V14C23 14.8284 23.6716 15.5 24.5 15.5C25.3284 15.5 26 14.8284 26 14V13C26 12.1716 25.3284 11.5 24.5 11.5Z" fill="#00A6ED" />
<path d="M6 18C6 17.1716 6.67157 16.5 7.5 16.5C8.32843 16.5 9 17.1716 9 18C9 17.1716 9.67157 16.5 10.5 16.5C11.3284 16.5 12 17.1716 12 18C12 17.1716 12.6716 16.5 13.5 16.5C14.3284 16.5 15 17.1716 15 18C15 17.1716 15.6716 16.5 16.5 16.5C17.3284 16.5 18 17.1716 18 18V19C18 19.8284 17.3284 20.5 16.5 20.5C15.6716 20.5 15 19.8284 15 19C15 19.8284 14.3284 20.5 13.5 20.5C12.6716 20.5 12 19.8284 12 19C12 19.8284 11.3284 20.5 10.5 20.5C9.67157 20.5 9 19.8284 9 19C9 19.8284 8.32843 20.5 7.5 20.5C6.67157 20.5 6 19.8284 6 19V18ZM24.5 16.5C23.6716 16.5 23 17.1716 23 18V19C23 19.8284 23.6716 20.5 24.5 20.5C25.3284 20.5 26 19.8284 26 19V18C26 17.1716 25.3284 16.5 24.5 16.5Z" fill="#FCD53F" />
<path d="M6 23C6 22.1716 6.67157 21.5 7.5 21.5C8.32843 21.5 9 22.1716 9 23C9 22.1716 9.67157 21.5 10.5 21.5C11.3284 21.5 12 22.1716 12 23C12 22.1716 12.6716 21.5 13.5 21.5C14.3284 21.5 15 22.1716 15 23C15 22.1716 15.6716 21.5 16.5 21.5C17.3284 21.5 18 22.1716 18 23V24C18 24.8284 17.3284 25.5 16.5 25.5C15.6716 25.5 15 24.8284 15 24C15 24.8284 14.3284 25.5 13.5 25.5C12.6716 25.5 12 24.8284 12 24C12 24.8284 11.3284 25.5 10.5 25.5C9.67157 25.5 9 24.8284 9 24C9 24.8284 8.32843 25.5 7.5 25.5C6.67157 25.5 6 24.8284 6 24V23ZM24.5 21.5C23.6716 21.5 23 22.1716 23 23V24C23 24.8284 23.6716 25.5 24.5 25.5C25.3284 25.5 26 24.8284 26 24V23C26 22.1716 25.3284 21.5 24.5 21.5Z" fill="#00D26A" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,9 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3H10C10.55 3 11 3.45 11 4V5.43C11 5.74 10.74 6 10.43 6H9C8.45 6 8 5.55 8 5V4C8 3.45 8.45 3 9 3Z" fill="#635994" />
<path d="M11.99 29.03H13C13.55 29.03 14 28.58 14 28.03V27.03C14 26.48 13.55 26.03 13 26.03H10.57C10.26 26.03 10 26.29 10 26.6V27.04C10 28.14 10.89 29.03 11.99 29.03Z" fill="#635994" />
<path d="M18 27.03V28.03C18 28.58 18.45 29.03 19 29.03H20.03C21.12 29.03 22 28.15 22 27.06V26.6C22 26.28 21.74 26.03 21.43 26.03H19C18.45 26.03 18 26.48 18 27.03Z" fill="#635994" />
<path d="M24 5V4C24 3.45 23.55 3 23 3H22C21.45 3 21 3.45 21 4V5.43C21 5.74 21.26 6 21.57 6H23C23.55 6 24 5.55 24 5Z" fill="#635994" />
<path d="M28 11.03C28 10.48 28.45 10.03 29 10.03C29.55 10.03 30 10.48 30 11.03V15.03C30 15.58 29.55 16.03 29 16.03H28.57C28.26 16.03 28 16.28 28 16.6V17.06C28 18.15 27.12 19.03 26.03 19.03H25.57C25.26 19.03 25 19.28 25 19.6V24.04C25 25.14 24.11 26.03 23.01 26.03H22.57C22.26 26.03 22 25.78 22 25.46V22.6C22 22.29 21.75 22.03 21.43 22.03H10.57C10.26 22.03 10 22.28 10 22.6V25.46C10 25.77 9.75 26.03 9.43 26.03H9C7.9 26.03 7 25.13 7 24.03V19.6C7 19.29 6.74 19.03 6.43 19.03H6C4.9 19.03 4 18.13 4 17.03V16.6C4 16.29 3.74 16.03 3.43 16.03H3C2.45 16.03 2 15.58 2 15.03V11.03C2 10.48 2.45 10.03 3 10.03H3.03C3.58 10.03 4.03 10.48 4.03 11.03V12.46C4.03 12.78 4.28 13.03 4.6 13.03L6.4 13.02C6.7 13.01 6.96 12.8 7 12.51C7.24 10.7 8.71 9.29 10.53 9.06C10.8 9.03 11 8.78 11 8.5V6.57C11 6.26 11.26 6 11.58 6H11.88C13.05 6 14 6.95 14 8.12V8.46C14 8.78 14.26 9.03 14.57 9.03H17.43C17.74 9.03 18 8.78 18 8.46V8.07C18 6.93 18.93 6 20.07 6H20.43C20.74 6 21 6.26 21 6.57V8.5C21 8.78 21.2 9.03 21.47 9.06C23.29 9.28 24.74 10.7 24.97 12.52C25.01 12.82 25.27 13.03 25.57 13.03H27.43C27.74 13.03 28 12.78 28 12.46V11.03Z" fill="#635994" />
<path d="M10 15.9824C10 16.5466 10.4455 17 10.9999 17C11.5543 17 12.0097 16.5466 11.9998 15.9824V14.0176C11.9998 13.4534 11.5543 13 10.9999 13C10.4455 13 10 13.4534 10 14.0176V15.9824Z" fill="#402A32" />
<path d="M20 15.9824C20 16.5466 20.4455 17 21 17C21.5545 17 22 16.5365 22 15.9824V14.0176C22 13.4534 21.5545 13 21 13C20.4455 13 20 13.4534 20 14.0176V15.9824Z" fill="#402A32" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,8 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 1C3.11929 1 2 2.11929 2 3.5V26.5C2 27.8807 3.11929 29 4.5 29H7V29.5C7 30.3284 7.67157 31 8.5 31H25.5C26.3284 31 27 30.3284 27 29.5V6.5C27 5.67157 26.3284 5 25.5 5H20.9142L17.6464 1.73223C17.1776 1.26339 16.5417 1 15.8787 1H4.5Z" fill="#B4ACBC" />
<path d="M3 3.5C3 2.67157 3.67157 2 4.5 2H15.8787C16.2765 2 16.658 2.15804 16.9393 2.43934L22.5607 8.06066C22.842 8.34196 23 8.7235 23 9.12132V26.5C23 27.3284 22.3284 28 21.5 28H4.5C3.67157 28 3 27.3284 3 26.5V3.5Z" fill="#F3EEF8" />
<path d="M6.5 11C6.22386 11 6 11.2239 6 11.5C6 11.7761 6.22386 12 6.5 12H19.5C19.7761 12 20 11.7761 20 11.5C20 11.2239 19.7761 11 19.5 11H6.5ZM6.5 14C6.22386 14 6 14.2239 6 14.5C6 14.7761 6.22386 15 6.5 15H19.5C19.7761 15 20 14.7761 20 14.5C20 14.2239 19.7761 14 19.5 14H6.5ZM6 17.5C6 17.2239 6.22386 17 6.5 17H19.5C19.7761 17 20 17.2239 20 17.5C20 17.7761 19.7761 18 19.5 18H6.5C6.22386 18 6 17.7761 6 17.5ZM6.5 20C6.22386 20 6 20.2239 6 20.5C6 20.7761 6.22386 21 6.5 21H14.5C14.7761 21 15 20.7761 15 20.5C15 20.2239 14.7761 20 14.5 20H6.5Z" fill="#998EA4" />
<path d="M16 2.00488C16.3534 2.03355 16.6868 2.18674 16.9393 2.43931L22.5607 8.06063C22.8132 8.3132 22.9664 8.64656 22.9951 8.99997H17.5C16.6716 8.99997 16 8.3284 16 7.49997V2.00488Z" fill="#CDC4D6" />
<path d="M22.3606 13.1177C22.4507 13.0417 22.5648 13 22.6828 13H25.5002C25.7763 13 26.0002 13.2239 26.0002 13.5V15.5C26.0002 15.7761 25.7763 16 25.5002 16H22.6828C22.5648 16 22.4507 15.9583 22.3606 15.8823L21.1739 14.8823C20.9368 14.6826 20.9368 14.3174 21.1739 14.1177L22.3606 13.1177Z" fill="#F70A8D" />
<path d="M25.3606 20.1177C25.4507 20.0417 25.5648 20 25.6828 20H28.5002C28.7763 20 29.0002 20.2239 29.0002 20.5V22.5C29.0002 22.7761 28.7763 23 28.5002 23H25.6828C25.5648 23 25.4507 22.9583 25.3606 22.8823L24.1739 21.8823C23.9368 21.6826 23.9368 21.3174 24.1739 21.1177L25.3606 20.1177Z" fill="#F9C23C" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,14 @@
<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="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="M12.6213 17.0149C12.8892 17.0819 13.052 17.3534 12.9851 17.6213C12.8392 18.2046 12.5727 18.6457 12.2151 18.9507C11.8588 19.2546 11.445 19.3955 11.0498 19.435C10.6581 19.4742 10.2759 19.4153 9.95546 19.3117C9.64377 19.2108 9.34567 19.0528 9.14645 18.8535C8.95118 18.6583 8.95118 18.3417 9.14645 18.1464C9.34171 17.9512 9.65829 17.9512 9.85355 18.1464C9.90433 18.1972 10.0437 18.2892 10.2633 18.3602C10.4741 18.4284 10.7169 18.4633 10.9502 18.44C11.18 18.417 11.3912 18.3391 11.5662 18.1899C11.7398 18.0418 11.9108 17.7954 12.0149 17.3787C12.0819 17.1108 12.3534 16.948 12.6213 17.0149Z" fill="#402A32" />
<path d="M16 24.5C14.6098 24.5 13.6831 25.3767 13.416 25.7773C13.2628 26.0071 12.9524 26.0692 12.7226 25.916C12.4929 25.7628 12.4308 25.4524 12.584 25.2226C12.9456 24.6803 13.9679 23.709 15.5 23.5291V21C15.5 20.7239 15.7239 20.5 16 20.5C16.2761 20.5 16.5 20.7239 16.5 21V23.5291C18.0321 23.709 19.0544 24.6803 19.416 25.2226C19.5692 25.4524 19.5071 25.7628 19.2773 25.916C19.0476 26.0692 18.7372 26.0071 18.584 25.7773C18.3169 25.3767 17.3902 24.5 16 24.5Z" fill="#402A32" />
<path d="M19.0149 17.6213C18.948 17.3534 19.1108 17.0819 19.3787 17.0149C19.6466 16.948 19.9181 17.1108 19.9851 17.3787C20.0892 17.7954 20.2602 18.0418 20.4338 18.1899C20.6088 18.3391 20.82 18.417 21.0498 18.44C21.2831 18.4633 21.5259 18.4284 21.7367 18.3602C21.9563 18.2892 22.0957 18.1972 22.1464 18.1464C22.3417 17.9512 22.6583 17.9512 22.8536 18.1464C23.0488 18.3417 23.0488 18.6583 22.8536 18.8535C22.6543 19.0528 22.3562 19.2108 22.0445 19.3117C21.7241 19.4153 21.3419 19.4742 20.9502 19.435C20.555 19.3955 20.1412 19.2546 19.7849 18.9507C19.4273 18.6457 19.1608 18.2046 19.0149 17.6213Z" fill="#402A32" />
<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="M8 23C8 21.8954 8.89543 21 10 21C11.1046 21 12 21.8954 12 23V26C12 27.1046 11.1046 28 10 28C8.89543 28 8 27.1046 8 26V23Z" fill="#5092FF" />
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,21 @@
<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="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="M12 24V30L7.91837 30C5.76327 30 4 28.1739 4 25.942V19.9996C4.83566 19.3719 5.87439 19 7 19C9.76142 19 12 21.2386 12 24Z" fill="#FF822D" />
<path d="M24.0816 30L20 30V24C20 21.2386 22.2386 19 25 19C26.1256 19 27.1643 19.3719 28 19.9996V25.8406C28 28.0725 26.2367 30 24.0816 30Z" fill="#FF822D" />
<path d="M17.0429 19H14.9571C14.5117 19 14.2886 19.5386 14.6036 19.8536L15.6465 20.8964C15.8417 21.0917 16.1583 21.0917 16.3536 20.8964L17.3965 19.8536C17.7114 19.5386 17.4884 19 17.0429 19Z" fill="#F70A8D" />
<path d="M7 20C4.79086 20 3 21.7909 3 24V30H11V24C11 21.7909 9.20914 20 7 20Z" fill="#FFB02E" />
<path d="M25 20C22.7909 20 21 21.7909 21 24V30H29V24C29 21.7909 27.2091 20 25 20Z" fill="#FFB02E" />
<path d="M14 24C14 22.8954 14.8954 22 16 22C17.1046 22 18 22.8954 18 24V25C18 26.1046 17.1046 27 16 27C14.8954 27 14 26.1046 14 25V24Z" fill="#BB1D80" />
<path d="M11.5 19C13.433 19 15 17.433 15 15.5C15 13.567 13.433 12 11.5 12C9.567 12 8 13.567 8 15.5C8 17.433 9.567 19 11.5 19Z" fill="white" />
<path d="M20.5 19C22.433 19 24 17.433 24 15.5C24 13.567 22.433 12 20.5 12C18.567 12 17 13.567 17 15.5C17 17.433 18.567 19 20.5 19Z" fill="white" />
<path d="M5 20.5351C5.30951 20.356 5.64523 20.2173 6 20.126V23.5C6 23.7761 5.77614 24 5.5 24C5.22386 24 5 23.7761 5 23.5V20.5351Z" fill="#FF6723" />
<path d="M8 20.126C8.35477 20.2173 8.69049 20.356 9 20.5351V23.5C9 23.7761 8.77614 24 8.5 24C8.22386 24 8 23.7761 8 23.5V20.126Z" fill="#FF6723" />
<path d="M23 20.5351C23.3095 20.356 23.6452 20.2173 24 20.126V23.5C24 23.7761 23.7761 24 23.5 24C23.2239 24 23 23.7761 23 23.5V20.5351Z" fill="#FF6723" />
<path d="M26 20.126C26.3548 20.2173 26.6905 20.356 27 20.5351V23.5C27 23.7761 26.7761 24 26.5 24C26.2239 24 26 23.7761 26 23.5V20.126Z" fill="#FF6723" />
</svg>

After

Width:  |  Height:  |  Size: 3.6 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,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="M12 17C11.4477 17 11 17.4477 11 18V19C11 19.5523 11.4477 20 12 20C12.5523 20 13 19.5523 13 19V18C13 17.4477 12.5523 17 12 17Z" fill="#402A32" />
<path d="M20 17C19.4477 17 19 17.4477 19 18V19C19 19.5523 19.4477 20 20 20C20.5523 20 21 19.5523 21 19V18C21 17.4477 20.5523 17 20 17Z" fill="#402A32" />
<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" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,8 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9989 29.9978C25.3333 29.9978 29.9978 23.7303 29.9978 15.9989C29.9978 8.26751 25.3333 2 15.9989 2C6.66443 2 2 8.26751 2 15.9989C2 23.7303 6.66443 29.9978 15.9989 29.9978Z" fill="#FFB02E"/>
<path d="M10.4191 16.2244C12.742 16.2244 14.6251 14.3414 14.6251 12.0185C14.6251 9.69557 12.742 7.8125 10.4191 7.8125C8.09621 7.8125 6.21313 9.69557 6.21313 12.0185C6.21313 14.3414 8.09621 16.2244 10.4191 16.2244Z" fill="white"/>
<path d="M21.5683 16.3011C23.9123 16.3011 25.8126 14.4009 25.8126 12.0568C25.8126 9.71274 23.9123 7.8125 21.5683 7.8125C19.2242 7.8125 17.324 9.71274 17.324 12.0568C17.324 14.4009 19.2242 16.3011 21.5683 16.3011Z" fill="white"/>
<path d="M14 12C14 13.6569 12.6569 15 11 15C9.34315 15 8 13.6569 8 12C8 10.3431 9.34315 9 11 9C12.6569 9 14 10.3431 14 12Z" fill="#402A32"/>
<path d="M24 12C24 13.6569 22.6569 15 21 15C19.3431 15 18 13.6569 18 12C18 10.3431 19.3431 9 21 9C22.6569 9 24 10.3431 24 12Z" fill="#402A32"/>
<path d="M10.8944 22.4472C11.098 22.04 12.46 20 16 20C19.54 20 20.902 22.04 21.1056 22.4472C21.3526 22.9412 21.9532 23.1414 22.4472 22.8944C22.9412 22.6474 23.1414 22.0468 22.8944 21.5528C22.4314 20.6267 20.46 18 16 18C11.54 18 9.56864 20.6267 9.10557 21.5528C8.85858 22.0468 9.05881 22.6474 9.55279 22.8944C10.0468 23.1414 10.6474 22.9412 10.8944 22.4472Z" fill="#402A32"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 10C2 8.3431 3.34315 7 5 7H27C28.6569 7 30 8.3431 30 10V21.25C30 22.9069 28.6569 24.25 27 24.25H5C3.34315 24.25 2 22.9069 2 21.25V10Z" fill="#CDC4D6"/>
<path d="M4 9.5C4 9.2239 4.22386 9 4.5 9H5.5C5.77614 9 6 9.2239 6 9.5V10C6 10.2761 5.77614 10.5 5.5 10.5H4.5C4.22386 10.5 4 10.2761 4 10V9.5ZM4 13.5C4 13.2239 4.22386 13 4.5 13H5.5C5.77614 13 6 13.2239 6 13.5V14.5C6 14.7761 5.77614 15 5.5 15H4.5C4.22386 15 4 14.7761 4 14.5V13.5ZM6.5 16C6.22386 16 6 16.2239 6 16.5V17.5C6 17.7761 6.22386 18 6.5 18H7.5C7.77614 18 8 17.7761 8 17.5V16.5C8 16.2239 7.77614 16 7.5 16H6.5ZM7.5 19C7.22386 19 7 19.2239 7 19.5V20.5C7 20.7761 7.22386 21 7.5 21H8.5C8.77614 21 9 20.7761 9 20.5V19.5C9 19.2239 8.77614 19 8.5 19H7.5ZM23.5 19C23.2239 19 23 19.2239 23 19.5V20.5C23 20.7761 23.2239 21 23.5 21H24.5C24.7761 21 25 20.7761 25 20.5V19.5C25 19.2239 24.7761 19 24.5 19H23.5ZM10 19.5C10 19.2239 10.2239 19 10.5 19H21.5C21.7761 19 22 19.2239 22 19.5V20.5C22 20.7761 21.7761 21 21.5 21H10.5C10.2239 21 10 20.7761 10 20.5V19.5ZM9 16.5C9 16.2239 9.22386 16 9.5 16H10.5C10.7761 16 11 16.2239 11 16.5V17.5C11 17.7761 10.7761 18 10.5 18H9.5C9.22386 18 9 17.7761 9 17.5V16.5ZM12.5 16C12.2239 16 12 16.2239 12 16.5V17.5C12 17.7761 12.2239 18 12.5 18H13.5C13.7761 18 14 17.7761 14 17.5V16.5C14 16.2239 13.7761 16 13.5 16H12.5ZM15 16.5C15 16.2239 15.2239 16 15.5 16H16.5C16.7761 16 17 16.2239 17 16.5V17.5C17 17.7761 16.7761 18 16.5 18H15.5C15.2239 18 15 17.7761 15 17.5V16.5ZM18.5 16C18.2239 16 18 16.2239 18 16.5V17.5C18 17.7761 18.2239 18 18.5 18H19.5C19.7761 18 20 17.7761 20 17.5V16.5C20 16.2239 19.7761 16 19.5 16H18.5ZM21 16.5C21 16.2239 21.2239 16 21.5 16H22.5C22.7761 16 23 16.2239 23 16.5V17.5C23 17.7761 22.7761 18 22.5 18H21.5C21.2239 18 21 17.7761 21 17.5V16.5ZM24.5 16C24.2239 16 24 16.2239 24 16.5V17.5C24 17.7761 24.2239 18 24.5 18H25.5C25.7761 18 26 17.7761 26 17.5V16.5C26 16.2239 25.7761 16 25.5 16H24.5ZM7.5 13C7.22386 13 7 13.2239 7 13.5V14.5C7 14.7761 7.22386 15 7.5 15H8.5C8.77614 15 9 14.7761 9 14.5V13.5C9 13.2239 8.77614 13 8.5 13H7.5ZM10 13.5C10 13.2239 10.2239 13 10.5 13H11.5C11.7761 13 12 13.2239 12 13.5V14.5C12 14.7761 11.7761 15 11.5 15H10.5C10.2239 15 10 14.7761 10 14.5V13.5ZM13.5 13C13.2239 13 13 13.2239 13 13.5V14.5C13 14.7761 13.2239 15 13.5 15H14.5C14.7761 15 15 14.7761 15 14.5V13.5C15 13.2239 14.7761 13 14.5 13H13.5ZM16 13.5C16 13.2239 16.2239 13 16.5 13H17.5C17.7761 13 18 13.2239 18 13.5V14.5C18 14.7761 17.7761 15 17.5 15H16.5C16.2239 15 16 14.7761 16 14.5V13.5ZM19.5 13C19.2239 13 19 13.2239 19 13.5V14.5C19 14.7761 19.2239 15 19.5 15H20.5C20.7761 15 21 14.7761 21 14.5V13.5C21 13.2239 20.7761 13 20.5 13H19.5ZM22 13.5C22 13.2239 22.2239 13 22.5 13H23.5C23.7761 13 24 13.2239 24 13.5V14.5C24 14.7761 23.7761 15 23.5 15H22.5C22.2239 15 22 14.7761 22 14.5V13.5ZM25.5 13C25.2239 13 25 13.2239 25 13.5V14.5C25 14.7761 25.2239 15 25.5 15H26.5C26.7761 15 27 14.7761 27 14.5V13.5C27 13.2239 26.7761 13 26.5 13H25.5ZM10.5 9C10.2239 9 10 9.2239 10 9.5V10C10 10.2761 10.2239 10.5 10.5 10.5H11.5C11.7761 10.5 12 10.2761 12 10V9.5C12 9.2239 11.7761 9 11.5 9H10.5ZM13 9.5C13 9.2239 13.2239 9 13.5 9H14.5C14.7761 9 15 9.2239 15 9.5V10C15 10.2761 14.7761 10.5 14.5 10.5H13.5C13.2239 10.5 13 10.2761 13 10V9.5ZM16.5 9C16.2239 9 16 9.2239 16 9.5V10C16 10.2761 16.2239 10.5 16.5 10.5H17.5C17.7761 10.5 18 10.2761 18 10V9.5C18 9.2239 17.7761 9 17.5 9H16.5ZM19 9.5C19 9.2239 19.2239 9 19.5 9H20.5C20.7761 9 21 9.2239 21 9.5V10C21 10.2761 20.7761 10.5 20.5 10.5H19.5C19.2239 10.5 19 10.2761 19 10V9.5ZM22.5 9C22.2239 9 22 9.2239 22 9.5V10C22 10.2761 22.2239 10.5 22.5 10.5H23.5C23.7761 10.5 24 10.2761 24 10V9.5C24 9.2239 23.7761 9 23.5 9H22.5ZM25 9.5C25 9.2239 25.2239 9 25.5 9H26.5C26.7761 9 27 9.2239 27 9.5V10C27 10.2761 26.7761 10.5 26.5 10.5H25.5C25.2239 10.5 25 10.2761 25 10V9.5ZM2 23V21C2 22.6569 3.34315 24 5 24H27C28.6569 24 30 22.6569 30 21V23C30 24.6569 28.6569 26 27 26H5C3.34315 26 2 24.6569 2 23Z" fill="#998EA4"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

4
src/front/emoji/link.svg Normal file
View file

@ -0,0 +1,4 @@
<svg width="100%" height="100%" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.1475 21.1475C19.5275 20.7171 18.3006 21.0135 16.5275 21.7175L13.1175 25.1275C11.3475 26.8975 8.4775 26.8975 6.7175 25.1275C4.9475 23.3575 4.9475 20.4875 6.7175 18.7275L10.1275 15.3175H11.0489L13.4375 13.4475C14.7475 13.3075 16.1175 13.7175 17.1175 14.7275C18.1175 15.7375 18.5375 17.0975 18.3975 18.4075C19.1157 19.1907 20.0747 19.3579 20.8347 18.5979L21.7075 16.5375C21.4375 14.9975 20.7075 13.5175 19.5175 12.3275C18.3275 11.1375 16.8475 10.4075 15.3075 10.1375L13.1175 9.46387L10.6975 10.6975C9.8375 11.0775 9.0275 11.6175 8.3175 12.3275L4.3175 16.3275C1.2275 19.4175 1.2275 24.4475 4.3175 27.5375C7.4075 30.6275 12.4375 30.6275 15.5275 27.5375L19.5275 23.5375C20.2275 22.8275 20.7675 22.0175 21.1475 21.1475Z" fill="#9B9B9B" />
<path d="M27.5277 4.3175C24.4377 1.2275 19.4077 1.2275 16.3177 4.3175L12.3177 8.3175C11.6177 9.0275 11.0777 9.8375 10.6977 10.6975C12.1577 10.0475 13.7677 9.8575 15.3177 10.1275L18.7277 6.7175C20.4977 4.9475 23.3677 4.9475 25.1277 6.7175C26.8877 8.4875 26.8977 11.3575 25.1277 13.1175L21.7177 16.5275L21.1277 17.1175C20.3677 17.8775 19.3977 18.2875 18.4077 18.3975C17.0977 18.5375 15.7277 18.1275 14.7277 17.1175C13.7277 16.1075 13.3077 14.7475 13.4477 13.4375C12.4477 13.5475 11.4877 13.9575 10.7277 14.7175L10.1377 15.3075C10.4077 16.8475 11.1377 18.3275 12.3277 19.5175C13.5177 20.7075 14.9977 21.4375 16.5377 21.7075C18.0877 21.9775 19.6977 21.7875 21.1577 21.1375C22.0177 20.7575 22.8277 20.2175 23.5377 19.5075L27.5377 15.5075C30.6177 12.4375 30.6177 7.4075 27.5277 4.3175Z" fill="#BEBEBE" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 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="M16 2C12.6863 2 10 4.68629 10 8V11C10 14.3137 12.6863 17 16 17C19.3137 17 22 14.3137 22 11V8C22 4.68629 19.3137 2 16 2ZM16 4.5C17.933 4.5 19.5 6.067 19.5 8V11C19.5 12.933 17.933 14.5 16 14.5C14.067 14.5 12.5 12.933 12.5 11V8C12.5 6.067 14.067 4.5 16 4.5Z" fill="#D3D3D3" />
<path d="M5 14C5 12.3431 6.34315 11 8 11H24C25.6569 11 27 12.3431 27 14V27C27 28.6569 25.6569 30 24 30H8C6.34315 30 5 28.6569 5 27V14Z" fill="#F9C23C" />
<path d="M17.5 20.5002C18.1072 20.0441 18.5 19.3179 18.5 18.5C18.5 17.1193 17.3807 16 16 16C14.6193 16 13.5 17.1193 13.5 18.5C13.5 19.3179 13.8928 20.0441 14.5 20.5002V24C14.5 24.8284 15.1716 25.5 16 25.5C16.8284 25.5 17.5 24.8284 17.5 24V20.5002Z" fill="#433B6B" />
</svg>

After

Width:  |  Height:  |  Size: 816 B

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="M3 13C3 18.5228 7.47715 23 13 23C18.5228 23 23 18.5228 23 13C23 7.47715 18.5228 3 13 3C7.47715 3 3 7.47715 3 13Z" fill="#00A6ED" />
<path d="M18.3481 7.73205C18.9004 8.68864 18.7665 9.79989 18.049 10.2141C17.3316 10.6283 16.3023 10.1886 15.75 9.23205C15.1977 8.27547 15.3316 7.16421 16.049 6.75C16.7665 6.33579 17.7958 6.77547 18.3481 7.73205Z" fill="white" />
<path d="M2 13C2 19.0751 6.92487 24 13 24C15.2952 24 17.4262 23.2971 19.1895 22.0947C18.9147 23.3086 19.2498 24.6327 20.195 25.5779L23.3769 28.7599C24.8414 30.2243 27.2158 30.2243 28.6803 28.7599C30.1447 27.2954 30.1447 24.921 28.6803 23.4566L25.4983 20.2746C24.5607 19.3371 23.2503 18.9997 22.0445 19.2626C23.2774 17.4852 24 15.327 24 13C24 6.92487 19.0751 2 13 2C6.92487 2 2 6.92487 2 13ZM22 13C22 17.9706 17.9706 22 13 22C8.02944 22 4 17.9706 4 13C4 8.02944 8.02944 4 13 4C17.9706 4 22 8.02944 22 13Z" fill="#533566" />
</svg>

After

Width:  |  Height:  |  Size: 1,005 B

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="M21.0163 5.15966C19.1428 5.48746 17.5594 7.06018 16.6978 8.09396C16.3495 8.51195 15.6505 8.51195 15.3022 8.09396C14.4406 7.06018 12.8572 5.48746 10.9837 5.15966C6.26039 4.32908 3.40517 6.85743 2.40485 10.0008L5.60146 14.2955L6.09508 21.6282C9.14914 25.3465 13.0775 28.3459 14.9355 29.6684C15.577 30.125 16.4229 30.1248 17.0642 29.668C19.646 27.8288 26.2261 22.7486 28.9042 17.0021L27.3547 13.195L26.5453 5.99222C25.1352 5.13927 23.2927 4.75936 21.0163 5.15966Z" fill="#F92F60" />
<path d="M29.5949 10H2.40511C1.92106 11.5205 1.87107 13.185 2.25363 14.6829C2.45195 15.463 2.73767 16.2373 3.0923 17H28.9052C29.2605 16.2373 29.547 15.463 29.7464 14.6829C30.1289 13.185 30.0789 11.5205 29.5949 10Z" fill="#E6E6E6" />
<path d="M2.86942 16.4973L26.5285 5.98218C28.3793 7.09408 29.4886 9.01902 29.8599 11.0675L6.0959 21.6293C4.77942 20.0266 3.62532 18.2904 2.86942 16.4973Z" fill="#F4F4F4" />
</svg>

After

Width:  |  Height:  |  Size: 1,007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View file

Before

Width:  |  Height:  |  Size: 867 KiB

After

Width:  |  Height:  |  Size: 867 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -1,25 +1,25 @@
{
"name": "english",
"substrings": {
"ContactLink": "<a class=\"text-backdrop italic\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>"
"ContactLink": "<a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>"
},
"strings": {
"AppTitleCobalt": "cobalt",
"LinkInput": "paste the link here",
"AboutSummary": "{appName} is your go-to place for downloads from social and media platforms. zero ads, trackers, or other creepy bullshit. simply paste a share link and you're ready to rock!",
"EmbedBriefDescription": "save what you love without ads, trackers, or other creepy bullshit.",
"AboutSummary": "cobalt is your go-to place for downloads from social and media platforms. zero ads, trackers, or other creepy bullshit. simply paste a share link and you're ready to rock!",
"EmbedBriefDescription": "save what you love. no ads, trackers, or other creepy bullshit.",
"MadeWithLove": "made with <3 by wukko",
"AccessibilityInputArea": "link input area",
"AccessibilityOpenAbout": "open about popup",
"AccessibilityDownloadButton": "download button",
"AccessibilityOpenSettings": "open settings popup",
"AccessibilityClosePopup": "close the popup",
"AccessibilityOpenDonate": "open donation popup",
"TitlePopupAbout": "what's {appName}?",
"TitlePopupAbout": "what's cobalt?",
"TitlePopupSettings": "settings",
"TitlePopupError": "uh-oh...",
"TitlePopupChangelog": "what's new?",
"TitlePopupDonate": "support {appName}",
"TitlePopupDownload": "how to continue?",
"TitlePopupDonate": "support cobalt",
"TitlePopupDownload": "how to save?",
"ErrorSomethingWentWrong": "something went wrong and i couldn't get anything for you. try again, but if issue persists, {ContactLink}.",
"ErrorUnsupported": "it seems like this service is not supported yet or your link is invalid. have you pasted the right link?",
"ErrorBrokenLink": "{s} is supported, but something is wrong with your link. maybe you didn't copy it fully?",
@ -29,8 +29,8 @@
"ErrorCouldntFetch": "i couldn't find anything about this link. check if it works and try again! some content may be region restricted, so keep that in mind.",
"ErrorLengthLimit": "i can't process videos longer than {s} minutes, so pick something shorter instead!",
"ErrorBadFetch": "something went wrong when i tried getting info about your link. are you sure it works? check if it does, and try again.",
"ErrorNoInternet": "there's no internet or {appName} api is down. check your connection and try again.",
"ErrorCantConnectToServiceAPI": "i couldn't connect to the service api. maybe it's down, or {appName} got blocked. try again, but if error persists, {ContactLink}.",
"ErrorNoInternet": "there's no internet or cobalt api is temporarily unavailable. check your connection and try again.",
"ErrorCantConnectToServiceAPI": "i couldn't connect to the service api. maybe it's down, or cobalt got blocked. try again, but if error persists, {ContactLink}.",
"ErrorEmptyDownload": "i don't see anything i could download by your link. try a different one!",
"ErrorLiveVideo": "this is a live video, i am yet to learn how to look into future. wait for the stream to finish and try again!",
"SettingsAppearanceSubtitle": "appearance",
@ -46,10 +46,9 @@
"AccessibilityEnableDownloadPopup": "ask what to do with downloads",
"SettingsQualityDescription": "if selected quality isn't available, closest one is used instead.",
"LinkGitHubChanges": "&gt;&gt; see previous commits and contribute on github",
"NoScriptMessage": "{appName} uses javascript for api requests and interactive interface. you have to allow javascript to use this site. there are no pesty scripts, pinky promise.",
"DownloadPopupDescriptionIOS": "easiest way to save videos on ios:\n1. add <a class=\"text-backdrop italic\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">this siri shortcut</a>.\n2. press \"share\" above and select \"save to photos\" in appeared share sheet.\nif asked, review the permission request popup on top, and press \"always allow\".\n\nalternative method: press and hold the download button, hide the video preview, and select \"download linked file\" to download.\nthen, open safari downloads, select the file you downloaded, open share menu, and finally press \"save video\".",
"NoScriptMessage": "cobalt uses javascript for api requests and interactive interface. you have to allow javascript to use this site. there are no pesty scripts, pinky promise.",
"DownloadPopupDescriptionIOS": "easiest way to save videos on ios:\n1. add <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">this siri shortcut</a>.\n2. press \"share\" above and select \"save to photos\" in appeared share sheet.\nif asked, review the permission request, and press \"always allow\".\n\nalternative method:\npress and hold the download button, hide the video preview, and select \"download linked file\" to download.\nthen, open safari downloads, select the file you downloaded, open share menu, and finally press \"save video\".",
"DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.",
"DownloadPopupWayToSave": "pick a way to save",
"ClickToCopy": "press to copy",
"Download": "download",
"CopyURL": "copy",
@ -88,15 +87,14 @@
"MediaPickerTitle": "pick what to save",
"MediaPickerExplanationPC": "click or right click to download what you want.",
"MediaPickerExplanationPhone": "press or press and hold to download what you want.",
"MediaPickerExplanationPhoneIOS": "press and hold, hide the preview, and then select \"download linked file\" to save.",
"TwitterSpaceWasntRecorded": "this twitter space wasn't recorded, so there's nothing to download. try another one!",
"ErrorCantProcess": "i couldn't process your request :(\nyou can try again, but if issue persists, please {ContactLink}.",
"ChangelogPressToHide": "collapse",
"Donate": "donate",
"DonateSub": "help me keep it up",
"DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's <span class=\"text-backdrop\">completely free to use</span>. but turns out developing and keeping up a web service used by over 150,000 people is not that easy.\n\nif you ever found {appName} useful and want to help continue its development and support, or simply want to thank the developer, consider chipping in! every cent helps and is VERY appreciated :D\n\ncurrently, i have big (scaling) plans, and i need your help. {appName}'s usage is growing daily, so i need to make up for it. <span class=\"text-backdrop\">donations are more appreciated than ever.</span>\n\ni am yet to earn anything from {appName}, everything goes back to users, so you're essentially helping everyone.",
"DonateSub": "help it stay online",
"DonateExplanation": "cobalt doesn't shove ads in your face and doesn't sell your personal data, and thus is <span class=\"text-backdrop\">completely free to use for everyone</span>. but development and maintenance of a media-heavy service used by over 350k people is quite costly. both in terms of time and money. as a student, it's rather difficult for me to handle such expenses on my own.\n\nif cobalt has helped you in the past and you want to keep it growing and evolving, you can do so by making a donation!\n\nby donating you're helping everyone who uses cobalt: teachers, students, musicians, content creators, artists, lecturers, and many, many more!\n\nin past few months donations have let me:\n*; increase stability and uptime to nearly 100%.\n*; speed up ALL downloads, especially heavier ones.\n*; open cobalt api for free public use.\n*; withstand several huge user influxes with 0 downtime.\n*; move to a reliable and trustworthy cloud infrastructure provider.\n*; separate frontend and api for resilience and future decentralization.\n\n<span class=\"text-backdrop\">every cent matters and is extremely appreciated</span>, you can truly make a difference!",
"DonateVia": "donate via",
"DonateHireMe": "...or you can <a class=\"text-backdrop italic\" href=\"{s}\" target=\"_blank\">hire me</a> :)",
"DonateHireMe": "...or you can <a class=\"text-backdrop link\" href=\"{s}\" target=\"_blank\">hire me</a> :)",
"SettingsVideoMute": "mute audio",
"SettingsVideoMuteExplanation": "removes audio from video downloads when possible.",
"ErrorSoundCloudNoClientId": "i couldn't get the temporary token that's required to download songs from soundcloud. try again, but if issue persists, {ContactLink}.",
@ -104,21 +102,47 @@
"CollapseSupport": "support & source code",
"CollapsePrivacy": "privacy policy",
"ServicesNote": "this list is not final and keeps expanding over time, make sure to check it once in a while!",
"FollowSupport": "follow {appName} on mastodon or twitter for support, polls, news, and more:",
"SupportNote": "please note that questions and issues may take a while to respond to, there's only one person managing everything.",
"FollowSupport": "keep in touch with cobalt for support, polls, news, and more:",
"SupportNote": "please note that response may take a while, there's only one person managing everything.",
"SourceCode": "report issues, explore source code, star or fork the repo:",
"PrivacyPolicy": "{appName}'s privacy policy is simple: no data about you is ever collected or stored. zero, zilch, nada, nothing.\nwhat you download is your business, not mine.\n\nsome non-backtraceable data does get temporarily stored when requested download requires live render. it's necessary for that feature to function.\n\nin that case, <span class=\"text-backdrop\">salted sha256 hash of your ip address</span> and information about requested stream are temporarily stored in server's RAM for <span class=\"text-backdrop\">2 minutes</span>. after 2 minutes all previously stored information is permanently removed. hash of your ip address is <span class=\"text-backdrop\">used for limiting stream access only to you</span>.\nno one (even me) has access to this data, because official {appName} codebase doesn't provide a way to read it outside of processing functions in the first place.\n\nyou can check {appName}'s <a class=\"text-backdrop italic\" href=\"{repo}\" target=\"_blank\">github repo</a> yourself and see that everything is as stated.",
"ErrorYTUnavailable": "this youtube video is unavailable or age restricted. i am currently unable to download videos with sensitive content. try another one!",
"PrivacyPolicy": "cobalt's privacy policy is simple: no data about you is ever collected or stored. zero, zilch, nada, nothing.\nwhat you download is solely your business, not mine or anyone else's.\n\nif your download requires live render, some non-backtraceable data is temporarily stored in server's RAM. it's necessary for this feature to function.\n\nin this case info about requested content is stored for <span class=\"text-backdrop\">20 seconds</span> and then permanently removed.\nno one (even me) has access to this data. official cobalt codebase doesn't provide a way to read it outside of processing functions.\n\nyou can check cobalt's <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">source code</a> yourself and see that everything is as stated.",
"ErrorYTUnavailable": "this youtube video is unavailable, it could be region or age restricted. try another one!",
"ErrorYTTryOtherCodec": "i couldn't find anything to download with your settings. try another codec or quality!\n\nnote: youtube api sometimes acts unexpectedly. blame google for this, not me.",
"SettingsCodecSubtitle": "youtube codec",
"SettingsCodecDescription": "h264: generally better player support, but quality tops out at 1080p.\nav1: low player support, but supports 8k & HDR.\nvp9: usually highest bitrate, preserves most detail. supports 4k & HDR.\n\npick h264 if you want best editor/player/social media compatibility.",
"SettingsAudioDub": "youtube audio track",
"SettingsAudioDubDescription": "defines which audio track will be used. if dubbed track isn't available, original video language is used instead.\n\noriginal: original video language is used.\nauto: default browser (and {appName}) language is used.",
"SettingsAudioDubDescription": "defines which audio track will be used. if dubbed track isn't available, original video language is used instead.\n\noriginal: original video language is used.\nauto: default browser (and cobalt) language is used.",
"SettingsDubDefault": "original",
"SettingsDubAuto": "auto",
"SettingsVimeoPrefer": "vimeo downloads type",
"SettingsVimeoPreferDescription": "progressive: direct file link to vimeo's cdn. max quality is 1080p.\ndash: video and audio are merged by {appName} into one file. max quality is 4k.\n\npick \"progressive\" if you want best editor/player/social media compatibility. if progressive download isn't available, dash is used instead.",
"SettingsVimeoPreferDescription": "progressive: direct file link to vimeo's cdn. max quality is 1080p.\ndash: video and audio are merged by cobalt into one file. max quality is 4k.\n\npick \"progressive\" if you want best editor/player/social media compatibility. if progressive download isn't available, dash is used instead.",
"ShareURL": "share",
"ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!"
"ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!",
"ErrorTwitterRIP": "twitter has restricted access to any content to unauthenticated users. while there's a way to get regular tweets, spaces are, unfortunately, impossible to get at this time. i am looking into possible solutions.",
"UrgentDonate": "cobalt needs your help!",
"PopupCloseDone": "done",
"Accessibility": "accessibility",
"SettingsReduceTransparency": "reduce transparency",
"SettingsDisableAnimations": "disable animations",
"FeatureErrorGeneric": "your browser doesn't allow or support this feature. check if there are any updates available and try again!",
"ClipboardErrorFirefox": "you're using firefox where all clipboard reading functionality is disabled.\n\nyou can fix this by following steps listed <a class=\"text-backdrop link\" href=\"{repo}/wiki/Troubleshooting#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">here!</a>\n\n...or you can paste the link manually instead.",
"ClipboardErrorNoPermission": "cobalt can't access the most recent item in your clipboard without your permission.\n\nif you don't want to give access, just paste the link manually instead.\n\nif you do, go to site settings and enable the clipboard permission.",
"SupportSelfTroubleshooting": "experiencing issues? try <a class=\"text-backdrop link\" href=\"{repo}/wiki/Troubleshooting\" target=\"_blank\">self-troubleshooting guide</a> first!",
"AccessibilityGoBack": "go back and close the popup",
"CollapseKeyboard": "keyboard shortcuts",
"KeyboardShortcutsIntro": "use cobalt even faster with keyboard shortcuts:",
"KeyboardShortcutQuickPaste": "paste the link",
"KeyboardShortcutClear": "clear link input area",
"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!",
"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."
}
}

View file

@ -1,115 +0,0 @@
{
"name": "español",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">presenta un problema en github</a>"
},
"strings": {
"LinkInput": "pega tu enlace aquí",
"AboutSummary": "{appName} es tu lugar ideal para descargas de redes sociales. sin anuncios u otras mierdas sospechosas. ¡solo necesitas pegar un enlace y listo!",
"AboutSupportedServices": "servicios compatibles:",
"EmbedBriefDescription": "guarda contenido de redes sociales sin preocuparte por rastreadores",
"MadeWithLove": "hecho con <3 por wukko",
"AccessibilityInputArea": "cuadro de captura",
"AccessibilityOpenAbout": "abrir ventana emergente de acerca de",
"AccessibilityDownloadButton": "botón de descarga",
"AccessibilityOpenSettings": "abrir ventana emergente de ajustes",
"AccessibilityClosePopup": "cerrar la ventana emergente",
"AccessibilityOpenDonate": "abrir ventana emergente de donación",
"TitlePopupAbout": "¿qué es {appName}?",
"TitlePopupSettings": "ajustes",
"TitlePopupError": "oh-no...",
"TitlePopupChangelog": "¿qué hay de nuevo?",
"TitlePopupDonate": "apoya a {appName}",
"TitlePopupDownload": "descargar",
"ErrorSomethingWentWrong": "algo salió mal y no pude encontrar nada para ti. puedes intentar de nuevo, pero si el problema persiste, por favor {ContactLink}.",
"ErrorUnsupported": "parece que este servicio aún no es compatible o tu enlace no es válido.",
"ErrorBrokenLink": "{s} es compatible con cobalt, pero algo está mal con tu enlace. ¿tal vez no lo copiaste completamente?",
"ErrorNoLink": "¡no puedo adivinar qué quieres descargar! por favor introduce un enlace.",
"ErrorPageRenderFail": "algo salió mal y la página no se pudo procesar. si quieres que solucione esto, por favor {ContactLink}. sería útil si proporcionas el commit hash ({s}) junto con pasos de recreación, gracias :D",
"ErrorRateLimit": "estás haciendo demasiadas solicitudes. cálmate y vuelve a intentarlo en unos minutos.",
"ErrorCouldntFetch": "no se pudo obtener ninguna información sobre tu enlace. comprueba si tu enlace es correcto e inténtalo de nuevo.",
"ErrorLengthLimit": "el limite de duración actual es de {s} minutos. lo que intentaste descargar es mas largo que eso. ¡escoge otra cosa que descargar!",
"ErrorBadFetch": "algo salió mal con la obtención de info. puedes probar con un formato y una resolución diferentes o simplemente intentarlo de nuevo más tarde.",
"ErrorCorruptedStream": "parece que esta descarga está corrupta. inténtalo de nuevo o intenta con otro formato o resolución.",
"ErrorNoInternet": "parece que no hay internet o la api de {appName} no está disponible. revisa tu conexión e intenta de nuevo.",
"ErrorCantConnectToServiceAPI": "no pude conectarme a la api de {s} . parace que {s} no está disponible o la ip del servidor de {appName} fue bloqueada. inténtalo de nuevo mas tarde.",
"ErrorEmptyDownload": "parece que no hay nada que descargar. ¡intentalo de nuevo con otro enlace!",
"ErrorLiveVideo": "no se puede descargar un video en vivo. espera que termine la transmisión y vuelve a intentarlo.",
"SettingsAppearanceSubtitle": "apariencia",
"SettingsThemeSubtitle": "tema",
"SettingsFormatSubtitle": "formato de descarga",
"SettingsQualitySubtitle": "calidad",
"SettingsThemeAuto": "auto",
"SettingsThemeLight": "claro",
"SettingsThemeDark": "oscuro",
"SettingsQualitySwitchMax": "max",
"SettingsQualitySwitchHigh": "alta",
"SettingsQualitySwitchMedium": "media",
"SettingsQualitySwitchLow": "baja",
"SettingsQualitySwitchLowest": "mas baja",
"SettingsKeepDownloadButton": "mantener &gt;&gt; visible",
"AccessibilityKeepDownloadButton": "mantener el botón de descarga siempre visible",
"SettingsEnableDownloadPopup": "pregunta por la forma de guardar",
"AccessibilityEnableDownloadPopup": "preguntar qué hacer con las descargas",
"SettingsFormatDescription": "selecciona webm si necesitas la máxima calidad disponible. los videos webm suelen tener un mayor bitrate, pero los dispositivos ios no pueden reproducirlos de forma nativa.",
"SettingsQualityDescription": "si la calidad seleccionada no está disponible, la más cercana es elegida en su lugar.\nsi quieres publicar un vídeo de youtube en las redes sociales, selecciona una combinación de mp4 y 720p. esos videos normalmente no están en el códec av1, por lo que deberían reproducirse bien básicamente en todas partes.",
"LinkGitHubIssues": "&gt;&gt; informa sobre problemas y consulta el código fuente en github",
"LinkGitHubChanges": "&gt;&gt; mira los cambios anteriores y contribuye en github",
"NoScriptMessage": "{appName} usa javascript para las solicitudes de api y para la interfaz interactiva. tienes que permitir javascript en tu navegador para usar este sitio. no tenemos ningún anuncio ni rastreadores, lo prometo con el meñique.",
"DownloadPopupDescriptionIOS": "como tienes un dispositivo ios, debes mantener presionado el botón de descarga y luego seleccionar \"descargar video\" en la ventana emergente que aparece para guardar el video. esto será necesario mientras apple obligue a todos los desarrolladores de navegadores en ios a usar safari webview",
"DownloadPopupDescription": "el botón de descarga abre una nueva pestaña con el archivo solicitado. puedes desactivar esta ventana emergente en los ajustes.",
"DownloadPopupWayToSave": "elige una forma de guardar",
"ClickToCopy": "click para copiar",
"Download": "descargar",
"CopyURL": "copiar url",
"AboutTab": "acerca de",
"ChangelogTab": "changelog",
"DonationsTab": "donaciones",
"SettingsVideoTab": "vídeo",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "otros",
"ChangelogLastMajor": "versión actual y commit",
"AccessibilityModeToggle": "cambiar el modo de descarga",
"DonateLinksDescription": "los enlaces de donación se abren en una nueva pestaña. esta es la mejor manera para donar dinero si quieres que lo reciba directamente.",
"SettingsAudioFormatBest": "mejor",
"SettingsAudioFormatDescription": "cuando seleccionas el formato mejor, obtienes audio en la mejor calidad disponible, porque el audio se mantiene en su formato original. si seleccionas otro formato obtendrás un archivo ligeramente comprimido",
"Keyphrase": "guarda lo que amas",
"SettingsRemoveWatermark": "desactivar marca de agua",
"ErrorPopupCloseButton": "vale",
"ErrorLengthAudioConvert": "el límite de duración actual para la conversión de audio es de {s} minutos. escoge el formato \"mejor\" !",
"SettingsAudioFullTikTok": "descargar el audio completo",
"SettingsAudioFullTikTokDescription": "se descarga el audio original o el sonido usado en el vídeo sin ningún cambio adicional por el autor del vídeo",
"ErrorCantGetID": "No pude obtener la info completa del enlace acortado. asegúrate de que funciona o prueba un enlace completo.",
"ErrorNoVideosInTweet": "este tweet no tiene videos o gifs. ¡inténtalo con otro!",
"ImagePickerTitle": "elige imágenes para descargar",
"ImagePickerDownloadAudio": "descargar audio",
"ImagePickerExplanationPC": "haz clic derecho en una imagen para guardarla.",
"ImagePickerExplanationPhone": "mantén presionada una imagen para guardarla.",
"ErrorNoUrlReturned": "el servidor no devolvió un enlace de descarga. Esto nunca debería suceder. recarga la página y vuelve a intentarlo, pero si eso no ayuda, {ContactLink}.",
"ErrorUnknownStatus": "he recibido una respuesta que no puedo procesar. lo más probable es que algo con el status esté mal. esto nunca debería suceder. recarga la página y vuelve a intentarlo, pero si eso no ayuda, {ContactLink}.",
"PasteFromClipboard": "pegar desde el portapapeles",
"FollowTwitter": "sigue la cuenta de {appName} en twitter para encuestas, actualizaciones y más: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>",
"ChangelogOlder": "versiones anteriores",
"ChangelogPressToExpand": "presiona para expandir",
"Miscellaneous": "otros",
"ModeToggleAuto": "modo automático ",
"ModeToggleAudio": "modo audio",
"SettingsDisableNotifications": "ocultar burbujas de notificación",
"MediaPickerTitle": "elige qué guardar",
"MediaPickerExplanationPC": "haz clic o clic derecho para descargar lo que quieras.",
"MediaPickerExplanationPhone": "presiona o presiona y mantén pulsado para descargar lo que quieras.",
"MediaPickerExplanationPhoneIOS": "mantén presionado, oculta la vista previa, y luego selecciona \"descargar archivo enlazado\" para guardar.",
"TwitterSpaceWasntRecorded": "este espacio de twitter no fue grabado, así que no hay nada que descargar. ¡prueba con otro!",
"ErrorCantProcess": "no he podido procesar tu solicitud :(\npuedes intentarlo de nuevo, pero si el problema persiste, por favor {ContactLink}.",
"ChangelogPressToHide": "presiona para ocultar",
"Donate": "donar",
"DonateSub": "ayúdame a mantenerlo",
"DonateExplanation": "{appName} no muestra anuncios (y nunca lo hará) o vende tus datos, por lo tanto es <span class=\"text-backdrop\">completamente gratis de usar</span>. pero resulta ser que mantener un servicio web usado por miles de personas es más o menos costoso\n\nsi alguna vez has encontrado que {appName} te es útil y quieres mantenerlo en línea, o simplemente quieres darle las gracias al desarrollador, ¡concidera aportar algo! cada centavo ayuda y es MUY apreciado\n",
"DonateVia": "donar vía",
"DonateHireMe": "o, como alternativa, puedes <a class=\"text-backdrop\" href=\"{s}\" target=\"_blank\">contratarme</a>.",
"SettingsVideoMute": "silenciar audio",
"SettingsVideoMuteExplanation": "deshabilita el audio en el vídeo descargado cuando sea posible. obtendrás el archivo de vídeo fuente si los canales de vídeo y audio se sirven en dos archivos por el servicio de origen. se ignora cuando el modo de audio está encendido o si el servicio solo soporta audio.",
"SettingsVideoGeneral": "general",
"ErrorSoundCloudNoClientId": "no se pudo encontrar el client_id necesario para obtener datos de audio de soundcloud. Inténtalo de nuevo, y si el problema persiste, {ContactLink}."
}
}

View file

@ -1,101 +0,0 @@
{
"name": "français",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">fais-moi signe</a>"
},
"strings": {
"LinkInput": "collez le lien ici",
"AboutSummary": "{appName} est l'endroit idéal pour télécharger sur les réseaux sociaux. zero pubs, trackers, ou toute autre connerie effrayante attachée. il suffit de coller un lien de partage et vous êtes prêt à vous lancer!",
"AboutSupportedServices": "services actuellement supportés:",
"EmbedBriefDescription": "sauvegarder le contenu des médias sociaux sans être suivi par des personnes mal intentionnées",
"MadeWithLove": "crée avec <3 par wukko et tous les contributeurs sur github (traduction par Greep)",
"AccessibilityInputArea": "zone de saisie du lien",
"AccessibilityOpenAbout": "ouvrir la fenêtre popup à propos",
"AccessibilityDownloadButton": "bouton de téléchargement",
"AccessibilityOpenSettings": "ouvrir la fenêtre popup des paramètres",
"AccessibilityOpenChangelog": "voir la fenêtre popup du journal des modifications",
"AccessibilityClosePopup": "fermer la popup",
"AccessibilityOpenDonate": "ouvrir une popup de donation",
"TitlePopupAbout": "c'est quoi {appName}?",
"TitlePopupSettings": "paramètres",
"TitlePopupError": "ah! c'est cringe là...",
"TitlePopupChangelog": "Quoi de neuf ?",
"TitlePopupDonate": "supporter {appName}",
"TitlePopupDownload": "télecharger",
"ErrorSomethingWentWrong": "quelque chose s'est mal passé et je n'ai rien pu obtenir pour vous. vous pouvez réessayer, mais si le problème persiste, s'il vous plaît {ContactLink}.",
"ErrorUnsupported": "il semble que ce service ne soit pas encore supporté ou que votre lien ne soit pas valide.",
"ErrorBrokenLink": "{s} est supporté, mais quelque chose ne va pas avec votre lien. peut-être que vous ne l'avez pas copié entièrement ?",
"ErrorNoLink": "je ne peux pas deviner ce que vous voulez télécharger ! s'il vous plaît donnez-moi un lien.",
"ErrorPageRenderFail": "quelque chose s'est mal passé et la page n'a pas pu s'afficher. Si c'est un problème récurrent ou critique, veuillez {ContactLink}. il serait utile de fournir le hash du commit actuel ({s}) et les étapes de recréation d'erreur. merci :D",
"ErrorRateLimit": "vous faites beaucoup trop de demandes. calmez-vous et réessayez dans quelques minutes.",
"ErrorCouldntFetch": "Impossible de récupérer les métadonnées. Vérifiez si votre lien est correct et réessayez.",
"ErrorLengthLimit": "la durée limite actuelle est de {s} minutes. ce que vous avez essayé de télécharger est plus long que {s} minutes. choisissez autre chose à télécharger !",
"ErrorBadFetch": "Quelque chose s'est mal passé avec la récupération des informations. Vous pouvez essayer un autre format et une autre résolution ou réessayer plus tard.",
"ErrorCorruptedStream": "ce téléchargement est malheureusement corrompu. essayez à nouveau ou essayez un format et une résolution différents.",
"ErrorNoInternet": "il n'y a pas d'internet ou l'api de {appName} est en panne. Vérifiez votre connexion et réessayez.",
"ErrorCantConnectToServiceAPI": "je n'ai pas pu me connecter à l'api de {s}. il semble que {s} soit hors service ou que l'ip du serveur {appName} soit bloqué. réessayez plus tard.",
"ErrorEmptyDownload": "il n'y a rien à télécharger. essayez autre chose !",
"ErrorLiveVideo": "je ne peux pas télécharger une vidéo en direct. attendez que le flux se termine et réessayez.",
"SettingsAppearanceSubtitle": "apparence",
"SettingsThemeSubtitle": "thème",
"SettingsFormatSubtitle": "télecharger le format",
"SettingsMiscSubtitle": "plus de paramètres",
"SettingsDownloadsSubtitle": "télechargement",
"SettingsQualitySubtitle": "qualité",
"SettingsThemeAuto": "auto",
"SettingsThemeLight": "clair",
"SettingsThemeDark": "sombre",
"SettingsQualitySwitchMax": "max",
"SettingsQualitySwitchHigh": "haute",
"SettingsQualitySwitchMedium": "moyenne",
"SettingsQualitySwitchLow": "basse",
"SettingsQualitySwitchLowest": "plus basse",
"SettingsKeepDownloadButton": "garder &gt;&gt; visible",
"AccessibilityKeepDownloadButton": "garder le bouton de téléchargement toujours visible",
"SettingsEnableDownloadPopup": "demander un moyen de sauvegarder",
"AccessibilityEnableDownloadPopup": "demander ce qu'il faut faire avec les téléchargements",
"SettingsFormatDescription": "sélectionnez webm si vous avez besoin de la qualité maximale disponible. les vidéos webm sont généralement de meilleure qualité mais les appareils ios ne peuvent pas les lire en natif.",
"SettingsQualityDescription": "si la résolution choisie n'est pas disponible, la résolution la plus proche est choisie à la place. si vous voulez poster une vidéo youtube sur twitter, choisissez une combinaison de mp4 et 720p. twitter aime beaucoup plus les vidéos de ce type.",
"DonateSubtitle": "aidez-moi à payer l'hébergement",
"DonateDescription": "je n'aime pas vraiment la crypto dans son état actuel, mais c'est le seul moyen fiable pour moi de recevoir de l'argent et de payer quoi que ce soit à l'étranger.",
"LinkGitHubIssues": "&gt;&gt; signaler les problèmes et consulter le code source sur github",
"LinkGitHubChanges": "&gt;&gt; voir les changements précédents et contribuer sur github",
"LinkDonateContact": "&gt;&gt; faites-moi savoir si la monnaie que vous voulez donner n'est pas listée",
"NoScriptMessage": "{appName} utilise javascript pour les demandes d'api et l'interface interactive. vous devez autoriser javascript pour utiliser ce site. nous n'avons pas de publicités ou de traceurs, c'est promis.",
"DownloadPopupDescriptionIOS": "comme vous avez un appareil ios, vous devez appuyer sur le bouton de téléchargement et le maintenir enfoncé, puis sélectionner \"télécharger la vidéo\" dans la fenêtre popup qui apparaît pour enregistrer la vidéo. cela sera nécessaire tant qu'apple imposera safari webview à tous les développeurs de navigateurs sur ios.",
"DownloadPopupDescription": "le bouton de téléchargement ouvre un nouvel onglet avec le fichier demandé. vous pouvez désactiver cette popup dans les paramètres.",
"DownloadPopupWayToSave": "choisissez un moyen de sauvegarder",
"ClickToCopy": "appuyer pour copier",
"Download": "télecharger",
"CopyURL": "copier l'url",
"AboutTab": "à propos",
"ChangelogTab": "journal de modifications",
"DonationsTab": "donations",
"SettingsVideoTab": "vidéo",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "autre",
"ChangelogLastCommit": "dernier commit",
"ChangelogLastMajor": "dernière mise à jour majeure",
"AccessibilityModeToggle": "basculer le mode de téléchargement",
"DonateLinksDescription": "les liens vers les dons s'ouvrent dans un nouvel onglet. c'est la meilleure façon de donner de l'argent, si vous voulez que je le reçoive directement.",
"SettingsAudioFormatBest": "meilleure",
"SettingsAudioFormatDescription": "lorsque le meilleur format est sélectionné, vous obtenez l'audio dans la meilleure qualité disponible, car l'audio est conservé dans son format d'origine. si vous sélectionnez autre chose, vous obtiendrez un fichier légèrement compressé.",
"Keyphrase": "sauvegardez ce que vous aimez",
"SettingsDisableChangelogOnUpdate": "ne pas afficher le journal de modifications après les mises à jour majeures",
"SettingsRemoveWatermark": "désactiver le filigrane",
"ErrorPopupCloseButton": "fermer",
"ModeToggle": "mode",
"ModeToggleSmart": "intélligent",
"ErrorLengthAudioConvert": "la longueur limite actuelle pour la conversion audio est de {s} minutes. choisissez plutôt le format \"meilleure\" !",
"SettingsAudioFullTikTok": "télécharger l'audio complet",
"SettingsAudioFullTikTokDescription": "cet audio est le plus souvent de la musique ou un son original utilisé dans une vidéo. un audio sans voix off, tts, ou découpage sera téléchargé, s'il est disponible, bien sûr.",
"ErrorCantGetID": "Je n'ai pas pu obtenir les informations complètes à partir du lien raccourci. Assurez-vous que cela fonctionne ou essayez un lien complet.",
"ErrorNoVideosInTweet": "Ce tweet n'a pas de vidéos ou de gifs. Essayez un autre !",
"ImagePickerTitle": "choisir les images à télécharger",
"ImagePickerDownloadAudio": "Télécharger l'audio",
"ImagePickerExplanationPC": "faites un clic droit sur une image pour l'enregistrer.",
"ImagePickerExplanationPhone": "appuyez et maintenez une image enfoncée pour l'enregistrer.",
"ErrorNoUrlReturned": "le serveur n'a pas retourné de lien de téléchargement. Cela ne devrait jamais se produire. Rechargez la page et réessayez, mais si cela n'aide pas, {ContactLink}.",
"ErrorUnknownStatus": "J'ai reçu une réponse que je ne peux pas traiter. Il est fort probable que quelque chose avec le statut est erroné. cela ne devrait jamais arriver. Rechargez la page et réessayez, mais si cela n'aide pas, {ContactLink}."
}
}

View file

@ -1,74 +0,0 @@
{
"name": "indonesia",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">beri tau saya</a>"
},
"strings": {
"LinkInput": "tempel link kamu disini",
"AboutSummary": "{appName} adalah tempat terbaik kamu untuk download video sosial media tanpa iklan, pelacak, atau omong kosong lainnya. tinggal tempel link dan udah deh",
"AboutSupportedServices": "layanan yang didukung:",
"EmbedBriefDescription": "simpan konten dari sosial media tanpa hal aneh mengikuti kamu",
"AccessibilityInputArea": "tempat tempel link",
"AccessibilityOpenAbout": "buka bagian pengantar",
"AccessibilityDownloadButton": "tombol download",
"AccessibilityOpenSettings": "buka pengaturan",
"AccessibilityOpenChangelog": "buka log perubahan",
"AccessibilityClosePopup": "tutup popup ini",
"AccessibilityOpenDonate": "buka bagian donasi",
"TitlePopupAbout": "apa itu {appName}?",
"TitlePopupSettings": "pengaturan",
"TitlePopupError": "uh-oh...",
"TitlePopupChangelog": "apa yang baru?",
"TitlePopupDonate": "dukung {appName}",
"TitlePopupDownload": "download",
"ErrorSomethingWentWrong": "ada yang tidak beres dan saya tidak bisa mendapatkan apa-apa untuk kamu. tolong coba lagi, tapi kalo masih tidak bisa, tolong kontak {ContactLink}.",
"ErrorUnsupported": "sepertinya layanan atau sosial media kamu belom didukung oleh kita atau link kamu tidak sah.",
"ErrorBrokenLink": "{s} sudah didukung kita, tapi ada sesuatu yang salah dengan link kamu, mungkin kamu belom salin sepenuhnya?",
"ErrorNoLink": "saya tidak bisa menebak apa yang kamu mau download! tolong tempel link yang benar.",
"ErrorPageRenderFail": "ada sesuatu yang salah dan halaman tidak bisa dirender. kalo masalah ini terus berulang tolong beritahu {ContactLink}. akan sangat membantu kalo kamu juga tuliskan commit hash sekarang ({s}) dan langkah-langkah rekreasi error ini, terima kasih :D",
"ErrorRateLimit": "kamu membuat terlalu banyak permintaan. tunggu sebentar dan coba sebentar lagi.",
"ErrorCouldntFetch": "kita tidak bisa mendapatkan metadata. coba cek link kamu benar atau tidak dan coba lagi.",
"ErrorLengthLimit": "batas panjang file saat ini adalah {s} menit. hal yang kamu coba download itu lebih panjang dari {s} menit, pilih file lain untuk didownload!",
"ErrorBadFetch": "ada sesuatu yang salah saat pengambilan info. coba ganti pilihan format atau resolusi file, atau coba lagi nanti.",
"ErrorCorruptedStream": "download file ini sayangnya rusak. coba lagi atau coba memakai pilihan format dan resolusi yang beda.",
"ErrorNoInternet": "tidak ada internet atau api {AppName} sedang tidak tersedia. coba cek internet kamu dan coba lagi.",
"ErrorCantConnectToServiceAPI": "kita tidak bisa tidak bisa menghubungi api {s}. sepertinya {s} lagi tidak tersedia atau ip server {appName} diblokir. coba lagi nanti.",
"ErrorEmptyDownload": "tidak ada konten media untuk didownload. coba sesuatu yang lain!",
"ErrorLiveVideo": "kita tidak bisa mendownload video siaran langsung yang sedang berlangsung. tunggu untuk siaran langsungnya untuk selesai dan coba lagi.",
"ErrorNoStreamID": "streamId itu tidak ditemukan.",
"ErrorNoType": "tidak ada tipe respons yang diekspektasikan.",
"SettingsAppearanceSubtitle": "penampilan",
"SettingsThemeSubtitle": "tema",
"SettingsFormatSubtitle": "format download",
"SettingsMiscSubtitle": "pengaturan lebih lanjut",
"SettingsDownloadsSubtitle": "download",
"SettingsQualitySubtitle": "kualitas",
"SettingsThemeAuto": "otomatis",
"SettingsThemeLight": "terang",
"SettingsThemeDark": "gelap",
"SettingsQualitySwitchMax": "maksimal",
"SettingsQualitySwitchHigh": "tinggi",
"SettingsQualitySwitchMedium": "sedang",
"SettingsQualitySwitchLow": "rendah",
"SettingsQualitySwitchLowest": "paling rendah",
"SettingsFormatSwitchAudio": "audio saja",
"SettingsKeepDownloadButton": "biarkan &gt;&gt; tetap terlihat",
"AccessibilityKeepDownloadButton": "biarkan tombol download selalu kelihatan",
"SettingsEnableDownloadPopup": "tanya cara untuk simpan",
"AccessibilityEnableDownloadPopup": "tanyakan apa yang dilakukan dengan file yang baru didownload",
"SettingsFormatDescription": "pilih webm kalo kamu butuh kualitas tertinggi. kualitas video webm biasanya lebih tinggi tapi perangkat ios tidak bisa menjalankannya secara native.",
"SettingsQualityDescription": "jika resolusi yang dipilih tidak tersedia, resolusi yang dipilih akan diganti dengan resolusi tersedia yang terdekat. kalo kamu mau kirim video youtube di twitter, pilih kombinasi mp4 dan 720p. twitter lebih suka video seperti itu.",
"DonateSubtitle": "sekarang lagi susah untuk bayar hosting bagi saya",
"DonateDescription": "saya tidak suka crypto dengan keadaannya sekarang, tetapi saat ini itu adalah satu-satunya cara bagi saya untuk membayar apa pun yang di luar negeri. kartu mastercard/visa dan layanan yang mirip seperti paypal tidak lagi menjadi pilihan.",
"LinkGitHubIssues": "&gt;&gt; laporkan masalah atau liat source code di github",
"LinkGitHubChanges": "&gt;&gt; liat perubahan sebelumnya dan berkontribusi di github",
"LinkDonateContact": "&gt;&gt; beri tau saya jika mata uang yg kamu mau donasi belom ada di atas",
"NoScriptMessage": "{appName} memakai javascript untuk permintaan api dan ui interaktif. kamu harus mengizinkan javascript untuk menggunakan situs ini. kita tidak punya iklan atau pelacak, saya janji.",
"DownloadPopupDescriptionIOS": "karena kamu punya perangkat ios, kamu harus teken lama tombol download dan pilih \"download video\" di popup yang muncul untuk menyimpan videonya. ini akan diperlukan selama apple memaksa webview safari pada semua browser developer di ios.",
"DownloadPopupDescription": "tombol download membuka tab baru dengan file yang diminta. kamu bisa menonaktifkan popup ini di pengaturan.",
"DownloadPopupWayToSave": "pilih cara untuk disimpan",
"ClickToCopy": "tekan untuk salin",
"Download": "download",
"CopyURL": "salin url"
}
}

View file

@ -1,101 +0,0 @@
{
"name": "italiano",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">contatta il gestore/a>"
},
"strings": {
"LinkInput": "incolla qui il link",
"AboutSummary": "{appName} è il sito perfetto per scaricare media dai social. niente pubblicità, trackers, o altre minchiate. incolla un link e via!",
"AboutSupportedServices": "servizi attualmente supportati:",
"EmbedBriefDescription": "salva contenuti dai social media senza alcun fastidio",
"MadeWithLove": "realizzato con <3 da wukko",
"AccessibilityInputArea": "qui puoi inserire un link",
"AccessibilityOpenAbout": "apri la sezione info",
"AccessibilityDownloadButton": "tasto download",
"AccessibilityOpenSettings": "apri le impostazioni",
"AccessibilityOpenChangelog": "apri la sezione aggiornamenti",
"AccessibilityClosePopup": "chiudi la finestra",
"AccessibilityOpenDonate": "apri sezione donazioni",
"TitlePopupAbout": "cos'è {appName}?",
"TitlePopupSettings": "impostazioni",
"TitlePopupError": "uh-oh...",
"TitlePopupChangelog": "cosa c'è di nuovo?",
"TitlePopupDonate": "supporta {appName}",
"TitlePopupDownload": "scarica",
"ErrorSomethingWentWrong": "qualcosa è andato storto e non ho potuto effettuare il download. puoi riprovare, ma se il problema persiste, {ContactLink}",
"ErrorUnsupported": "il servizio non è ancora supportato, oppure il tuo link è invalido.",
"ErrorBrokenLink": "{s} è supportato, ma c'è qualcosa che non va con il tuo link. forse non lo hai copiato completamente?",
"ErrorNoLink": "non posso indovinare cosa vuoi scaricare! inserisci un link.",
"ErrorPageRenderFail": "qualcosa è andato storto e la pagina non è riuscita a renderizzare. se il problema persiste, {ContactLink}. se è possibile, specifica l'hash del commit attuale ({s}) e i passaggi per ricreare l'errore. grazie in anticipo :D",
"ErrorRateLimit": "stai facendo troppe richieste! datti una calmata e riprova tra un po'.",
"ErrorCouldntFetch": "non ho potuto ottenere informazioni sul tuo link. controlla di averlo inserito correttamente e riprova.",
"ErrorLengthLimit": "spiacenti! il limite di lunghezza attuale è di {s} minuti. il video che hai provato a scaricare è più lungo di {s} minuti.",
"ErrorBadFetch": "si è verificato un errore quando ho cercato di ottenere informazioni sul tuo link. controlla se sia funzionante e riprova.",
"ErrorCorruptedStream": "purtroppo questo download è corrotto. prova di nuovo, oppure prova a cambiare formato o risoluzione.",
"ErrorNoInternet": "la connessione è assente oppure ci sono problemi con l'api di {appName}. controlla la tua connessione e riprova.",
"ErrorCantConnectToServiceAPI": "non riesco a connettermi all'api di {s}. sembra ci siano problemi con {s} o che l'ip di {appName} sia stato bloccato. riprova più tardi.",
"ErrorEmptyDownload": "non vedo nulla che possa scaricare da qui. prova un link diverso.",
"ErrorLiveVideo": "non posso guardare nel futuro e scaricare una diretta in corso! aspetta che la diretta finisca e riprova.",
"SettingsAppearanceSubtitle": "aspetto",
"SettingsThemeSubtitle": "tema",
"SettingsFormatSubtitle": "formato download",
"SettingsMiscSubtitle": "altre impostazioni",
"SettingsDownloadsSubtitle": "download",
"SettingsQualitySubtitle": "qualità",
"SettingsThemeAuto": "automatico",
"SettingsThemeLight": "chiaro",
"SettingsThemeDark": "scuro",
"SettingsQualitySwitchMax": "massima",
"SettingsQualitySwitchHigh": "alta",
"SettingsQualitySwitchMedium": "media",
"SettingsQualitySwitchLow": "bassa",
"SettingsQualitySwitchLowest": "minima",
"SettingsKeepDownloadButton": "mantieni &gt;&gt; visibile",
"AccessibilityKeepDownloadButton": "mantieni il tasto download sempre visibile",
"SettingsEnableDownloadPopup": "abilita il popup di esplora risorse al download",
"AccessibilityEnableDownloadPopup": "abilita il popup di esplora risorse al download",
"SettingsFormatDescription": "il formato webm offre qualità maggiore, ma potrebbe non essere supportato da alcuni sistemi operativi, come ios.",
"SettingsQualityDescription": "se la risoluzione selezionata non è disponibile, verrà scelta quella più vicina. se vuoi postare il video su twitter, scegliere il formato mp4 a 720p creerà meno problemi.",
"DonateSubtitle": "aiutami a pagare per l'hosting",
"DonateDescription": "le crypto nel loro stato attuale non mi fanno impazzire, ma per me è l'unico modo affidabile per ricevere e pagare soldi con i paesi esteri.",
"LinkGitHubIssues": "&gt;&gt; segnala problemi e visualizza il codice sorgente su github",
"LinkGitHubChanges": "&gt;&gt; vedi i cambiamenti precedenti e contribuisci su github",
"LinkDonateContact": "&gt;&gt; fammi sapere se la valuta che vuoi donare non è elencata",
"NoScriptMessage": "{appName} utilizza javascript per le richieste api e l'interfaccia utente. devi abilitare javascript per questo sito. non ci sono tracker o pubblicità, promesso!",
"DownloadPopupDescriptionIOS": "visto che hai un dispositivo ios, devi tenere premuto il tasto download e selezionare \"scarica video\" nel popup che appare. questo sarà necessario fin quando apple non smetterà di forzare safari webview su tutti i browser per ios.",
"DownloadPopupDescription": "il tasto download apre una nuova scheda con il file richiesto. puoi disabilitare questo popup nelle impostazioni.",
"DownloadPopupWayToSave": "scegli un modo per scaricare",
"ClickToCopy": "premi per copiare",
"Download": "scarica",
"CopyURL": "copia url",
"AboutTab": "info",
"ChangelogTab": "cambiamenti",
"DonationsTab": "donazioni",
"SettingsVideoTab": "video",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "altro",
"ChangelogLastCommit": "ultimo commit (in inglese)",
"ChangelogLastMajor": "ultimo aggiornamento principale (in inglese)",
"AccessibilityModeToggle": "alternare la modalità download",
"DonateLinksDescription": "i link di donazione si apriranno in una nuova scheda. questo è il modo migliore per donare soldi, se vuoi che li riceva direttamente.",
"SettingsAudioFormatBest": "migliore",
"SettingsAudioFormatDescription": "quando il formato \"migliore\" viene selezionato, sarà scaricato in formato originale, con la qualità migliore possibile. scegliendo altri formati, ci sarà una leggera compressione.",
"Keyphrase": "salva ciò che ami",
"SettingsDisableChangelogOnUpdate": "non mostrare le novità dopo aggiornamenti principali",
"SettingsRemoveWatermark": "rimuovi filigrana",
"ErrorPopupCloseButton": "capito",
"ModeToggle": "modalità",
"ModeToggleSmart": "inteligente",
"ErrorLengthAudioConvert": "il limite di lunghezza attuale per la conversione audio è di {s} minuti. scegli il formato \"migliore\" se vuoi evitare limitazioni.",
"SettingsAudioFullTikTok": "scarica l'audio completo",
"SettingsAudioFullTikTokDescription": "scarica l'audio originale del video senza cambiamenti aggiuntivi dell'autore.",
"ErrorCantGetID": "non ho potuto ottenere informazioni sufficienti dal link accorciato. assicurati che funzioni, o prova con il link completo.",
"ErrorNoVideosInTweet": "questo tweet non contiene video o gif. provane un altro.",
"ImagePickerTitle": "scegli le immagini da scaricare",
"ImagePickerDownloadAudio": "scarica l'audio",
"ImagePickerExplanationPC": "fai clic destro su un'immagine per salvarla.",
"ImagePickerExplanationPhone": "tieni premuto su un'immagine per salvarla.",
"ErrorNoUrlReturned": "il server non ha restituito un link di download. questo non avrebbe dovuto succedere. ricarica la pagina e riprova, ma se il problema persiste, {ContactLink}",
"ErrorUnknownStatus": "ho ricevuto una risposta che non posso elaborare. questo non avrebbe dovuto succedere. ricarica la pagina e riprova, ma se il problema persiste, {ContactLink}"
}
}

View file

@ -1,101 +0,0 @@
{
"name": "nederlands",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">laat het me weten</a>"
},
"strings": {
"LinkInput": "plak de link hier",
"AboutSummary": "{appName} is je go-to plaats voor social media downloads. zonder advertenties, trackers, of wat voor creepy bullshit dan ook. plak gewoon een link en je bent er klaar voor!",
"AboutSupportedServices": "ondersteunde apps:",
"EmbedBriefDescription": "bewaar content van sociale media zonder creeps die je volgen",
"MadeWithLove": "met <3 gemaakt door wukko",
"AccessibilityInputArea": "link input plek",
"AccessibilityOpenAbout": "open over popup",
"AccessibilityDownloadButton": "download knop",
"AccessibilityOpenSettings": "instellingen openen popup",
"AccessibilityOpenChangelog": "bekijk de changelog popup ",
"AccessibilityClosePopup": "sluit de popup",
"AccessibilityOpenDonate": "open donatie popup",
"TitlePopupAbout": "wat is {appName}?",
"TitlePopupSettings": "instellingen",
"TitlePopupError": "oeps...",
"TitlePopupChangelog": "wat is er nieuw?",
"TitlePopupDonate": "ondersteun {appName}",
"TitlePopupDownload": "download",
"ErrorSomethingWentWrong": "er is iets misgegaan en ik kon niks voor je krijgen. je kan het opnieuw proberen, maar als het probleem door blijft gaan, please {ContactLink}.",
"ErrorUnsupported": "het lijkt erop dat deze service nog niet ondersteund word, of je link is ongeldig.",
"ErrorBrokenLink": "{s} wordt ondersteund, maar er is iets mis met de link, misschien heb je de link niet volledig gekopieerd?",
"ErrorNoLink": "ik kan je gedachten niet lezen! voer hier een link in.",
"ErrorPageRenderFail": "er is iets misgegaan en de pagina kan niet worden weergegeven. als het de hele tijd gebeurt, neem dan contact op: {ContactLink}. het zou handig zijn als je de huidige commit-hash ({s}) en de stappen om de error te laten gebeuren zou opgeven. bedankt :D",
"ErrorRateLimit": "je maakt te veel verzoeken. probeer opnieuw in een paar minuten.",
"ErrorCouldntFetch": "kan metadata niet ophalen. kijk of de link correct is en probeer opnieuw.",
"ErrorLengthLimit": "huidige lengtelimiet is {s} minuten. wat je geprobeerd hebt om te downloaden is langer dan {s} minuten. kies iets anders om te downloaden!",
"ErrorBadFetch": "er is iets misgegaan met het ophalen van info. je kan een ander formaat en resolutie kiezen of opnieuw proberen.",
"ErrorCorruptedStream": "deze download is helaas beschadigd. probeer het opnieuw of probeer een ander formaat en resolutie",
"ErrorNoInternet": "er is geen internet of de api van {appName} is niet beschikbaar. controleer je verbinding en probeer het opnieuw.",
"ErrorCantConnectToServiceAPI": "ik kon geen verbinding maken met {s} api. het lijkt erop dat {s} niet beschikbaar is of dat het server-ip van {appName} is geblokkeerd. probeer later opnieuw",
"ErrorEmptyDownload": "er is niks te downloaden. probeer iets anders!",
"ErrorLiveVideo": "ik kan geen live video downloaden. wacht tot de stream afgelopen is en probeer opnieuw.",
"SettingsAppearanceSubtitle": "uiterlijk",
"SettingsThemeSubtitle": "thema",
"SettingsFormatSubtitle": "download formaat",
"SettingsMiscSubtitle": "meer instellingen",
"SettingsDownloadsSubtitle": "downloads",
"SettingsQualitySubtitle": "kwaliteit",
"SettingsThemeAuto": "automatisch",
"SettingsThemeLight": "licht",
"SettingsThemeDark": "donker",
"SettingsQualitySwitchMax": "max",
"SettingsQualitySwitchHigh": "hoog",
"SettingsQualitySwitchMedium": "medium",
"SettingsQualitySwitchLow": "laag",
"SettingsQualitySwitchLowest": "laagst",
"SettingsKeepDownloadButton": "laat &gt;&gt; zichtbaar blijven",
"AccessibilityKeepDownloadButton": "laat de download know altijd zichtbaar blijven",
"SettingsEnableDownloadPopup": "vraag voor een manier om op te slaan",
"AccessibilityEnableDownloadPopup": "vraag wat te doen met de downloads",
"SettingsFormatDescription": "selecteer webm als je maximale beschikbare kwaliteit nodig hebt. webm video's zijn meestal van hogere kwaliteit, maar ios apparaten kunnen ze niet native afspelen.",
"SettingsQualityDescription": "als de geselecteerde resolutie niet beschikbaar is, wordt in plaats daarvan de dichtstbijzijnde gekozen. als je een youtube video op twitter wil plaatsen, selecteer dan een combinatie van mp4 en 720p. twitter houdt meer van zulke video's.",
"DonateSubtitle": "help me met het betalen van hosting",
"DonateDescription": "ik hou niet echt van crypto in zijn huidige staat, maar het is de enige betrouwbare manier voor mij om geld te ontvangen en te betalen voor iets in het buitenland.",
"LinkGitHubIssues": "&gt;&gt; meld problemen en bekijk de broncode op github",
"LinkGitHubChanges": "&gt;&gt; zie eerdere wijzigingen en draag bij op github",
"LinkDonateContact": "&gt;&gt; laat het me weten als de geld soort die je wil doneren niet in de lijst staat",
"NoScriptMessage": "{appName} gebruikt javascript voor api-verzoeken en interactieve interface. je moet javascript toestaan om deze site te gebruiken. we hebben geen advertenties of trackers, pinky promise.",
"DownloadPopupDescriptionIOS": "omdat je een ios-apparaat hebt, moet je de downloadknop ingedrukt houden en vervolgens \"download video\" selecteren in de verschenen pop-up om de video op te slaan. dit moet zolang Apple alle browserontwikkelaars op ios een safari webview forceert.",
"DownloadPopupDescription": "download knop opent een nieuw tabblad met het gevraagde bestand. je kunt deze popup uitschakelen in instellingen.",
"DownloadPopupWayToSave": "kies een manier om op te slaan",
"ClickToCopy": "click om te kopiëren",
"Download": "download",
"CopyURL": "kopieër url",
"AboutTab": "over",
"ChangelogTab": "veranderingen",
"DonationsTab": "donaties",
"SettingsVideoTab": "video",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "ander",
"ChangelogLastCommit": "laatste commit",
"ChangelogLastMajor": "laatste grote update",
"AccessibilityModeToggle": "zet download modus aan of uit",
"DonateLinksDescription": "donatie linken openen in een nieuw tabblad. dit is de beste manier om geld te doneren, als je wil dat ik het direct krijg.",
"SettingsAudioFormatBest": "beste",
"SettingsAudioFormatDescription": "wanneer het formaat geselecteerd wordt, krijg je de audio in de beste kwaliteit beschikbaar, omdat de audio in zijn originele formaat is gebleven. als je iets anders selecteert dan dat, krijg je een lichtelijk gecompresseerd bestand.",
"Keyphrase": "sla op wat je houdt",
"SettingsDisableChangelogOnUpdate": "laat changelog niet zien na grote updates",
"SettingsRemoveWatermark": "zet watermark uit",
"ErrorPopupCloseButton": "snappie",
"ModeToggle": "modus",
"ModeToggleSmart": "slim",
"ErrorLengthAudioConvert": "huidige limit voor de lengte van audioconversie is {s} minuten. kies optie \"beste\" om limieten te voorkomen.",
"SettingsAudioFullTikTok": "download volledige audio",
"SettingsAudioFullTikTokDescription": "download de originele audio/geluid dat in de video gebruikt is, zonder extra veranderingen van de video auteur.",
"ErrorCantGetID": "ik kon niet de volledige info krijgen van de verkorte link, zorg ervoor dat het werkt of probeer de volledige link.",
"ErrorNoVideosInTweet": "deze tweet heeft geen video's of gifs. probeer een andere!",
"ImagePickerTitle": "kies afbeeldingen om te downloaden",
"ImagePickerDownloadAudio": "download audio",
"ImagePickerExplanationPC": "klik met de rechtermuisknop om een afbeelding op te slaan.",
"ImagePickerExplanationPhone": "houd een afbeelding ingedrukt om op te slaan.",
"ErrorNoUrlReturned": "server heeft geen downloadlink teruggegeven, dit zou nooit moeten gebeuren. ververs de pagina en probeer opnieuw, maar wanneer dit niet helpt, {ContactLink}.",
"ErrorUnknownStatus": "ik heb een antwoord ontvangen dat ik niet kan verwerken. hoogstwaarschijnlijk iets dat met de status fout is, dit hoort nooit te gebeuren. ververs de pagina en probeer opnieuw, maar wanneer dit niet helpt, {ContactLink}."
}
}

View file

@ -1,111 +0,0 @@
{
"name": "polski",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">daj mi znać</a>"
},
"strings": {
"LinkInput": "wklej link tutaj",
"AboutSummary": "{appName} to najlepsze miejsce do pobierania z mediów społecznościowych. zero reklam, śledzenia i innych podobnych głupot. po prostu wklejasz link i lecisz!",
"AboutSupportedServices": "aktualnie wspierane strony:",
"EmbedBriefDescription": "pobieraj rzeczy z social mediów bez reklam i śledzenia",
"MadeWithLove": "zrobione z <3 przez wukko",
"AccessibilityInputArea": "pole wklejania linku",
"AccessibilityOpenAbout": "otwórz okno informacji",
"AccessibilityDownloadButton": "przycisk pobierania",
"AccessibilityOpenSettings": "otwórz okno ustawień",
"AccessibilityOpenChangelog": "otwórz okno aktualizacji",
"AccessibilityClosePopup": "zamknij okno",
"AccessibilityOpenDonate": "otwórz okno darowizn",
"TitlePopupAbout": "czym jest {appName}?",
"TitlePopupSettings": "ustawienia",
"TitlePopupError": "ups...",
"TitlePopupChangelog": "co nowego?",
"TitlePopupDonate": "wesprzyj {appName}",
"TitlePopupDownload": "pobierz",
"ErrorSomethingWentWrong": "coś poszło nie tak i nie udało się niczego dla ciebie pobrać. możesz spróbować jeszcze raz, ale jeżeli problem nie ustąpi, {ContactLink}.",
"ErrorUnsupported": "wygląda na to, że ta strona nie jest jeszcze wspierana, albo twój link jest nieprawidłowy.",
"ErrorBrokenLink": "{s} jest wspierany, ale coś jest nie tak z twoim linkiem. może nie został skopiowany w całości?",
"ErrorNoLink": "nie potrafię czytać ci w myślach! proszę, daj mi link",
"ErrorPageRenderFail": "coś poszło nie tak i strona nie mogła zostać wyrenderowana. jeżeli problem jest krytyczny lub się powtarza, proszę {ContactLink}. byłoby dobrze gdybym otrzymał hash commita ({s}) i kroki do odtworzenia błędu. dzięki :D",
"ErrorRateLimit": "wysyłasz zbyt dużo żądań. uspokój się i spróbuj ponownie za parę minut.",
"ErrorCouldntFetch": "nie udało się pobrać metadanych. sprawdź, czy twój link jest poprawny i spróbuj ponownie.",
"ErrorLengthLimit": "aktualny limit długości to {s} minut. to, co próbujesz pobrać, jest dłuższe niż {s} minut. pobierz coś innego!",
"ErrorBadFetch": "coś poszło nie tak z pobieraniem informacji. wybierz inny format i rozdzielczość albo po prostu spróbuj ponownie później.",
"ErrorCorruptedStream": "niestety ten plik jest uszkodzony. spróbuj ponownie lub wybierz inny format i rozdzielczość.",
"ErrorNoInternet": "nie masz dostępu do internetu albo api {appName} nie działa. sprawdź swoje połączenie i spróbuj ponownie.",
"ErrorCantConnectToServiceAPI": "nie mogę połączyć się z api {s}. wygląda na to, że {s} nie działa albo adres ip serwera {appName} został zablokowany. spróbuj ponownie później.",
"ErrorEmptyDownload": "nie ma tu nic do pobrania! spróbuj pobrać coś innego.",
"ErrorLiveVideo": "nie mogę pobierać transmisji na żywo. poczekaj, aż stream się zakończy i spróbuj ponownie.",
"SettingsAppearanceSubtitle": "wygląd",
"SettingsThemeSubtitle": "motyw",
"SettingsFormatSubtitle": "format pliku",
"SettingsDownloadsSubtitle": "pobrane pliki",
"SettingsQualitySubtitle": "jakość",
"SettingsThemeAuto": "automatyczny",
"SettingsThemeLight": "jasny",
"SettingsThemeDark": "ciemny",
"SettingsQualitySwitchMax": "maksymalna",
"SettingsQualitySwitchHigh": "wysoka",
"SettingsQualitySwitchMedium": "średnia",
"SettingsQualitySwitchLow": "niska",
"SettingsQualitySwitchLowest": "minimalna",
"SettingsKeepDownloadButton": "pozostaw &gt;&gt; widoczny",
"AccessibilityKeepDownloadButton": "pozostaw przycisk pobierania zawsze widoczny",
"SettingsEnableDownloadPopup": "pytaj o sposób zapisu",
"AccessibilityEnableDownloadPopup": "pytaj co zrobić z pobranymi plikami",
"SettingsFormatDescription": "wybierz webm, jeżeli potrzebujesz najwyższej możliwej jakości. filmy webm są zwykle wyższej jakości, ale urządzenia z ios nie odtwarzają ich natywnie.",
"SettingsQualityDescription": "jeżeli wybrana jakość nie będzie dostępna, zostanie wybrana najbliższa pasująca.\njeżeli chcesz wrzucić film z youtube na social media, wybierz połączenie mp4 i 720p. te filmy zazwyczaj nie używają kodeka av1, więc powinny się odtwarzać w zasadzie wszędzie.",
"DonateSubtitle": "ciężko się teraz płaci za hosting",
"DonateDescription": "nie podoba mi się stan w jakim są teraz kryptowaluty, ale na razie jest to dla mnie jedyny sposób żeby płacić za coś za granicą. karty mastercard/visa i usługi takie jak paypal nie są już dostępną opcją.",
"LinkGitHubIssues": "&gt;&gt; zgłoś problem lub zobacz kod źródłowy na githubie",
"LinkGitHubChanges": "&gt;&gt; zobacz poprzednie zmiany lub pomóż nam tworzyć na githubie",
"LinkDonateContact": "&gt;&gt; daj mi znać, jeżeli waluta, którą chcesz wesprzeć nie jest na liście",
"NoScriptMessage": "{appName} używa javascriptu do żądań api i interaktywnego interfejsu. musisz zezwolić na javascript, jeżeli chcesz używać tej strony. nie mamy żadnych reklam ani śledzenia, obiecujemy.",
"DownloadPopupDescriptionIOS": "ponieważ masz urządzenie z systemem ios, musisz nacisnąć i przytrzymać przycisk pobierania i wybrać \"pobierz film\" w wyskakującym oknie aby zapisać film. będzie to wymagane tak długo, jak apple będzie wymuszało safari webview na wszystkich deweloperach przeglądarek na ios.",
"DownloadPopupDescription": "przycisk pobierania otwiera nową kartę z pobieranym plikiem. możesz wyłączyć to okno w ustawieniach.",
"DownloadPopupWayToSave": "wybierz sposób zapisu",
"ClickToCopy": "kliknij, aby skopiować",
"Download": "pobierz",
"CopyURL": "skopiuj url",
"AboutTab": "o aplikacji",
"ChangelogTab": "lista zmian",
"DonationsTab": "darowizny",
"SettingsVideoTab": "wideo",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "inne",
"ChangelogLastMajor": "bieżąca wersja i commit",
"AccessibilityModeToggle": "przełącz tryb pobierania",
"DonateLinksDescription": "linki do darowizn otwierają się w nowej karcie. to najlepszy sposób podarowania pieniędzy, jeśli chcesz, aby dotarły do mnie bezpośrednio.",
"SettingsAudioFormatBest": "najlepszy",
"SettingsAudioFormatDescription": "gdy wybierzesz najlepszy format, dostaniesz audio w najlepszej możliwej jakości, ponieważ jest zachowane w oryginalnym formacie. gdy wybierzesz któryś inny, dostaniesz lekko skompresowany plik.",
"Keyphrase": "zapisz to, co kochasz",
"SettingsRemoveWatermark": "wyłącz znak wodny",
"ErrorPopupCloseButton": "rozumiem",
"ErrorLengthAudioConvert": "aktualny limit długości konwersji dźwięku wynosi {s} minut. wybierz \"najlepszy\" format, jeśli chcesz uniknąć ograniczeń.",
"SettingsAudioFullTikTok": "pobierz pełny dźwięk",
"SettingsAudioFullTikTokDescription": "pobiera oryginalny dźwięk lub dźwięk używany w filmie bez żadnych dodatkowych zmian ze strony autora wideo.",
"ErrorCantGetID": "nie można było uzyskać informacji ze skróconego linku. upewnij się, że link działa lub spróbuj pełnego.",
"ErrorNoVideosInTweet": "ten tweet nie zawiera filmów ani gifów. spróbuj innego!",
"ImagePickerTitle": "wybierz obrazy do pobrania",
"ImagePickerDownloadAudio": "pobierz dźwięk",
"ImagePickerExplanationPC": "kliknij prawym przyciskiem myszy na obraz, aby go zapisać.",
"ImagePickerExplanationPhone": "naciśnij i przytrzymaj obraz, aby go zapisać.",
"ErrorNoUrlReturned": "serwer nie zwrócił linku do pobrania. to nie powinno się zdarzyć. odśwież stronę i spróbuj ponownie, ale jeśli to nie pomoże, {ContactLink}.",
"ErrorUnknownStatus": "otrzymano odpowiedź, której nie można przetworzyć. prawdopodobnie coś o statusie jest nieprawidłowe. to nigdy nie powinno się zdarzyć. odśwież stronę i spróbuj ponownie, ale jeśli to nie pomoże, {ContactLink}.",
"PasteFromClipboard": "wklej ze schowka",
"FollowTwitter": "obserwuj {appName} na twitterze po ankiety, aktualizacje i więcej: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>",
"ChangelogOlder": "poprzednie wersje",
"ChangelogPressToExpand": "pokaż",
"Miscellaneous": "pozostałe",
"ModeToggleAuto": "tryb auto",
"ModeToggleAudio": "tryb audio",
"SettingsDisableNotifications": "ukryj plakietki z powiadomieniami",
"MediaPickerTitle": "wybierz co zapisać",
"MediaPickerExplanationPC": "kliknij lub kliknij prawym przyciskiem, aby pobrać to, co chcesz",
"MediaPickerExplanationPhone": "naciśnij lub naciśnij i przytrzymaj, aby zapisać to, co chcesz",
"MediaPickerExplanationPhoneIOS": "naciśnij i przytrzymaj, ukryj podgląd i wybierz \"odnośnik pobierania\", aby zapisać.",
"TwitterSpaceWasntRecorded": "ten pokój na twitterze nie był nagrywany, więc nie mogę nic pobrać. spróbuj inny!",
"ErrorCantProcess": "no i chuj, nie udało się :(\nmożesz spróbować ponownie, ale jeśli problem będzie się powtarzał, {ContactLink}.",
"ChangelogPressToHide": "zwiń"
}
}

View file

@ -1,115 +0,0 @@
{
"name": "português (brasil)",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">abra um issue no github</a>"
},
"strings": {
"LinkInput": "cole seu link aqui",
"AboutSummary": "{appName} é o lugar ideal para baixar conteúdo das redes sociais. sem anúncios, rastreadores ou outras coisas suspeitas. apenas cole um link e pronto!",
"AboutSupportedServices": "serviços suportados atualmente:",
"EmbedBriefDescription": "salve conteúdo das redes sociais sem irritações",
"MadeWithLove": "feito com <3 por wukko",
"AccessibilityInputArea": "área de entrada de link",
"AccessibilityOpenAbout": "abrir janela sobre",
"AccessibilityDownloadButton": "botão de download",
"AccessibilityOpenSettings": "abrir janela de configurações",
"AccessibilityClosePopup": "fechar a janela",
"AccessibilityOpenDonate": "abrir janela de doação",
"TitlePopupAbout": "o que é {appName}?",
"TitlePopupSettings": "configurações",
"TitlePopupError": "Ah não...",
"TitlePopupChangelog": "o que há de novo?",
"TitlePopupDonate": "apoie {appName}",
"TitlePopupDownload": "baixar",
"ErrorSomethingWentWrong": "algo deu errado e eu não consegui encontrar nada para você. você pode tentar novamente, mas se o problema persistir, por favor {ContactLink}.",
"ErrorUnsupported": "parece que esse serviço ainda não é suportado, ou o link é inválido.",
"ErrorBrokenLink": "{s} é suportado, mas tem algo de errado com o link. talvez você não o copiou completamente?",
"ErrorNoLink": "não consigo adivinhar o que você quer baixar! por favor, me dê um link.",
"ErrorPageRenderFail": "algo deu errado e a página não pôde ser renderizada. se for um problema recorrente ou crítico, por favor {ContactLink}. seria útil se você fornecesse o hash da commit atual ({s}) e passos para recriar o erro. obrigado :D",
"ErrorRateLimit": "você está fazendo pedidos de mais. se acalme e tente novamente daqui a pouco.",
"ErrorCouldntFetch": "não consegui obter nenhuma informação sobre seu link. verifique se ele está correto e tente novamente.",
"ErrorLengthLimit": "o limite de duração atual é {s} minutos. o vídeo que você tentou baixar é maior que {s} minutos. escolha outra coisa para baixar!",
"ErrorBadFetch": "ocorreu um erro quando tentei obter informações sobre seu link. tem certeza de ele funciona? verifique e tente novamente.",
"ErrorCorruptedStream": "infelizmente, esse download está corrompido. tente novamente, ou tente um formato e resolução diferentes.",
"ErrorNoInternet": "não há internet, ou a api de {appName} está fora do ar. verifique sua conexão e tente novamente.",
"ErrorCantConnectToServiceAPI": "não foi possível conectar à api de {s}. parece que {s} está com problemas, ou o ip do {appName} foi bloqueado. tente novamente mais tarde.",
"ErrorEmptyDownload": "não vejo nada que eu poderia baixar daqui. tente um link diferente.",
"ErrorLiveVideo": "não tenho bola de cristal para conseguir baixar o vídeo de uma live que ainda não terminou. espere o fim da stream e tente de novo!",
"SettingsAppearanceSubtitle": "aparência",
"SettingsThemeSubtitle": "tema",
"SettingsFormatSubtitle": "formato do download",
"SettingsQualitySubtitle": "qualidade",
"SettingsThemeAuto": "auto",
"SettingsThemeLight": "claro",
"SettingsThemeDark": "escuro",
"SettingsQualitySwitchMax": "máx.",
"SettingsQualitySwitchHigh": "alta",
"SettingsQualitySwitchMedium": "média",
"SettingsQualitySwitchLow": "baixa",
"SettingsQualitySwitchLowest": "mais baixa",
"SettingsKeepDownloadButton": "manter &gt;&gt; visível",
"AccessibilityKeepDownloadButton": "manter o botão de download sempre visível",
"SettingsEnableDownloadPopup": "pergunte a maneira de salvar",
"AccessibilityEnableDownloadPopup": "perguntar o que fazer com os downloads",
"SettingsFormatDescription": "selecione o formato webm se você quer a melhor qualidade disponível. este formato normalmente tem uma taxa de bits maior, mas dispositivos ios não conseguem reproduzi-lo nativamente.",
"SettingsQualityDescription": "se a qualidade selecionada não estiver disponível, a mais próxima será usada.\nse quiser postar um vídeo do youtube nas redes sociais, selecione uma combinação de mp4 e 720p.",
"LinkGitHubIssues": "&gt;&gt; reporte problemas e confira o código-fonte no github",
"LinkGitHubChanges": "&gt;&gt; veja commits anteriores e contribua no github",
"NoScriptMessage": "{appName} usa javascript para a interface interativa e solicitações de api. você dever habilitar o javascript para usar este site. não tenho nenhum anúncio ou rastreador, eu prometo.",
"DownloadPopupDescriptionIOS": "pressione e segure o botão de download, esconda a pré-visualização do vídeo e, em seguida, selecione \"baixar arquivo\" para salvar.",
"DownloadPopupDescription": "o botão de download abre uma nova aba com o arquivo solicitado. você pode desativar este pop-up nas configurações.",
"DownloadPopupWayToSave": "escolha como salvar",
"ClickToCopy": "pressione para copiar",
"Download": "baixar",
"CopyURL": "copiar url",
"AboutTab": "sobre",
"ChangelogTab": "log de alterações (em inglês)",
"DonationsTab": "doações",
"SettingsVideoTab": "vídeo",
"SettingsAudioTab": "áudio",
"SettingsOtherTab": "outros",
"ChangelogLastMajor": "versão atual & commit",
"AccessibilityModeToggle": "alternar o modo de download",
"DonateLinksDescription": "links de doação abrem em uma nova aba. essa é a melhor maneira para doar se você deseja que eu receba sua doação diretamente.",
"SettingsAudioFormatBest": "melhor",
"SettingsAudioFormatDescription": "quando o melhor formato é selecionado, você obtém o áudio na melhor qualidade disponível, pois ele não é recodificado. ao selecionar outro formato, você obterá um arquivo um pouco comprimido. ",
"Keyphrase": "salve o que você ama",
"SettingsRemoveWatermark": "desativar marca d'água",
"ErrorPopupCloseButton": "entendido",
"ErrorLengthAudioConvert": "o limite de duração para a conversão de áudio é {s} minutos. selecione o formato \"melhor\" se você quiser evitar limitações.",
"SettingsAudioFullTikTok": "baixar áudio completo",
"SettingsAudioFullTikTokDescription": "baixa o áudio ou som original usado no vídeo sem as mudanças feitas pelo autor do vídeo.",
"ErrorCantGetID": "não conseguir obter toda a informação do link encurtado. verifique se o link funciona, ou tente usar um completo.",
"ErrorNoVideosInTweet": "não achei nenhum vídeo ou gif nesse tweet. tente outro!",
"ImagePickerTitle": "escolha imagens pra baixar",
"ImagePickerDownloadAudio": "baixar áudio",
"ImagePickerExplanationPC": "clique com o botão direito em uma imagem para salvá-la",
"ImagePickerExplanationPhone": "pressione e segure uma imagem para salvá-la",
"ErrorNoUrlReturned": "o servidor não retornou um link de download. isso nunca deveria acontecer. recarregue a página e tente novamente, mas se isso não ajudar, {ContactLink}.",
"ErrorUnknownStatus": "eu recebi uma resposta que não consigo processar. o provável é que há algo de errado com o status. isso nunca deveria acontecer. recarregue a página e tente novamente. se isso não ajudar, {ContactLink}.",
"PasteFromClipboard": "colar da área de transferência",
"FollowTwitter": "siga a conta do {appName} no twitter para enquetes, atualizações e muito mais: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>",
"ChangelogOlder": "versões anteriores (em inglês)",
"ChangelogPressToExpand": "clique para expandir",
"Miscellaneous": "outros",
"ModeToggleAuto": "modo automático",
"ModeToggleAudio": "modo áudio",
"SettingsDisableNotifications": "esconder pontos de notificação",
"MediaPickerTitle": "escolha o que salvar",
"MediaPickerExplanationPC": "clique ou clique com o botão direito para baixar o que você quer.",
"MediaPickerExplanationPhone": "pressione ou pressione e segure para baixar o que você quer.",
"MediaPickerExplanationPhoneIOS": "aperte e segure, oculte a pré-visualização e então selecione \"baixar arquivo\" para salvar.",
"TwitterSpaceWasntRecorded": "este espaço do twitter não foi gravado, então não há nada para baixar. tente outro!",
"ErrorCantProcess": "não pude processar o seu pedido :(\nvocê pode tentar novamente, mas se eu problema persistir, por favor {ContactLink}.",
"ChangelogPressToHide": "pressione aqui para recolher",
"Donate": "doar",
"DonateSub": "me ajude a mantê-lo",
"DonateExplanation": "{appName} não serve anúncios ou vende os seus dados (e nunca irá), portanto é <span class=\"text-backdrop\">completamente gratuito para uso</span>. mas ei! aparentemente, a manutenção de um serviço usado por milhares de pessoas é uma coisa cara.\n\nse alguma vez você achou o {appName} útil e quer ajudar a mantê-lo online, ou simplesmente deseja agradecer ao desenvolvedor, considere doar algo! cada centavo ajuda, e é aceito com MUITA gratidão.",
"DonateVia": "doar via",
"DonateHireMe": "ou, alternativamente, você pode <a class=\"text-backdrop\" href=\"{s}\" target=\"_blank\">me contratar</a>.",
"SettingsVideoMute": "silenciar áudio",
"SettingsVideoMuteExplanation": "desativa o áudio do vídeo baixado, quando possível. ignorado quando o modo áudio está ativado ou se o serviço só suporta áudio.",
"SettingsVideoGeneral": "geral",
"ErrorSoundCloudNoClientId": "não consegui encontrar o client_id necessário para obter os dados de áudio do soundclound. tente novamente, e se o problema persistir, {ContactLink}"
}
}

View file

@ -1,25 +1,25 @@
{
"name": "русский",
"substrings": {
"ContactLink": "<a class=\"text-backdrop italic\" href=\"{repo}\" target=\"_blank\">напиши об этом на github (можно на русском)</a>"
"ContactLink": "<a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши об этом на github (можно на русском)</a>"
},
"strings": {
"AppTitleCobalt": "кобальт",
"LinkInput": "вставь ссылку сюда",
"AboutSummary": "{appName} - твой друг при скачивании контента из соцсетей и других сервисов. никакой рекламы, трекеров и прочего мусора. вставляешь ссылку и получаешь файл. всё. ничего лишнего.",
"AboutSummary": "кобальт - твой друг при скачивании контента из соцсетей и других сервисов. никакой рекламы, трекеров и прочего мусора. вставляешь ссылку и получаешь файл. всё. ничего лишнего.",
"EmbedBriefDescription": "сохраняй то, что любишь. без рекламы, трекеров и лишней мороки.",
"MadeWithLove": "сделано wukko, с <3",
"AccessibilityInputArea": "зона вставки ссылки",
"AccessibilityOpenAbout": "открыть окно с инфой",
"AccessibilityDownloadButton": "кнопка скачивания",
"AccessibilityOpenSettings": "открыть настройки",
"AccessibilityClosePopup": "закрыть окно",
"AccessibilityOpenDonate": "сделать пожертвование",
"TitlePopupAbout": "что за {appName}?",
"TitlePopupAbout": "что за кобальт?",
"TitlePopupSettings": "настройки",
"TitlePopupError": "опаньки...",
"TitlePopupChangelog": "что нового?",
"TitlePopupDonate": "поддержи {appName}",
"TitlePopupDownload": "как продолжить?",
"TitlePopupDonate": "поддержи кобальт",
"TitlePopupDownload": "как сохранить?",
"ErrorSomethingWentWrong": "что-то пошло совсем не так и у меня не получилось ничего для тебя достать. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"ErrorUnsupported": "с твоей ссылкой что-то не так, или же этот сервис ещё не поддерживается. может быть, ты вставил не ту ссылку?",
"ErrorBrokenLink": "{s} поддерживается, но с твоей ссылкой что-то не так. может быть, ты её не полностью скопировал?",
@ -30,7 +30,7 @@
"ErrorLengthLimit": "я не могу обрабатывать видео длиннее чем {s} минут(ы), так что скачай что-нибудь покороче!",
"ErrorBadFetch": "произошла какая-то ошибка при получении данных по твоей ссылке. убедись, что она работает, и попробуй ещё раз.",
"ErrorNoInternet": "не получилось подключиться к серверу. проверь подключение к интернету и попробуй ещё раз!",
"ErrorCantConnectToServiceAPI": "у меня не получилось подключиться к серверу этого сервиса. возможно он лежит, или же {appName} заблокировали. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"ErrorCantConnectToServiceAPI": "у меня не получилось подключиться к серверу этого сервиса. возможно он лежит, или же кобальт заблокировали. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"ErrorEmptyDownload": "я не нашёл того, что могу скачать. попробуй другую ссылку!",
"ErrorLiveVideo": "я пока что не умею заглядывать в будущее, поэтому дождись окончания прямого эфира, и потом уже скачивай видео!",
"SettingsAppearanceSubtitle": "внешний вид",
@ -38,22 +38,21 @@
"SettingsFormatSubtitle": "формат",
"SettingsQualitySubtitle": "качество",
"SettingsThemeAuto": "авто",
"SettingsThemeLight": "светлая",
"SettingsThemeDark": "тёмная",
"SettingsThemeLight": "светлый",
"SettingsThemeDark": "тёмный",
"SettingsKeepDownloadButton": "всегда показывать &gt;&gt;",
"AccessibilityKeepDownloadButton": "всегда показывать кнопку скачивания на экране",
"SettingsEnableDownloadPopup": "выбор метода скачивания",
"AccessibilityEnableDownloadPopup": "спрашивать, что делать с загрузками",
"SettingsQualityDescription": "если выбранное качество недоступно, то выбирается ближайшее к нему.",
"LinkGitHubChanges": "&gt;&gt; смотри предыдущие изменения на github",
"NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.",
"DownloadPopupDescriptionIOS": "наиболее простой метод скачивания видео на ios:\n1. добавь <a class=\"text-backdrop italic\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">этот сценарий siri</a>.\n2. нажми \"поделиться\" выше и выбери \"save to photos\" в открывшемся окне.\nесли появляется окно с запросом разрешения, то прочитай его, потом нажми \"всегда разрешать\".\n\nальтернативный метод: зажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.\nпотом открой загрузки в safari, выбери скачанный файл, нажми иконку \"поделиться\", и, наконец, нажми \"сохранить видео\".",
"NoScriptMessage": "кобальт использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.",
"DownloadPopupDescriptionIOS": "наиболее простой метод скачивания видео на ios:\n1. добавь <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">этот сценарий siri</a>.\n2. нажми \"поделиться\" выше и выбери \"save to photos\" в открывшемся окне.\nесли появляется окно с запросом разрешения, то прочитай его, потом нажми \"всегда разрешать\".\n\nальтернативный метод:\nзажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.\nпотом открой загрузки в safari, выбери скачанный файл, нажми иконку \"поделиться\", и, наконец, нажми \"сохранить видео\".",
"DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода скачивания файла в настройках.",
"DownloadPopupWayToSave": "выбери, как сохранить",
"ClickToCopy": "нажми, чтобы скопировать",
"Download": "скачать",
"CopyURL": "скопировать",
"AboutTab": "о {appName}",
"AboutTab": "о сайте",
"ChangelogTab": "изменения",
"DonationsTab": "донаты",
"SettingsVideoTab": "видео",
@ -65,11 +64,11 @@
"SettingsAudioFormatBest": "лучший",
"SettingsAudioFormatDescription": "когда выбран \"лучший\", ты получишь аудио без каких-либо изменений. такое, какое оно есть на стороне сервиса. если же выбрано что-то другое, то аудио будет немного сжато.",
"Keyphrase": "сохраняй то, что любишь",
"SettingsRemoveWatermark": "убирать ватермарку",
"SettingsRemoveWatermark": "убрать ватермарку",
"ErrorPopupCloseButton": "ясно",
"ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат, чтобы обойти ограничения.",
"SettingsAudioFullTikTok": "полное аудио",
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео. без каких-либо изменений от автора поста.",
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео, без каких-либо изменений от автора поста.",
"ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, а если так и не получится, то {ContactLink}.",
"ErrorNoVideosInTweet": "я не смог найти никакого медиа контента в этом твите. попробуй другой!",
"ImagePickerTitle": "выбери картинки для скачивания",
@ -79,46 +78,72 @@
"ErrorNoUrlReturned": "я не получил ссылку для скачивания от сервера. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"ErrorUnknownStatus": "сервер ответил мне чем-то непонятным. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"PasteFromClipboard": "вставить и скачать",
"ChangelogOlder": "предыдущие версии (на английском)",
"ChangelogOlder": "предыдущие версии (тоже на английском)",
"ChangelogPressToExpand": "раскрыть",
"Miscellaneous": "разное",
"ModeToggleAuto": "авто режим",
"ModeToggleAudio": "аудио режим",
"SettingsDisableNotifications": "cкрыть уведомления",
"MediaPickerTitle": "выбери, что сохранить",
"MediaPickerExplanationPC": "кликни, чтобы скачать. также можно скачать через контекстное меню правой кнопки мыши.",
"MediaPickerExplanationPC": "кликни то, что хочешь скачать. также можно скачать правой кнопки мыши.",
"MediaPickerExplanationPhone": "нажми, или нажми и удерживай, чтобы скачать.",
"MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью и выбери \"загрузить файл по ссылке\", чтобы скачать.",
"TwitterSpaceWasntRecorded": "мне нечего скачать, так как этот twitter space не был записан. попробуй другой!",
"ErrorCantProcess": "я не смог обработать твой запрос :(\nты можешь попробовать ещё раз, но если не поможет, то {ContactLink}.",
"ChangelogPressToHide": "скрыть",
"Donate": "задонатить",
"Donate": "донаты",
"DonateSub": "ты можешь помочь!",
"DonateExplanation": "{appName} не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно</span>. но оказывается, что разработка и поддержка сервиса, которым пользуются более 150 тысяч людей, обходится довольно затратно.\n\nесли {appName} тебе помог и ты хочешь поблагодарить разработчика, то это можно сделать через донаты! каждый рубль помогает мне, моим котам, и {appName}! спасибо :)",
"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": "открыть",
"DonateHireMe": "...или же ты можешь <a class=\"text-backdrop italic\" href=\"{s}\" target=\"_blank\">пригласить меня на работу</a> :)",
"DonateHireMe": "...или же ты можешь <a class=\"text-backdrop link\" href=\"{s}\" target=\"_blank\">пригласить меня на работу</a> :)",
"SettingsVideoMute": "убрать аудио",
"SettingsVideoMuteExplanation": "убирает аудио при загрузке видео, но только когда это возможно.",
"SettingsVideoMuteExplanation": "убирает звук при загрузке видео, но только когда это возможно.",
"ErrorSoundCloudNoClientId": "мне не удалось достать временный токен, который необходим для скачивания аудио из soundcloud. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"CollapseServices": "что поддерживается?",
"CollapseSupport": "поддержка и исходный код",
"CollapsePrivacy": "политика конфиденциальности",
"ServicesNote": "этот список далеко не финальный и постоянно пополняется. заглядывай сюда почаще, тогда точно будешь знать, что поддерживается!",
"FollowSupport": "подписывайся на аккаунты {appName} на mastodon или twitter для новостей, поддержки, участия в опросах, и многого другого:",
"SupportNote": "так как я один занимаюсь разработкой и поддержкой в одиночку, время ожидания ответа может достигать нескольких часов. я отвечаю всем, не стесняйся.",
"FollowSupport": "оставайтесь на связи с кобальтом для новостей, поддержки, участия в опросах, и многого другого:",
"SupportNote": "так как я занимаюсь разработкой и поддержкой в одиночку, время ожидания ответа может достигать нескольких часов. но я отвечаю всем, так что не стесняйся.",
"SourceCode": "пиши о проблемах, шарься в исходнике, или же форкай репозиторий:",
"PrivacyPolicy": "политика конфиденциальности {appName} довольно проста: ничего не хранится об истории твоих действий или загрузок. совсем. даже ошибки.\nто, что ты скачиваешь - только твоё личное дело.\n\nв случаях, когда твоей загрузке требуется лайв-рендер, временно хранится неотслеживаемая информация. это необходимо для работы такого типа загрузок.\n\nв этом случае, <span class=\"text-backdrop\">sha256 хэш (с солью) твоего ip адреса</span> и данные о запрошенном стриме хранятся в ОЗУ сервера в течение <span class=\"text-backdrop\">двух минут</span>. по истечении этого периода всё стирается. хэш твоего ip адреса используется для предоставления доступа к стриму только тебе. ни у кого (даже у меня) нет доступа к временно хранящимся данным, так как оригинальный код {appName} не предоставляет такой возможности.\n\nты всегда можешь посмотреть <a class=\"text-backdrop italic\" href=\"{repo}\" target=\"_blank\">исходный код {appName}</a> и убедиться, что всё так, как описано.",
"ErrorYTUnavailable": "это видео недоступно или же ограничено по возрасту на youtube. пока что я не умею скачивать подобные видео. попробуй другое!",
"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",
"SettingsCodecDescription": "h264: обширная поддержка плеерами, но макс. качество всего лишь 1080p.\nav1: слабая поддержка плеерами, но поддерживает 8k и HDR.\nvp9: обычно наиболее высокий битрейт, лучше сохраняется качество видео. поддерживает 4k и HDR.\n\nвыбирай h264, если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями.",
"SettingsAudioDub": "звуковая дорожка для видео с youtube",
"SettingsAudioDubDescription": "определяет, какая звуковая дорожка используется при скачивании видео. если дублированная дорожка недоступна, то вместо неё используется оригинальная.\n\nоригинал: используется оригинальная дорожка.\nавто: используется язык браузера (и {appName}).",
"SettingsAudioDubDescription": "определяет, какая звуковая дорожка используется при скачивании видео. если дублированная дорожка недоступна, то вместо неё используется оригинальная.\n\nоригинал: используется оригинальная дорожка.\nавто: используется язык браузера и интерфейса кобальта.",
"SettingsDubDefault": "оригинал",
"SettingsDubAuto": "авто",
"SettingsVimeoPrefer": "тип загрузок с vimeo",
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: {appName} совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, {appName} скачает \"dash\".",
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: кобальт совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, кобальт скачает \"dash\".",
"ShareURL": "поделиться",
"ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость была ограничена. попробуй другой!"
"ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость была ограничена. попробуй другой!",
"ErrorTwitterRIP": "твиттер ограничил доступ к любому контенту на сайте для пользователей без аккаунтов. я нашёл лазейку, чтобы доставать обычные твиты, а для spaces, к сожалению, нет. я ищу возможные варианты выхода из ситуации.",
"UrgentDonate": "нужна твоя помощь!",
"PopupCloseDone": "готово",
"Accessibility": "общедоступность",
"SettingsReduceTransparency": "уменьшить прозрачность",
"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ну а если хочешь, то открой настройки сайта и разреши доступ на чтение буфера обмена.",
"SupportSelfTroubleshooting": "возникли проблемы? попробуй сначала исправить всё сам <a class=\"text-backdrop link\" href=\"{repo}/wiki/Troubleshooting\" target=\"_blank\">по этому гиду!</a>",
"AccessibilityGoBack": "вернуться назад и закрыть окно",
"CollapseKeyboard": "горячие клавиши",
"KeyboardShortcutsIntro": "пользуйся кобальтом ещё быстрее с горячими клавишами:",
"KeyboardShortcutQuickPaste": "вставить ссылку",
"KeyboardShortcutClear": "очистить зону вставки ссылки",
"KeyboardShortcutClosePopup": "закрыть все окна",
"CollapseLegal": "правовые штучки",
"FairUse": "кобальт - это инструмент для облегчения скачивания контента из интернета, и он <span class=\"text-backdrop\">не несёт никакой ответственности</span>. ты несёшь ответственность за то, что скачиваешь, как используешь и распространяешь скачанный контент.\n\nкобальт не собирает никакой информации о тебе, и не может донести на тебя, но, пожалуйста, будь сознателен при использовании чужого контента и всегда указывай авторов!\n\nпри использовании в образовательных целях (лекции, домашние задания и т.д.), пожалуйста, прикладывай ссылку на источник.\n\nчестное использование и указание авторства выгодно всем.",
"UrgentFeatureUpdate71": "расширение поддержки сервисов!",
"UrgentThanks": "спасибо за поддержку!",
"SettingsDisableMetadata": "не добавлять метаданные",
"UrgentNewDomain": "новый домен, тот же кобальт",
"NewDomainWelcomeTitle": "привет!",
"NewDomainWelcome": "кобальт переезжает! те же функции, тот же владелец, просто более запоминающийся домен. по-прежнему без рекламы.\n\n<span class=\"text-backdrop\">cobalt.tools</span> - новый основной домен, т.е. где ты сейчас находишься. не забудь обновить закладки и переустановить веб-приложение!",
"DataTransferSuccess": "кстати, твои настройки были перенесены автоматически :)",
"DataTransferError": "при переносе настроек что-то пошло не так. придётся зайти в настройки и настроить кобальт вручную."
}
}

View file

@ -1,106 +0,0 @@
{
"name": "українська",
"substrings": {
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">звернись до творця</a>"
},
"strings": {
"LinkInput": "встав посилання сюди",
"AboutSummary": "{appName} - твій помічник з завантаження контенту з соцмереж. ніякої реклами, трекерів та іншого лайна. вставляєш посилання, отримуєш файл, і допиваєш свій смузі у спокої.",
"AboutSupportedServices": "сервіси, що підтримуються станом на зараз:",
"EmbedBriefDescription": "зберігай контент із соцмереж без роздратувань",
"MadeWithLove": "wukko створив це все з <3",
"AccessibilityInputArea": "місце для посилання",
"AccessibilityOpenAbout": "про {appName}",
"AccessibilityDownloadButton": "кнопка завантаження",
"AccessibilityOpenSettings": "відкрити налаштування",
"AccessibilityOpenChangelog": "відкрити список змін",
"AccessibilityClosePopup": "закрити вікно",
"AccessibilityOpenDonate": "відкрити інфу щодо пожертв",
"TitlePopupAbout": "що таке {appName}?",
"TitlePopupSettings": "налаштування",
"TitlePopupError": "от халепа...",
"TitlePopupChangelog": "що нового?",
"TitlePopupDonate": "підтримай {appName}",
"TitlePopupDownload": "завантажити",
"ErrorSomethingWentWrong": "щось пішло не так, і нічого з того, що ти просив, отримати не вдалось. ти можеш спробувати ще раз, але якщо проблема не зникне, будь ласка, {ContactLink}",
"ErrorUnsupported": "схоже, цей сервіс ще не підтримується, або твоє посилання недійсне.",
"ErrorBrokenLink": "{s} підтримується, але з твоїм посиланням щось не так. можливо, ти не повністю його скопіював?",
"ErrorNoLink": "я не вмію вгадувати, що ти хочеш завантажити! будь ласка, дай мені посилання.",
"ErrorPageRenderFail": "щось пішло не так і сторінку відобразити не вдалося. якщо ця проблема повторюється або надзвичайно важлива, будь ласка, {ContactLink}. було б корисно, якби ти надав поточний хеш коміту ({s}) та кроки відтворення помилки. наперед дякую :D",
"ErrorRateLimit": "ти надсилаєш забагато запитів. заспокойся та спробуй ще раз пізніше.",
"ErrorCouldntFetch": "не зміг отримати ніякої інформації з твого посилання. перевір, щоб посилання було дійсним, та спробуй ще раз.",
"ErrorLengthLimit": "поточний ліміт тривалості становить {s} хв. відео, яке ти намагався завантажити, триває більше {s} хв. спробуй щось інше!",
"ErrorBadFetch": "я намагався отримати інформацію з твого посилання, але сталася помилка. ти впевнений, що воно працює? переконайся у правильності посилання та спробуй ще раз.",
"ErrorCorruptedStream": "на жаль, це завантаження пошкоджено. повтори спробу або спробуй інший формат та роздільну здатність.",
"ErrorNoInternet": "немає інтернету, або api {appName} не працює. перевір з'єднання з інтернетом та спробуй ще раз.",
"ErrorCantConnectToServiceAPI": "у мене не вийшло під'єднатися до api {s}. можливо, {s} не працює, або ip сервера {appName} було заблоковано. спробуй ще раз пізніше.",
"ErrorEmptyDownload": "тут я не бачу нічого, що можна було б завантажити. спробуй інше посилання.",
"ErrorLiveVideo": "я не можу дивитися в майбутнє і завантажувати відео з прямого етеру. почекай, доки трансляція закінчиться, та спробуй ще раз!",
"SettingsAppearanceSubtitle": "вигляд",
"SettingsThemeSubtitle": "тема",
"SettingsFormatSubtitle": "формат завантаження",
"SettingsMiscSubtitle": "інші налаштування",
"SettingsDownloadsSubtitle": "завантаження",
"SettingsQualitySubtitle": "якість",
"SettingsThemeAuto": "авто",
"SettingsThemeLight": "світла",
"SettingsThemeDark": "темна",
"SettingsQualitySwitchMax": "макс",
"SettingsQualitySwitchHigh": "висока",
"SettingsQualitySwitchMedium": "середня",
"SettingsQualitySwitchLow": "низька",
"SettingsQualitySwitchLowest": "найнижча",
"SettingsKeepDownloadButton": "зробити &gt;&gt; видимим",
"AccessibilityKeepDownloadButton": "завжди показувати кнопку завантаження",
"SettingsEnableDownloadPopup": "питати щодо методу зберігання",
"AccessibilityEnableDownloadPopup": "питати, що робити з завантаженнями",
"SettingsFormatDescription": "обери webm, якщо ти бажаєш максимальної якості. webm зазвичай кращий за якістю, але пристрої на ios не можуть програвати їх без сторонніх програм.",
"SettingsQualityDescription": "якщо вибрана роздільна здатність недоступна, замість неї вибирається найближча. якщо ти хочеш опублікувати відео з youtube у twitter, вибери комбінацію mp4 і 720p. з такими відео twitter працює найкраще.",
"DonateSubtitle": "допоможи платити за оренду серверу",
"DonateDescription": "мені не дуже подобається криптовалюта у її сучасному стані, але наразі для мене це єдиний надійний спосіб отримувати гроші та платити за що-небудь за кордоном.",
"LinkGitHubIssues": "&gt;&gt; повідомляй про помилки та переглядай код на github",
"LinkGitHubChanges": "&gt;&gt; ознайомся з попередніми комітами та зроби свій внесок на github",
"LinkDonateContact": "&gt;&gt; дай нам знати, якщо валюти, у якій ти б хотів задонатити, немає у списку",
"NoScriptMessage": "{appName} використовує javascript, щоб отримувати інформацію з сайтів та мати інтерактивний інтерфейс. тобі доведеться дозволити javascript, щоб користуватися сайтом. тут ніколи не буде ніякої реклами чи трекерів.",
"DownloadPopupDescriptionIOS": "в тебе пристрій на ios, тож для завантаження тобі треба натиснути та утримувати кнопку завантаження, а потім у вікні, яке з'явиться, вибрати \"завантажити відео\". такий обряд доведеться виконувати допоки apple не припинить змушувати розробників використовувати safari webview в усіх браузерах.",
"DownloadPopupDescription": "кнопка завантаження відкриває нову вкладку з запитуваним файлом. це можна змінити у налаштуваннях.",
"DownloadPopupWayToSave": "вибери спосіб зберігання",
"ClickToCopy": "натисни, щоб скопіювати",
"Download": "завантажити",
"CopyURL": "скопіювати url",
"AboutTab": "інфа",
"ChangelogTab": "список змін",
"DonationsTab": "донати",
"SettingsVideoTab": "відео",
"SettingsAudioTab": "аудіо",
"SettingsOtherTab": "інше",
"ChangelogLastCommit": "останній коміт (англійською мовою)",
"ChangelogLastMajor": "останнє оновлення",
"AccessibilityModeToggle": "змінити режим завантаження",
"DonateLinksDescription": "лінки на донати відкриваються у нових вкладках. це найкращий спосіб донатити, якщо хочеш, щоб ми безпосередньо отримували гроші.",
"SettingsAudioFormatBest": "найкращий",
"SettingsAudioFormatDescription": "якщо вибрано найкращий формат, буде завантажено оригінал аудіо. якщо вибрано щось інше, завантажений файл буде трохи стисненим.",
"Keyphrase": "зберігай те, що любиш",
"SettingsDisableChangelogOnUpdate": "не показувати \"що нового\" після суттєвих оновлень",
"SettingsRemoveWatermark": "прибрати вотермарку",
"ErrorPopupCloseButton": "зрозуміло",
"ModeToggle": "режим",
"ModeToggleSmart": "розумний",
"ErrorLengthAudioConvert": "я не можу конвертувати аудіо, якщо воно довше ніж {s} хв. вибери \"найкращий\" формат аудіо, щоб завантажити аудіо такої тривалості.",
"SettingsAudioFullTikTok": "завантажувати повне аудіо",
"SettingsAudioFullTikTokDescription": "зазвичай таке аудіо - оригінальний звук або пісня, яке використовується у відео. тобто це аудіо без обрізань, голосу за кадром тощо.",
"ErrorCantGetID": "я не зміг отримати повну інформацію за скороченим посиланням. переконайся, що воно працює, або спробуй повне посилання.",
"ErrorNoVideosInTweet": "цей твіт не містить відео чи gif. спробуй інший!",
"ImagePickerTitle": "вибери зображення для завантаження",
"ImagePickerDownloadAudio": "завантажити аудіо",
"ImagePickerExplanationPC": "клацни на зображення правою кнопкою миші, щоб зберегти.",
"ImagePickerExplanationPhone": "натисни та утримуй зображення, щоб зберегти.",
"ErrorNoUrlReturned": "сервер не повернув посилання для завантаження. таке не має траплятись. онови сторінку і спробуй ще раз, але якщо це не допоможе, {ContactLink}",
"ErrorUnknownStatus": "я отримав відповідь, яку я не можу обробити. найімовірніше, щось не так зі статусом. таке не має траплятись. онови сторінку і спробуй ще раз, але якщо це не допоможе, {ContactLink}",
"PasteFromClipboard": "вставити з буфера обміну",
"SettingsDisableClipboard": "приховати кнопку буфера обміну",
"FollowTwitter": "читай твіттер акаунт {appName}, щоб слідкувати за опитуваннями, новинами та більше: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>",
"ChangelogOlder": "попередні оновлення",
"ChangelogPressToExpand": "натисни, щоб завантажити"
}
}

View file

@ -1,5 +1,5 @@
import * as fs from "fs";
import { appName, links, repo } from "../modules/config.js";
import { links, repo } from "../modules/config.js";
import loadJson from "../modules/sub/loadJSON.js";
const locPath = './src/localization/languages';
@ -7,19 +7,16 @@ const locPath = './src/localization/languages';
let loc = {}
let languages = [];
export function loadLoc() {
fs.readdir(locPath, (err, files) => {
if (err) return false;
files.forEach(file => {
loc[file.split('.')[0]] = loadJson(`${locPath}/${file}`);
languages.push(file.split('.')[0])
});
})
export async function loadLoc() {
const files = await fs.promises.readdir(locPath).catch((e) => { return [] });
files.forEach(file => {
loc[file.split('.')[0]] = loadJson(`${locPath}/${file}`);
languages.push(file.split('.')[0])
});
}
loadLoc();
export function replaceBase(s) {
return s.replace(/\n/g, '<br/>').replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut).replace(/{appName}/g, appName).replace(/{repo}/g, repo).replace(/\*;/g, "&bull;");
return s.replace(/\n/g, '<br/>').replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut).replace(/{repo}/g, repo).replace(/\*;/g, "&bull;");
}
export function replaceAll(lang, str, string, replacement) {
let s = replaceBase(str[string])

View file

@ -6,36 +6,25 @@ import { cleanURL, apiJSON } from "./sub/utils.js";
import { errorUnsupported } from "./sub/errors.js";
import loc from "../localization/manager.js";
import match from "./processing/match.js";
import hostOverrides from "./processing/hostOverrides.js";
export async function getJSON(originalURL, lang, obj) {
try {
let patternMatch, url = decodeURIComponent(originalURL),
hostname = new URL(url).hostname.split('.'),
host = hostname[hostname.length - 2];
if (!url.startsWith('https://')) return apiJSON(0, { t: errorUnsupported(lang) });
switch(host) {
case "youtu":
host = "youtube";
url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`;
break;
case "goo":
if (url.substring(0, 30) === "https://soundcloud.app.goo.gl/") {
host = "soundcloud";
url = `https://soundcloud.com/${url.replace("https://soundcloud.app.goo.gl/", "").split('/')[0]}`
}
break;
case "tumblr":
if (!url.includes("blog/view")) {
if (url.slice(-1) === '/') url = url.slice(0, -1);
url = url.replace(url.split('/')[5], '')
}
break;
}
let overrides = hostOverrides(host, url);
host = overrides.host;
url = overrides.url;
if (!(host && host.length < 20 && host in patterns && patterns[host]["enabled"])) return apiJSON(0, { t: errorUnsupported(lang) });
let pathToMatch = cleanURL(url, host).split(`.${patterns[host]['tld'] ? patterns[host]['tld'] : "com"}/`)[1].replace('.', '');
for (let i in patterns[host]["patterns"]) {
patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(cleanURL(url, host).split(`.${patterns[host]['tld'] ? patterns[host]['tld'] : "com"}/`)[1].replace('.', ''));
patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(pathToMatch);
if (patternMatch) break
}
if (!patternMatch) return apiJSON(0, { t: errorUnsupported(lang) });

View file

@ -1,15 +1,15 @@
import * as esbuild from "esbuild";
import * as fs from "fs";
import { languageList } from "../localization/manager.js";
import { loadLoc, languageList } from "../localization/manager.js";
import { cleanHTML } from "./sub/utils.js";
import page from "./pageRender/page.js";
function cleanHTML(html) {
let clean = html.replace(/ {4}/g, '');
clean = clean.replace(/\n/g, '');
return clean
}
export async function buildFront(commitHash, branch) {
try {
// preload localization files
await loadLoc();
// build html
if (!fs.existsSync('./build/')){
fs.mkdirSync('./build/');
@ -17,6 +17,10 @@ export async function buildFront(commitHash, branch) {
fs.mkdirSync('./build/pc/');
fs.mkdirSync('./build/mob/');
}
// get rid of old build path
if (fs.existsSync('./min')) {
fs.rmSync('./min', { recursive: true, force: true });
}
for (let i in languageList) {
i = languageList[i];
let params = {
@ -36,7 +40,7 @@ export async function buildFront(commitHash, branch) {
// build js & css
await esbuild.build({
entryPoints: ['src/front/cobalt.js', 'src/front/cobalt.css'],
outdir: 'min/',
outdir: 'build/min/',
minify: true,
loader: { '.js': 'js', '.css': 'css', },
charset: 'utf8'

View file

@ -0,0 +1,7 @@
import { buildFront } from "./build.js";
import { getCurrentBranch, shortCommit } from "./sub/currentCommit.js";
const commitHash = shortCommit();
const branch = getCurrentBranch();
await buildFront(commitHash, branch);

View file

@ -1,54 +1,154 @@
{
"current": {
"version": "5.4",
"title": "instagram support, hop, docker, and more!",
"banner": "catphonestand.webp",
"content": "something many of you've been waiting for is finally here! try it out and let me know what you think :)\n\n<span class='text-backdrop'>tl;dr:</span>\n*; added experimental instagram support! download any reels or videos you like, and make sure to report any issues you encounter. yes, you can convert either to audio.\n*; fixed support for on.soundcloud links.\n*; added share button to \"how to save?\" popup.\n*; added docker support.\n*; main instance is now powered by <a class=\"text-backdrop italic\" href=\"https://hop.io/\" target=\"_blank\">hop.io</a>.\n\nservice improvements:\n*; added experimental support for videos from instagram. currently only reels and post videos are downloadable, but i'm looking into ways to save high resolution photos too. if you experience any issues, please report them on either of support platforms.\n*; fixed support for on.soundcloud share links. should work just as well as other versions!\n*; fixed an issue that made some youtube videos impossible to download.\n\ninterface improvements:\n*; new css-only checkmark! yes, i can't stop tinkering with it because slight flashing on svg load annoyed me. now it loads instantly (and also looks slightly better).\n*; fixed copy animation.\n*; minor localization improvements.\n*; fixed the embed logo that i broke somewhere in between 5.3 and 5.4.\n\ninternal improvements:\n*; now using nanoid for live render stream ids.\n*; added support for docker. it's kind of clumsy because of how i get .git folder inside the container, but if you know how to do it better, feel free to make a pr.\n*; cobalt now checks only for existence of environment variables, not exactly the .env file.\n*; changed the way user ip address is retrieved for instances using cloudflare.\n*; added ability to disable cors, both to setup script and environment variables.\n*; moved main instance to <a class=\"text-backdrop italic\" href=\"https://hop.io/\" target=\"_blank\">hop.io</a> infra. there should no longer be random downtimes. huge shout out to the hop team for being so nice and helping me out :D\n\ni can't believe how diverse and widespread cobalt has become. it's used in all fields: music production, education, content creation, and even game development. <span class='text-backdrop'>thank you</span>. this is absolutely nuts.\nif you don't mind sharing, please tell me about your use case. i'd really love to hear how you use cobalt and how i could make it even more useful for you."
"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"
},
"history": [{
"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!",
"banner": {
"file": "meowthproductions.webp",
"width": 640,
"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"
}, {
"version": "7.0",
"date": "August 15, 2023",
"title": "biggest ui refresh yet!",
"banner": {
"file": "meowthcooking.webp",
"width": 640,
"height": 360
},
"content": "hey! this update is huge and mostly aimed to refresh the ui, but there are also some other nice fixes/additions. read below for more info :)\n\n<span class=\"text-backdrop\">tl;dr:</span>\n*; entirety of web app has been refreshed. it's more prettier and optimized than ever, both on phone and desktop.\n*; if you're on ios, try adding cobalt to home screen! it'll look and act like a native app.\n*; all soundcloud links are now supported and audio quality is higher than before.\n*; all x (previously twitter) links are now supported and work properly.\n*; newer reddit videos are downloadable now.\n*; added some sort of eula, list of keyboard shortcuts, updated privacy policy for more clarity. check it all in refreshed about tab!\n*; cobalt now lets you know if your browser doesn't support clipboard pasting and helps you fix it.\n\n<span class=\"text-backdrop\">accessibility notice:</span>\nthis update includes animations and transparency, if you'd like to disable any or all of them, head to settings > other > accessibility.\n\n<span class=\"text-backdrop\">[full changelog]</span>\n\nservice improvements:\n*; fixed unexpected 502 errors when downloading newer reddit videos.\n*; newer reddit videos (with audio) are downloadable now.\n*; upgraded soundcloud downloads to use higher audio quality than before.\n*; all soundcloud links are now supported.\n*; added support for x.com urls.\n*; changed twitter api once again. now everything works, again.\n\nweb improvements:\n*; all-new matte glass aesthetic, applied to revamped popup headers, tab selectors, and also small popups.\n*; rounded corners everywhere! cobalt is now safe for everyone who can't handle sharp objects.\n*; paddings everywhere are smaller, more content fits on the screen at once.\n*; optimized installed web app to look and act like a native app, especially on ios.\n*; added update release dates to changelogs.\n*; cobalt now lets you know if your browser doesn't support clipboard api and helps you fix it.\n*; refreshed all popups: less padding, more content.\n*; completely remade error and download popups, they're consistent with the rest of refreshed design.\n*; refreshed the look of entire changelog tab: separated title and version/commit, made title bigger, evened out all paddings.\n*; replaced close button with back button, moved it to left.\n*; added interaction animations.\n*; added more keyboard shorcuts.\n*; added a list of keyboard shortcuts to about tab.\n*; added eula to about tab. check it out.\n*; added more accessibility options, put them all into one category. you can disable animations and transparency if you want to.\n*; added a link to self-troubleshooting guide to about tab.\n*; renamed 2160p and 4320p to 4k and 8k respectfully for better clarity.\n*; popups now work without any weird workarounds, especially on mobile. they're clean and nice.\n*; home screen now also works without any weird workarounds. it is also clean and nice.\n*; optimized css of almost all ui elements. should be even more consistent across platforms now.\n*; added ability to translate \"cobalt\" more in-depth localization. for example, in russian \"cobalt\" is now \"кобальт\", that's the style i'll be going with from now on.\n*; updated many localization strings for more clarity.\n*; removed ability to change the app name dynamically in all locations. cobalt is a sustained app name.\n*; updated donation and privacy policy texts for more clarity in both english and russian.\n*; home screen now smoothly fades in instead of popping in.\n*; proper banner loading. no more jumping text!\n*; proper banner error handling. if banner wasn't loaded, it'll simply go grey instead of disappearing.\n*; links are no longer italic and are instead underlined.\n*; collapsible lists now have corresponding emoji.\n*; donate button is now highlighted with magenta instead of white.\n*; proper dropdown arrow.\n*; removed 6.0 api fallback.\n*; fixed celebrations emoji. again.\n*; cleaned up all related frontend modules, especially page.js.\n*; urgent notice is now a js element, not a static piece of text. can be updated easily.\n\napi improvements:\n*; now catching all json api related errors.\n*; moved on demand blocks to web server, now changelog can be updated independently from preferred api server.\n*; now sending standard rate limiting headers.\n*; better readability in source.\n\nother improvements:\n*; renamed docker-compose.yml.example to docker-compose.example.yml for linting in code editors.\n*; added a wiki with wip troubleshooting guide on github. more guides are coming soon!\n\nthat's a ton of changes! i really hope you like this update as much as i do.\n\nif you experience any issues, feel free to contact me on any platform listed in about tab! i'd love to hear back from you.\n\nthank you for sticking with me and cobalt, i hope you have THE best day :D"
}, {
"version": "6.2",
"date": "June 27 2023",
"title": "all network issues have been fixed!",
"banner": {
"file": "meowthhammer.webp",
"width": 1280,
"height": 827
},
"content": "hey! there have been some hiccups in cobalt's stability lately, i was going through finals while trying to scale up the infrastructure, and that didn't really work out, lol.\nBUT i'm happy to announce that i've optimized all nodes! <span class=\"text-backdrop\">there should no longer be any networking issues</span>.\n\nenjoy stable experience while i work in background to make cobalt even better :)\n\nhere's what's new in this update:\n*; better button contrast in both themes. \n*; button highlight in light theme now actually looks like a highlight.\n*; removed ip gate for streamables and updated privacy policy to reflect this change.\n*; streamable links now last for 20 seconds instead of 2 minutes.\n*; cleaned up stream verification algorithm. now the same function doesn't run 4 times in a row.\n*; removed deprecated way of hosting a cobalt instance.\n\nthank you for sticking with cobalt, and i hope you have a great day :D\n\nbanner photo is by <a class=\"text-backdrop link\" href=\"https://twitter.com/halftroller\" target=\"_blank\">@halftroller</a> on twitter, thank you so much!"
}, {
"version": "6.0",
"date": "June 7, 2023",
"title": "better reliability, new infrastructure, pinterest support, and way more!",
"banner": {
"file": "catswitchboxes.webp",
"width": 600,
"height": 314
},
"content": "hey! long time no see, hopefully over 40 changes will make up for it :)\n\ncobalt now has an official community discord server. you can go there for news, support, or just to chat. <a class=\"text-backdrop link\" href=\"https://discord.gg/pQPt8HBUPu\" target=\"_blank\">go check it out!</a>\n\n<span class='text-backdrop'>tl;dr</span>\n*; new infra, new hosting structure, new main instance api url. developers, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/blob/current/docs/API.md\" target=\"_blank\">get it here.</a>\n*; added support for pinterest, vine archive, tumblr audio, youtube vr videos.\n*; better web app performance and look.\n*; better stability thanks to load balancing.\n*; (hopefully) no more random video/audio download drops.\n\nservice improvements:\n*; added support for pinterest videos and stories (pr by <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/commit/40291c4d24cb5f441cdddfd26104f149bc4ee27c\" target=\"_blank\">@Snazzah</a>).\n*; added support for tumblr audio. sorry, tumblr.\n*; added support for youtube vr videos. please note that they're in youtube's proprietary ratio.\n*; added support for vine archive.\n*; added support for ancient vk videos in 240p.\n*; fixed an issue related to muted video downloads from tumblr.\n*; moved to twitter v2 api.\n*; soundcloud share links are now processed without errors.\n\nui improvements:\n*; lazy image loading. should significantly speed up the page load.\n*; fixed checkbox width on mobile devices.\n*; addition of a temporary urgent notice.\n*; added hover border to all buttons.\n*; less annoying donation button highlight.\n*; more consistent color scheme.\n*; added link to a discord server into about popup.\n*; remember celebratory emoji changes? they've been fixed, and are now dynamically loaded!\n*; changelog history now lets you try to load it again if first attempt failed for whatever reason.\n*; padding (everywhere) has been slightly reduced to fit in more content and be consistent across ui.\n*; added more info to the \"how to save\" popup for ios devices.\n*; crypto wallet press-to-copy buttons now look like buttons.\n*; improved ui layout for smallest screens (iphone 5, 5s, se, etc).\n*; removed partial translations for sake of clarity and consistency.\n\ninternal improvements:\n*; separated web and api servers. they're now completely independent and therefore more stress-resistant.\n*; added a dedicated script for building the web app if you don't want to reload the frontend server.\n*; web app building improvements.\n*; async localization preloading.\n*; consistent server start time reporting.\n*; dynamic stream and ip hashing salt generation.\n\ninfrastructure improvements:\n*; load balancing: your api requests are now sent to the least busy server. yes, there are now several of them with more to come in the future.\n*; when possible, server in closest region is used instead of a far-away one. this should help with download speeds.\n*; currently there are multiple servers in europe. i will let you know when (and if) i manage to get an american one.\n\nupdates for developers and instance hosters:\n*; server info api endpoint: you can now check up on the api server of choice. it reports all the basic info you may need. <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/blob/current/docs/API.md#get-apiserverinfo\" target=\"_blank\">check the api docs</a> for more info.\n*; api names: each and every api instance should have a distinctive name. this will be useful in the future :)\n*; added docker compose sample config.\n*; updated and more granular setup script.\n*; better api scalability and faster server start up thanks to web and api separation.\n*; added ability to specify ffmpeg threads. simply add ffmpegThreads to your environment variables!\n\ni'm still in awe from how popular cobalt has become. there are now over 200k of unique users monthly, and that number only keeps growing. i even had to come up with something to accommodate for larger traffic, it's absolutely insane.\n\nlove you all, have a great day :D"
}, {
"version": "5.4",
"title": "instagram support, docker, and more!",
"banner": {
"file": "catphonestand.webp",
"width": 451,
"height": 272
},
"content": "something many of you've been waiting for is finally here! try it out and let me know what you think :)\n\n<span class='text-backdrop'>tl;dr:</span>\n*; added experimental instagram support! download any reels or videos you like, and make sure to report any issues you encounter. yes, you can convert either to audio.\n*; fixed support for on.soundcloud links.\n*; added share button to \"how to save?\" popup.\n*; added docker support.\n\nservice improvements:\n*; added experimental support for videos from instagram. currently only reels and post videos are downloadable, but i'm looking into ways to save high resolution photos too. if you experience any issues, please report them on either of support platforms.\n*; fixed support for on.soundcloud share links. should work just as well as other versions!\n*; fixed an issue that made some youtube videos impossible to download.\n\ninterface improvements:\n*; new css-only checkmark! yes, i can't stop tinkering with it because slight flashing on svg load annoyed me. now it loads instantly (and also looks slightly better).\n*; fixed copy animation.\n*; minor localization improvements.\n*; fixed the embed logo that i broke somewhere in between 5.3 and 5.4.\n\ninternal improvements:\n*; now using nanoid for live render stream ids.\n*; added support for docker. it's kind of clumsy because of how i get .git folder inside the container, but if you know how to do it better, feel free to make a pr.\n*; cobalt now checks only for existence of environment variables, not exactly the .env file.\n*; changed the way user ip address is retrieved for instances using cloudflare.\n*; added ability to disable cors, both to setup script and environment variables.\n\ni can't believe how diverse and widespread cobalt has become. it's used in all fields: music production, education, content creation, and even game development. <span class='text-backdrop'>thank you</span>. this is absolutely nuts.\nif you don't mind sharing, please tell me about your use case. i'd really love to hear how you use cobalt and how i could make it even more useful for you."
}, {
"version": "5.3",
"title": "better looks, better feel",
"banner": "cattired.webp",
"banner": {
"file": "cattired.webp",
"width": 640,
"height": 286
},
"content": "this update isn't as big as previous ones, but it still greatly enhances the cobalt experience.\n\nhere's what's up:\n*; new mode switcher! elegant and 100% clear. should no longer cause any confusion. let me know if you like it better this way :D\n*; wide paste button on mobile is back, but now it's even closer to your finger.\n*; removed the weird grey chin on changelog banners.\n*; removed left-handed layout toggle since it is no longer needed.\n*; fixed input area display in chromium 112+.\n*; centered the main action box.\n*; cleaned up css of main action box to get rid of tricks and ensure correct display on all devices.\n*; fixed a bug that'd cause notifications dots to disappear when an unrelated checkbox was checked.\n\nhopefully from now on i'll focus on adding support for more services.\nthank you for using cobalt. stay cool :)"
}, {
"version": "5.2",
"title": "fastest one in the game",
"banner": "catspeed.webp",
"banner": {
"file": "catspeed.webp",
"width": 640,
"height": 356
},
"content": "hey, notice anything different? well, at very least the page loaded way faster! this update includes many improvements and fixes, but also some new features.\n\n<span class=\"text-backdrop\">tl;dr:</span>\n*; twitter retweet links are now supported.\n*; all vimeo videos should now be possible to download.\n*; you now can download audio from vimeo.\n*; it's now possible to pick between preferred vimeo download method in settings.\n*; fixed issues related to tiktok, twitter, twitter spaces, and vimeo downloads.\n*; overall cobalt performance should be MUCH better.\n\nservice improvements:\n*; added support for twitter retweet links. now all kinds of tweet links are supported.\n*; fixed the issue related to periods in tiktok usernames (#96).\n*; fixed twitter spaces downloads.\n*; added support for audio downloads from vimeo.\n*; added ability to choose between \"progressive\" and \"dash\" vimeo downloads. go to settings > video to pick your preference.\n*; fixed the issue related to vimeo quality picking.\n*; fixed the issue when vimeo module wouldn't show appropriate errors and instead would fallback to default ones.\n*; improved audio only downloads for some edge cases.\n*; (hopefully) better youtube reliability.\n*; temporarily disabled douyin support due to api endpoint cut off.\n\ninterface improvements:\n*; merged clipboard and mode switcher rows into one for mobile view.\n*; added left-handed layout toggle for those who prefer to have the clipboard button on left.\n*; new custom-made clipboard icon. now it clearly indicates what it does.\n*; improved english and russian localization. both are way more direct and less bloaty.\n*; frontend page is now rendered once and is cached on disk instead of being rendered every time someone requests a page. this greatly improves page loading speeds and further reduces strain put on the server.\n*; frontend page is now minimized just like js and css files. this should minimize traffic wasted on loading the page, along with minor loading speed improvement.\n*; added proper checkbox icon for better clarity.\n*; checkboxes are now stretched edge-to-edge on phone to be easier to manage for right-handed people.\n*; removed button hover highlights on phones.\n*; fixed button press animations for safari on ios.\n*; fixed text selection on ios. previously you could select text or images anywhere, but now they're selectable in limited places, just like on other platforms.\n*; frontend platform is now marked in settings: p is for pc; m is for mobile; i is for ios. this is done for possible future debugging and issue-solving.\n*; better error messaging.\n\ninternal improvements:\n*; better rate limiting, there should be way less cases of accidental limits.\n*; added support for m3u8 playlists. this will be useful for future additions, and is currently used by vimeo module.\n*; added support for \"chop\" stream format for vimeo downloads.\n*; fixed vk user id extraction. i assumed the - in url was a separator, but it's actually a part of id.\n*; completely reworked the vimeo module. it's much cleaner and better performant now.\n*; minor clean ups across the board.\n\nnot really related to this update, but thank you for 50k monthly users! i really appreciate that you're still here, because that means i'm doing some things right :D"
}, {
"version": "5.1",
"title": "the evil has been defeated",
"banner": "happymeowth.webp",
"banner": {
"file": "happymeowth.webp",
"width": 500,
"height": 330
},
"content": "hey, ever wanted to download a youtube video without a hassle? cobalt is here to help. this update fixes all issues related to youtube downloads.\nnot only that, but it also introduces features never before seen in a downloader, such as youtube dub downloads! read below to see what's up :)\n\n<span class=\"text-backdrop\">tl;dr:</span>\n*; audio in youtube videos FINALLY no longer gets cut off.\n*; you now can pick any video resolution you want (from 360p to 8k) and any possible youtube video codec (h264/av1/vp9).\n*; you now can download youtube videos with dubs in your native language. just check settings > audio.\n*; youtube processing has been vastly sped up.\n\nok, now onto the nerdy part of changelog. this update is pretty huge and includes improvements across the board.\n\nservice improvements:\n*; all youtube functionality has been reworked. cobalt now relies on innertube apis, not web scraping.\n*; random audio cut off issue has been fixed, let me know if it ever occurs again. (closes #62, #66, #75, #88).\n*; added support for youtube dubs. currently it's using your browser's default language when enabled, but i have plans on making a picker. i'll ask people on twitter and mastodon if this feature is needed, and add a picker in next updates.\n*; instead of adding more quality presets, i added granular quality options. pick whatever you like, from 360p up to 4320p (for all services, not just youtube).\n*; replaced a format picker with codec picker for youtube. you can pick h264, av1, or vp9. all of them should work as expected (closes #88).\n*; youtube audio files are now properly matched to corresponding video files.\n*; it's now always possible to download pristine h264 720p/360p videos from youtube. these videos will work ANYWHERE, so they're default for mobile.\n*; youtube requests are no longer permanently cached, ram usage should drop even further.\n*; youtube video and audio file names now include codec and dub language when applicable.\n*; max video and audio duration limits have been bumped up to 3 hours.\n*; general performance of entire youtube download process has been greatly improved.\n*; vk module has been reworked to be more compact and not make use of outdated technique of quality picking. should also be way more reliable.\n\ninternal improvements:\n*; cleaned up services config, all constants have been moved directly to modules for quicker access.\n*; matching module has been slightly cleaned up.\n\ninterface improvements:\n*; many descriptions and error messages have been slightly tuned to be less wordy.\n*; unnecessary title duplications in settings have been merged into one.\n*; added more clarity to quality and codec descriptions.\n\nif you use cobalt api, please note that you have to update your creation to support new features.\n\nthis is the second batch of 5.x improvements, there's way more to come. thank you for being here, i really appreciate your support.\n\nif you want to thank me (the developer), there's a nice tab under this changelog that has \"donations\" text on it. anything helps me continue developing and hosting the friendliest media downloader :D"
}, {
"version": "5.0",
"title": "it's all about attention to detail!",
"banner": "valentines.webp",
"banner": {
"file": "valentines.webp",
"width": 489,
"height": 374
},
"content": "happy valentine's day! i have an update for you, as a gift :D\n\ntl;dr: added support for <span class=\"text-backdrop\">reddit gifs</span>, fixed douyin downloads, fixed vimeo quality picking, revamped entirety of codebase, and many other fixes.\n\nhere's more info:\n\nthis update is mostly about cleaning up and polishing the codebase, but it also has some new features. here's what's up:\n\nservice-related improvements:\n*; you now can download gifs from reddit!\n*; attempting to download a video from douyin no longer throws an error (bytedance changed the api endpoint, yet again).\n*; fixed quality picking for vimeo downloads.\n*; fixed length limit check in vimeo module.\n*; fixed support for \"user view\" vk clips links.\n*; various twitter errors are now displayed correctly instead of falling back to the default error.\n*; state of all services is now tested on each commit.\n\nui improvements:\n*; cobalt social links no longer disappear if you have an aggressive ad blocking extension installed.\n*; various localization improvements for both english and russian.\n*; changed some service aliases to display full list of supported downloads.\n*; added current branch information to version text (in settings).\n*; fixed typos in older changelogs.\n\ninternal improvements:\n*; <span class=\"text-backdrop\">everything</span> has been sanitized, improved, and refactored. code is now much easier to read and maintain.\n*; rewrote and/or optimized all modules that were messy or inefficient.\n*; all git interaction functions now store info in cache instead of fetching it every time the function is called.\n*; added a test script that checks functionality of all supported services.\n*; updated deepsource config. checks are more accurate now.\n*; requests from internet explorer are now dropped entirely instead of redirecting people stuck in 90s to a proper browser download page. this was done to avoid (my) personal bias towards browsers.\n\ni put a ton of effort into this version, and i hope you like it as much as i do.\n\nthank you for using cobalt. there's so much more to come :)"
}, {
"version": "4.8",
"title": "prettier than ever",
"banner": "catmakeup.webp",
"content": "this version brings many visual improvements and a completely revamped \"about\" tab.\n\nwhat's new in \"about\" tab:\n*; all information is now split into collapsible sections, making it easier to navigate.\n*; added privacy policy to further prove that none of your data is collected.\n*; added emoji to the page title to make it look consistent with other pages.\n*; added mastodon account handle and link.\n*; there are now short notes at the end of each section.\n*; other changes that are too small to describe. just go check it out!\n\nvisual improvements:\n*; less wasted space: paddings and margins have been reduced and optimized for usability, consistency, and overall beauty.\n*; all <a class=\"text-backdrop italic\" href=\"https://youtu.be/dQw4w9WgXcQ\" target=\"_blank\">links</a> are now in italic. it's much easier to tell them apart from <span class=\"text-backdrop\">regular highlights</span>.\n*; error popup no longer looks broken and out of place.\n*; download popup now has a proper close button, not something from 2.x era.\n*; emoji are no longer selectable or draggable.\n*; better scalability: desktop layout for home screen is shown if device viewport is wide enough to fit in three action buttons.\n*; page shouldn't look broken on phones in landscape mode (i still highly recommend using cobalt in portrait mode).\n*; removed bulletpoint padding. it was unnecessary.\n*; updated some service names.\n\nas always, you can suggest features or report bugs on any platform listed in the \"support\" section of about tab.\n\nthank you for using cobalt. i hope you have a good day :)"
"banner": {
"file": "catmakeup.webp",
"width": 394,
"height": 266
},
"content": "this version brings many visual improvements and a completely revamped \"about\" tab.\n\nwhat's new in \"about\" tab:\n*; all information is now split into collapsible sections, making it easier to navigate.\n*; added privacy policy to further prove that none of your data is collected.\n*; added emoji to the page title to make it look consistent with other pages.\n*; added mastodon account handle and link.\n*; there are now short notes at the end of each section.\n*; other changes that are too small to describe. just go check it out!\n\nvisual improvements:\n*; less wasted space: paddings and margins have been reduced and optimized for usability, consistency, and overall beauty.\n*; all <a class=\"text-backdrop link\" href=\"https://youtu.be/dQw4w9WgXcQ\" target=\"_blank\">links</a> are now in italic. it's much easier to tell them apart from <span class=\"text-backdrop\">regular highlights</span>.\n*; error popup no longer looks broken and out of place.\n*; download popup now has a proper close button, not something from 2.x era.\n*; emoji are no longer selectable or draggable.\n*; better scalability: desktop layout for home screen is shown if device viewport is wide enough to fit in three action buttons.\n*; page shouldn't look broken on phones in landscape mode (i still highly recommend using cobalt in portrait mode).\n*; removed bulletpoint padding. it was unnecessary.\n*; updated some service names.\n\nas always, you can suggest features or report bugs on any platform listed in the \"support\" section of about tab.\n\nthank you for using cobalt. i hope you have a good day :)"
}, {
"version": "4.7",
"title": "we're better together! thank you for bug reports.",
"banner": "bettertogether.webp",
"content": "this update includes a bunch of improvements, many of which were made thanks to the community :D\n\nservice-related improvements:\n*; private soundcloud links are now supported (#68);\n*; tiktok usernames with dots in them no longer confuse cobalt (#71);\n*; .ogg files no longer wrongfully include a video channel (#67);\n*; fixed an issue that caused cobalt to freak out when user attempted to download an audio from audio-only service with \"mute video\" option enabled.\n\nui improvements:\n*; popup padding has been evened out. popups are now able to fit in more information on scroll, especially on mobile;\n*; all buttons are now of even size and are displayed without any padding issues across all modern browsers and devices;\n*; checkbox is no longer crippled on ios;\n*; many explanation texts have been simplified to get rid of unnecessary bloat (no bullshit, remember?);\n*; moved tiktok section in video settings higher due to higher priority;\n*; fixed unexpectedly displayed scrollbars on switch rows in firefox.\n\nstability improvements:\n*; ffmpeg process now should end upon finishing the render;\n*; ffmpeg should also quit when download is abruptly cut off;\n*; fixed a memory leak that was caused by misconfigured stream information caching (#63).\n\ninternal improvements:\n*; requested streams are now stored in cache for 2 minutes instead of 1000 hours (yes, 1000 hours, i fucked up);\n*; cached data is now reused if user requests same content within 2 minutes;\n*; page render module is now even cleaner than before;\n*; proper support for bullet-points in loc strings.\n\nyou can suggest features or report bugs on <a class=\"text-backdrop italic\" href=\"{repo}\" target=\"_blank\">github</a> or <a class=\"text-backdrop italic\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">twitter</a>. both work just fine, use whichever you're more comfortable with.\n\nthank you for using cobalt, and thank you for reading this changelog.\n\nyou're amazing, keep it up :)"
"banner": {
"file": "bettertogether.webp",
"width": 640,
"height": 358
},
"content": "this update includes a bunch of improvements, many of which were made thanks to the community :D\n\nservice-related improvements:\n*; private soundcloud links are now supported (#68);\n*; tiktok usernames with dots in them no longer confuse cobalt (#71);\n*; .ogg files no longer wrongfully include a video channel (#67);\n*; fixed an issue that caused cobalt to freak out when user attempted to download an audio from audio-only service with \"mute video\" option enabled.\n\nui improvements:\n*; popup padding has been evened out. popups are now able to fit in more information on scroll, especially on mobile;\n*; all buttons are now of even size and are displayed without any padding issues across all modern browsers and devices;\n*; checkbox is no longer crippled on ios;\n*; many explanation texts have been simplified to get rid of unnecessary bloat (no bullshit, remember?);\n*; moved tiktok section in video settings higher due to higher priority;\n*; fixed unexpectedly displayed scrollbars on switch rows in firefox.\n\nstability improvements:\n*; ffmpeg process now should end upon finishing the render;\n*; ffmpeg should also quit when download is abruptly cut off;\n*; fixed a memory leak that was caused by misconfigured stream information caching (#63).\n\ninternal improvements:\n*; requested streams are now stored in cache for 2 minutes instead of 1000 hours (yes, 1000 hours, i fucked up);\n*; cached data is now reused if user requests same content within 2 minutes;\n*; page render module is now even cleaner than before;\n*; proper support for bullet-points in loc strings.\n\nyou can suggest features or report bugs on <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">github</a> or <a class=\"text-backdrop link\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">twitter</a>. both work just fine, use whichever you're more comfortable with.\n\nthank you for using cobalt, and thank you for reading this changelog.\n\nyou're amazing, keep it up :)"
}, {
"version": "4.6",
"title": "mute videos and proper soundcloud support",
"banner": "shutup.png",
"banner": {
"file": "shutup.webp",
"width": 1024,
"height": 665
},
"content": "i've been longing to implement both of these things, and here they finally are.\n\nservice-related improvements:\n*; you now can download videos with no audio! simply enable the \"mute audio\" option in settings &gt; audio.\n*; soundcloud module has been updated, and downloads should no longer break after some time.\nvisual improvements:\n*; moved some things around in settings popup, and added separators where separation is needed.\n*; updated some texts in english and russian.\n*; version and commit hash have been joined together, now they're a single unit.\ninternal improvements:\n*; updated api documentation to include isAudioMuted.\n*; simplified the startup message.\n*; created render elements for separator and explanation due to high duplication of them in the page.\n*; fully deprecated GET method for API requests.\n*; fixed some code quirks.\nhere's how soundcloud downloads got fixed:\n\npreviously, client_id was (stupidly) hardcoded. that means cobalt wasn't able to fetch song data if soundcloud web app got updated.\nnow, cobalt tries to find the up-to-date client_id, caches it in memory, and checks if web app version has changed to update the id accordingly. you can see this change for yourself on github."
}, {
"version": "4.5",
"title": "better, faster, stronger, stable",
"banner": "meowthstrong.webp",
"content": "your favorite social media downloader just got even better! this update includes a ton of improvements and fixes.\n\nin fact, there are so many changes, i had to split them in sections.\n\nservice-related improvements:\n*; vimeo module has been revamped, all sorts of videos should now be supported.\n*; vimeo audio downloads! you now can download audios from more recent videos.\n*; cobalt now supports all sorts of tumblr links. (even those scary ones from the mobile app)\n*; vk clips support has been fixed. they rolled back the separation of videos and clips, so i had to do the same.\n*; youtube videos with community warnings should now be possible to download.\nuser interface improvements:\n*; list of supported services is now MUCH easier to read.\n*; banners in changelog history should no longer overlap each other.\n*; bullet points! they have a bit of extra padding, so it makes them stand out of the rest of text.\ninternal improvements:\n*; cobalt will now match the link to regex when using ?u= query for autopasting it into input area.\n*; better rate limiting: limiting now is done per minute, not per 20 minutes. this ensures less waiting and less attack area for request spammers.\n*; moved to my own fork of ytdl-core, cause main project seems to have been abandoned. go check it out on <a class=\"text-backdrop italic\" href=\"https://github.com/wukko/better-ytdl-core\" target=\"_blank\">github</a> or <a class=\"text-backdrop italic\" href=\"https://www.npmjs.com/package/better-ytdl-core\" target=\"_blank\">npm</a>!\n*; ALL user inputs are now properly sanitized on the server. that includes variables for POST api method, too.\n*; \"got\" package has been (mostly) replaced by native fetch api. this should greatly reduce ram usage.\n*; all unnecessary duplications of module imports have been gotten rid of. no more error passing strings from inside of service modules. you don't make mistakes only if you don't do anything, right?\n*; other code optimizations. there's less clutter overall.\nhuge update, right? seems like everything's fixed now?\n\nnope, one issue still persists: sometimes youtube server drops packets for an audio file while cobalt's rendering the video for you. this results in abrupt cuts of audio. if you want to help solving this issue, <a class=\"text-backdrop italic\" href=\"https://github.com/wukko/cobalt/issues/62\" target=\"_blank\">please feel free to do it on github!</a>\n\nthank you for reading this, and thank you for sticking with cobalt and me."
"banner": {
"file": "meowthstrong.webp",
"width": 500,
"height": 280
},
"content": "your favorite social media downloader just got even better! this update includes a ton of improvements and fixes.\n\nin fact, there are so many changes, i had to split them in sections.\n\nservice-related improvements:\n*; vimeo module has been revamped, all sorts of videos should now be supported.\n*; vimeo audio downloads! you now can download audios from more recent videos.\n*; cobalt now supports all sorts of tumblr links. (even those scary ones from the mobile app)\n*; vk clips support has been fixed. they rolled back the separation of videos and clips, so i had to do the same.\n*; youtube videos with community warnings should now be possible to download.\nuser interface improvements:\n*; list of supported services is now MUCH easier to read.\n*; banners in changelog history should no longer overlap each other.\n*; bullet points! they have a bit of extra padding, so it makes them stand out of the rest of text.\ninternal improvements:\n*; cobalt will now match the link to regex when using ?u= query for autopasting it into input area.\n*; better rate limiting: limiting now is done per minute, not per 20 minutes. this ensures less waiting and less attack area for request spammers.\n*; moved to my own fork of ytdl-core, cause main project seems to have been abandoned. go check it out on <a class=\"text-backdrop link\" href=\"https://github.com/wukko/better-ytdl-core\" target=\"_blank\">github</a> or <a class=\"text-backdrop link\" href=\"https://www.npmjs.com/package/better-ytdl-core\" target=\"_blank\">npm</a>!\n*; ALL user inputs are now properly sanitized on the server. that includes variables for POST api method, too.\n*; \"got\" package has been (mostly) replaced by native fetch api. this should greatly reduce ram usage.\n*; all unnecessary duplications of module imports have been gotten rid of. no more error passing strings from inside of service modules. you don't make mistakes only if you don't do anything, right?\n*; other code optimizations. there's less clutter overall.\nhuge update, right? seems like everything's fixed now?\n\nnope, one issue still persists: sometimes youtube server drops packets for an audio file while cobalt's rendering the video for you. this results in abrupt cuts of audio. if you want to help solving this issue, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/issues/62\" target=\"_blank\">please feel free to do it on github!</a>\n\nthank you for reading this, and thank you for sticking with cobalt and me."
}, {
"version": "4.4",
"title": "over 1 million monthly requests. thank you.",
"banner": "onemillionr.webp",
"banner": {
"file": "onemillionr.webp",
"width": 1441,
"height": 1441
},
"content": "this is a huge milestone for me, i cannot express enough how grateful i am for each and every one of you.\nthank you for using cobalt, and thank you for showing that people love the web that's friendly and bullshit-free. i'm hoping to never disappoint you in the future and keep up the good work.\n\nthank you <3\n\nif you want to thank ME, check out the renovated donations tab, which now is also linked alongside bottom action buttons."
}, {
"version": "4.3.2",
@ -57,8 +157,12 @@
}, {
"version": "4.3",
"title": "developers, developers, developers, developers",
"banner": "developersdevelopersdevelopers.webp",
"content": "this update features a TON of improvements.\n\n<a class=\"text-backdrop italic\" href=\"https://www.youtube.com/watch?v=SaVTHG-Ev4k\" target=\"_blank\">developers</a>, you now can rely on cobalt for getting content from social media. the api has been revamped and <a class=\"text-backdrop italic\" href=\"https://github.com/wukko/cobalt/tree/current/docs/API.md\" target=\"_blank\">documentation</a> is now available. you can read more about API changes down below. go crazy, and have fun :D\n\nif you're not a developer, here's a list of changes that you probably care about:\n- rate limit is now approximately 8 times bigger. no more waiting, even if you want to download entirety of your tiktok \"for you\" page.\n- some updates will now have expressive banners, just like this one.\n- fixed what was causing an error when a youtube video had no description.\n- mp4 format button text should now be displayed properly, no matter if you touched the switcher or not.\n\nnext, the star of this update — improved api!\n- main endpoint now uses POST method instead of GET.\n- internal variables for preferences have been updated to be consistent and easier to understand.\n- ip address is now hashed right upon request, not somewhere deep inside the code.\n- global stream salt variable is no longer unnecessarily passed over a billion functions.\n- url and picker keys are now separate in the json response.\n- cobalt web app now correctly processes responses with \"success\" status.\n\nif you currently have a siri shortcut or some other script that uses the GET method, make sure to update it soon. this method is deprecated, limited, and will be removed entirely in coming updates.\n\nif you ever make something using cobalt's api, make sure to mention <a class=\"text-backdrop italic\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a> on twitter, i would absolutely love to see what you made."
"banner": {
"file": "developers.webp",
"width": 640,
"height": 360
},
"content": "this update features a TON of improvements.\n\n<a class=\"text-backdrop link\" href=\"https://www.youtube.com/watch?v=SaVTHG-Ev4k\" target=\"_blank\">developers</a>, you now can rely on cobalt for getting content from social media. the api has been revamped and <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt/tree/current/docs/API.md\" target=\"_blank\">documentation</a> is now available. you can read more about API changes down below. go crazy, and have fun :D\n\nif you're not a developer, here's a list of changes that you probably care about:\n- rate limit is now approximately 8 times bigger. no more waiting, even if you want to download entirety of your tiktok \"for you\" page.\n- some updates will now have expressive banners, just like this one.\n- fixed what was causing an error when a youtube video had no description.\n- mp4 format button text should now be displayed properly, no matter if you touched the switcher or not.\n\nnext, the star of this update — improved api!\n- main endpoint now uses POST method instead of GET.\n- internal variables for preferences have been updated to be consistent and easier to understand.\n- ip address is now hashed right upon request, not somewhere deep inside the code.\n- global stream salt variable is no longer unnecessarily passed over a billion functions.\n- url and picker keys are now separate in the json response.\n- cobalt web app now correctly processes responses with \"success\" status.\n\nif you currently have a siri shortcut or some other script that uses the GET method, make sure to update it soon. this method is deprecated, limited, and will be removed entirely in coming updates.\n\nif you ever make something using cobalt's api, make sure to mention <a class=\"text-backdrop link\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a> on twitter, i would absolutely love to see what you made."
}, {
"version": "4.2",
"title": "optimized quality picking and 8k video support",
@ -70,7 +174,7 @@
}, {
"version": "4.0",
"title": "better and faster than ever",
"content": "this update has a ton of improvements and new features.\n\nchanges you probably care about:\n- cobalt now has support for recorded twitter spaces! download the previous conversation no matter how long it was.\n- download speeds from youtube are at least 10 times better now. you're welcome.\n- both video and audio length limits have been extended to 2 hours.\n- audio downloads from youtube, youtube music, twitter spaces, and soundcloud now have metadata! most often it's just title and artist, but when cobalt is able to get more info, it adds that metadata too.\n- tiktok downloads have been fixed, yet again, and if they ever break in the future, cobalt will fall back to downloading a less annoyingly watermarked video.\n- soundcloud downloads have been fixed, too.\n\nless notable changes:\n- currently experimenting with using mp3 as default audio format. if you set something other than mp3 before, it'll be set to mp3. you can always change it back in settings. let me know what you think about this.\n- \"download audio\" button from image picker no longer stays on the screen after popup was closed.\n- clipboard button now shows up depending on your browser's support for it.\n- you can no longer manually hide the clipboard button, 'cause it's unnecessary.\n- small internal improvements such as separation of changelog version and title.\n- fair bit of internal clean up.\n\nif you want to help me implement covers for downloaded audios, <a class=\"text-backdrop italic\" href=\"https://github.com/wukko/cobalt\" target=\"_blank\">you can do it on github</a>."
"content": "this update has a ton of improvements and new features.\n\nchanges you probably care about:\n- cobalt now has support for recorded twitter spaces! download the previous conversation no matter how long it was.\n- download speeds from youtube are at least 10 times better now. you're welcome.\n- both video and audio length limits have been extended to 2 hours.\n- audio downloads from youtube, youtube music, twitter spaces, and soundcloud now have metadata! most often it's just title and artist, but when cobalt is able to get more info, it adds that metadata too.\n- tiktok downloads have been fixed, yet again, and if they ever break in the future, cobalt will fall back to downloading a less annoyingly watermarked video.\n- soundcloud downloads have been fixed, too.\n\nless notable changes:\n- currently experimenting with using mp3 as default audio format. if you set something other than mp3 before, it'll be set to mp3. you can always change it back in settings. let me know what you think about this.\n- \"download audio\" button from image picker no longer stays on the screen after popup was closed.\n- clipboard button now shows up depending on your browser's support for it.\n- you can no longer manually hide the clipboard button, 'cause it's unnecessary.\n- small internal improvements such as separation of changelog version and title.\n- fair bit of internal clean up.\n\nif you want to help me implement covers for downloaded audios, <a class=\"text-backdrop link\" href=\"https://github.com/wukko/cobalt\" target=\"_blank\">you can do it on github</a>."
}, {
"version": "3.7",
"title": "support for multi media tweets is here!",
@ -86,7 +190,7 @@
}, {
"version": "3.5.4",
"title": "tiktok support is back :D",
"content": "you can download videos, sounds, and images from tiktok again!\nhuge thank you to <a class=\"text-backdrop italic\" href=\"https://github.com/minzique\" target=\"_blank\">@minzique</a> for finding another api endpoint that works."
"content": "you can download videos, sounds, and images from tiktok again!\nhuge thank you to <a class=\"text-backdrop link\" href=\"https://github.com/minzique\" target=\"_blank\">@minzique</a> for finding another api endpoint that works."
}, {
"version": "3.5.2",
"title": "vk clips support, improved changelog system, and less bugs",

View file

@ -6,19 +6,33 @@ let changelog = loadJSON('./src/modules/changelog/changelog.json')
export default function(string) {
try {
switch (string) {
case "version":
return `<span class="text-backdrop changelog-tag-version">v.${changelog["current"]["version"]}</span>${
changelog["current"]["date"] ? `<span class="changelog-tag-date">· ${changelog["current"]["date"]}</span>` : ''
}`
case "title":
return `<span class="text-backdrop">${changelog["current"]["version"]}:</span> ${replaceBase(changelog["current"]["title"])}`;
return replaceBase(changelog["current"]["title"]);
case "banner":
return changelog["current"]["banner"] ? `updateBanners/${changelog["current"]["banner"]}` : false;
return changelog["current"]["banner"] ? {
url: `updateBanners/${changelog["current"]["banner"]["file"]}`,
width: changelog["current"]["banner"]["width"],
height: changelog["current"]["banner"]["height"]
} : false;
case "content":
return replaceBase(changelog["current"]["content"]);
case "history":
return changelog["history"].map((i) => {
return {
title: `<span class="text-backdrop">${i["version"]}:</span> ${replaceBase(i["title"])}`,
title: replaceBase(i["title"]),
version: `<span class="text-backdrop changelog-tag-version">v.${i["version"]}</span>${
i["date"] ? `<span class="changelog-tag-date">· ${i["date"]}</span>` : ''
}`,
content: replaceBase(i["content"]),
version: i["version"],
banner: i["banner"] ? `updateBanners/${i["banner"]}` : false,
banner: i["banner"] ? {
url: `updateBanners/${i["banner"]["file"]}`,
width: i["banner"]["width"],
height: i["banner"]["height"]
} : false,
}
});
default:

View file

@ -6,7 +6,6 @@ const servicesConfigJson = loadJson("./src/modules/processing/servicesConfig.jso
export const
services = servicesConfigJson.config,
audioIgnore = servicesConfigJson.audioIgnore,
appName = packageJson.name,
version = packageJson.version,
streamLifespan = config.streamLifespan,
maxVideoDuration = config.maxVideoDuration,

View file

@ -10,7 +10,6 @@ const names = {
"🀄": "dragon_face_wukko",
"💸": "money_with_wings",
"⚙️": "gear",
"☹️": "frowning_face",
"📋": "clipboard",
"🎃": "pumpkin",
"🎄": "christmas_tree",
@ -23,18 +22,36 @@ const names = {
"🐙": "octopus",
"🔮": "crystal_ball",
"💪": "biceps",
"💖": "sparkling_heart"
"💖": "sparkling_heart",
"👾": "alien_monster",
"😿": "cat_crying",
"🙀": "cat_flabbergasted",
"🐱": "cat_smile",
"❤️‍🩹": "mending_heart",
"🔒": "locked",
"🔍": "magnifying_glass",
"🔗": "link",
"⌨": "keyboard",
"📑": "boring_document",
"🧮": "abacus",
"😸": "cat_grin"
}
let sizing = {
18: 0.8,
22: 0.4,
30: 0.7,
48: 0.9,
64: 0.9
64: 0.9,
78: 0.9
}
export default function(emoji, size, disablePadding) {
export default function(emoji, size, disablePadding, fluent) {
if (!size) size = 22;
let padding = size !== 22 ? `margin-right:${sizing[size] ? sizing[size] : "0.4"}rem;` : false;
if (disablePadding) padding = 'margin-right:0!important;';
if (!names[emoji]) emoji = "❓";
return `<img class="emoji" draggable=false height="${size}" width="${size}" ${padding ? `style="${padding}"` : ''}alt="${emoji}" src="emoji/${names[emoji]}.svg">`
let filePath = `emoji/${names[emoji]}.svg`;
if (fluent) filePath = `emoji/3d/${names[emoji]}.svg`;
return `<img class="emoji" draggable=false height="${size}" width="${size}" ${padding ? `style="${padding}"` : ''}alt="${emoji}" src="${filePath}" loading="lazy">`
}

View file

@ -1,13 +1,23 @@
import { 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">
<path d="M14.1905 28.5L2 16L14.1905 3.5L16.2857 5.62054L7.65986 14.4654H30V17.5346H7.65986L16.2857 26.3516L14.1905 28.5Z" fill="#E1E1E1"/>
</svg>`
export const dropdownSVG = `<svg width="18" height="18" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28 12.0533L16 24L4 12.0533L6.03571 10L14.7188 18.4104L16.25 19.9348L17.7813 18.4104L25.9375 10L28 12.0533Z" fill="#E1E1E1"/>
</svg>`
export function switcher(obj) {
let items = ``;
if (obj.name === "download") {
items = obj.items;
} else {
for (let i = 0; i < obj.items.length; i++) {
let classes = obj.items[i]["classes"] ? obj.items[i]["classes"] : []
let classes = obj.items[i]["classes"] ? obj.items[i]["classes"] : [];
if (i === 0) classes.push("first");
if (i === (obj.items.length - 1)) classes.push("last");
items += `<button id="${obj.name}-${obj.items[i]["action"]}" class="switch${classes.length > 0 ? ' ' + classes.join(' ') : ''}" onclick="changeSwitcher('${obj.name}', '${obj.items[i]["action"]}')">${obj.items[i]["text"] ? obj.items[i]["text"] : obj.items[i]["action"]}</button>`
}
}
@ -19,26 +29,18 @@ export function switcher(obj) {
${obj.explanation ? `<div class="explanation">${obj.explanation}</div>` : ``}
</div>`
}
export function checkbox(obj) {
let paddings = ["bottom-margin", "top-margin", "no-margin", "top-margin-only"];
let checkboxes = ``;
for (let i = 0; i < obj.length; i++) {
let paddingClass = obj[i].padding && paddings.includes(obj[i].padding) ? ` ${obj[i].padding}` : '';
export function checkbox(action, text, paddingType, aria) {
let paddingClass = ` `
switch (paddingType) {
case 1:
paddingClass += "bottom-margin"
break;
case 2:
paddingClass += "top-margin"
break;
case 3:
paddingClass += "no-margin"
break;
case 4:
paddingClass += "top-margin-only"
checkboxes += `<label id="${obj[i].action}-chkbx" class="checkbox${paddingClass}">
<input id="${obj[i].action}" type="checkbox" aria-label="${obj[i].aria ? obj[i].aria : obj[i].name}" onclick="checkbox('${obj[i].action}')">
<span>${obj[i].name}</span>
</label>`
}
return `<label id="${action}-chkbx" class="checkbox${paddingClass}">
<input id="${action}" type="checkbox" ${aria ? `aria-label="${aria}"` : `aria-label="${text}"`} onclick="checkbox('${action}')">
<span>${text}</span>
</label>`
return checkboxes
}
export function sep(paddingType) {
let paddingClass = ``
@ -50,7 +52,7 @@ export function sep(paddingType) {
return `<div class="separator${paddingClass}"></div>`
}
export function popup(obj) {
let classes = obj.classes ? obj.classes : []
let classes = obj.classes ? obj.classes : [];
let body = obj.body;
if (Array.isArray(obj.body)) {
body = ``
@ -65,47 +67,63 @@ export function popup(obj) {
}
}
return `
${obj.standalone ? `<div id="popup-${obj.name}" class="popup center box${classes.length > 0 ? ' ' + classes.join(' ') : ''}" style="visibility: hidden;">` : ''}
${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.standalone && !obj.buttonOnly ? `<button id="close-button" class="switch up" onclick="popup('${obj.name}', 0)" ${obj.header.closeAria ? `aria-label="${obj.header.closeAria}"` : ''}>x</button>` : ''}
${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 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"${obj.footer ? ' class="with-footer"' : ''}>
<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>
${obj.footer ? `<div id="popup-footer" class="popup-footer">
<a id="popup-bottom" class="popup-footer-content" target="_blank" href="${obj.footer.url}">${obj.footer.text}</a>
</div>` : ''}
${classes.includes("small") ? `<div class="glass-bkg small"></div>`: ''}
${obj.standalone ? `</div>` : ''}`
}
export function multiPagePopup(obj) {
let tabs = ``
let tabContent = ``
let tabs = `
<button id="back-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>
${backButtonSVG}
</button>`;
let tabContent = ``;
for (let i = 0; i < obj.tabs.length; i++) {
tabs += `<button id="tab-button-${obj.name}-${obj.tabs[i]["name"]}" class="switch tab tab-${obj.name}" onclick="changeTab(event, 'tab-${obj.name}-${obj.tabs[i]["name"]}', '${obj.name}')">${obj.tabs[i]["title"]}</button>`
tabContent += `<div id="tab-${obj.name}-${obj.tabs[i]["name"]}" class="popup-tab-content tab-content-${obj.name}">${obj.tabs[i]["content"]}</div>`
}
tabs += `<button id="close-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>x</button>`
return `
<div id="popup-${obj.name}" class="popup center box scrollable" style="visibility: hidden;">
<div id="popup-content">${obj.header ? `<div id="popup-header" class="popup-header">
${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>` : ''}${tabContent}</div>
<div id="popup-tabs" class="switches popup-tabs">${tabs}</div>
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${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">
<div class="switches popup-tabs-child">${tabs}</div>
<div class="glass-bkg alone"></div>
</div>
</div>`
}
export function collapsibleList(arr) {
let items = ``
let items = ``;
for (let i = 0; i < arr.length; i++) {
items += `<div id="${arr[i]["name"]}-collapse" class="collapse-list">
let classes = arr[i]["classes"] ? arr[i]["classes"] : [];
if (i === 0) classes.push("first");
if (i === (arr.length - 1)) classes.push("last");
items += `<div id="${arr[i]["name"]}-collapse" class="collapse-list${classes.length > 0 ? ' ' + classes.join(' ') : ''}">
<div class="collapse-header" onclick="expandCollapsible(event)">
<div class="collapse-title">${arr[i]["title"]}</div>
<div class="collapse-indicator">^</div>
<div class="collapse-indicator">${dropdownSVG}</div>
</div>
<div id="${arr[i]["name"]}-body" class="collapse-body">${arr[i]["body"]}</div>
</div>`
@ -113,26 +131,34 @@ export function collapsibleList(arr) {
return items;
}
export function popupWithBottomButtons(obj) {
let tabs = ``
let tabs = `
<button id="back-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>
${backButtonSVG}
</button>`
for (let i = 0; i < obj.buttons.length; i++) {
tabs += obj.buttons[i]
}
tabs += `<button id="close-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>x</button>`
return `
<div id="popup-${obj.name}" class="popup center box scrollable" style="visibility: hidden;">
<div id="popup-content">${obj.header ? `<div id="popup-header" class="popup-header">
${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>` : ''}${obj.content}</div>
<div id="popup-buttons" class="switches popup-tabs">${tabs}</div>
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${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">
<div id="picker-buttons" class="switches popup-tabs-child">${tabs}</div>
<div class="glass-bkg alone"></div>
</div>
</div>`
}
export function backdropLink(link, text) {
return `<a class="text-backdrop italic" href="${link}" target="_blank">${text}</a>`
}
export function socialLink(emji, name, handle, url) {
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop italic" href="${url}" target="_blank">${handle}</a></div>`
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop link" href="${url}" target="_blank">${handle}</a></div>`
}
export function settingsCategory(obj) {
return `<div id="settings-${obj.name}" class="settings-category">
@ -187,3 +213,35 @@ export function celebrationsEmoji() {
return false
}
}
export function urgentNotice(obj) {
if (obj.visible) {
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 ``
}
export function keyboardShortcuts(arr) {
let base = `<div id="keyboard-shortcuts" class="explanation">`;
for (let i = 0; i < arr.length; i++) {
base += `<div class="shortcut-category">`;
for (let c = 0; c < arr[i].items.length; c++) {
let combo = arr[i].items[c].combo.split('+').map(
key => `<span class="text-backdrop key">${key}</span>`
).join("+")
base += `<div class="shortcut">${combo}: ${arr[i].items[c].name}</div>`
}
base += `</div>`
}
base += `</div>`;
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,5 @@
import changelogManager from "../changelog/changelogManager.js"
import { cleanHTML } from "../sub/utils.js";
let cache = {}
@ -10,8 +11,22 @@ export function changelogHistory() { // blockId 0
let historyLen = history.length;
for (let i in history) {
let separator = (i !== 0 && i !== historyLen) ? '<div class="separator"></div>' : '';
render += `${separator}${history[i]["banner"] ? `<div class="changelog-banner"><img class="changelog-img" src="${history[i]["banner"]}" onerror="this.style.display='none'"></img></div>` : ''}<div id="popup-desc" class="changelog-subtitle">${history[i]["title"]}</div><div id="popup-desc" class="desc-padding">${history[i]["content"]}</div>`
render += `
${separator}${history[i]["banner"] ?
`<div class="changelog-banner">
<img class="changelog-img" ` +
`src="${history[i]["banner"]["url"]}" ` +
`width="${history[i]["banner"]["width"]}" ` +
`height="${history[i]["banner"]["height"]}" ` +
`onerror="this.style.opacity=0" loading="lazy">`+
`</img>
</div>` : ''}
<div id="popup-desc" class="changelog-tags">${history[i]["version"]}</div>
<div id="popup-desc" class="changelog-subtitle">${history[i]["title"]}</div>
<div id="popup-desc" class="desc-padding">${history[i]["content"]}</div>`
}
render = cleanHTML(render);
cache['0'] = render;
return render;
}

View file

@ -1,5 +1,5 @@
import { backdropLink, checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink } from "./elements.js";
import { services as s, appName, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, 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";
import emoji from "../emoji.js";
@ -7,9 +7,7 @@ import changelogManager from "../changelog/changelogManager.js";
let com = getCommitInfo();
let enabledServices = Object.keys(s).filter((p) => {
if (s[p].enabled) return true;
}).sort().map((p) => {
let enabledServices = Object.keys(s).filter(p => s[p].enabled).sort().map((p) => {
return `<br>&bull; ${s[p].alias ? s[p].alias : p}`
}).join('').substring(4)
@ -30,48 +28,58 @@ for (let i in donations["crypto"]) {
export default function(obj) {
const t = (str, replace) => { return loc(obj.lang, str, replace) };
let ua = obj.useragent.toLowerCase();
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');
try {
return `<!DOCTYPE html>
<html lang="en">
return `
<!DOCTYPE html>
<html lang="${obj.lang}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
<title>${appName}</title>
<title>${t("AppTitleCobalt")}</title>
<meta property="og:url" content="${process.env.selfURL}" />
<meta property="og:title" content="${appName}" />
<meta property="og:url" content="${process.env.webURL || process.env.selfURL}" />
<meta property="og:title" content="${t("AppTitleCobalt")}" />
<meta property="og:description" content="${t('EmbedBriefDescription')}" />
<meta property="og:image" content="${process.env.selfURL}icons/generic.png" />
<meta name="title" content="${appName}" />
<meta property="og:image" content="${process.env.webURL || process.env.selfURL}icons/generic.png" />
<meta name="title" content="${t("AppTitleCobalt")}" />
<meta name="description" content="${t('AboutSummary')}" />
<meta name="theme-color" content="#000000" />
<meta name="twitter:card" content="summary" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="${t("AppTitleCobalt")}">
<link rel="icon" type="image/x-icon" href="icons/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png" />
<link rel="manifest" href="manifest.webmanifest" />
<link rel="stylesheet" href="fonts/notosansmono.css" rel="preload" />
<link rel="stylesheet" href="cobalt.css" />
<link rel="stylesheet" href="fonts/notosansmono.css" />
<link rel="me" href="${authorInfo.support.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",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
tabs: [{
name: "about",
title: `${emoji("🐲")} ${t('AboutTab')}`,
@ -82,31 +90,84 @@ export default function(obj) {
text: t('MadeWithLove'),
url: authorInfo.link
},
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: `${emoji("🔮", 30)} ${t('TitlePopupAbout')}`
},
body: [{
text: t('AboutSummary')
}, {
text: collapsibleList([{
"name": "services",
"title": t("CollapseServices"),
"body": `${enabledServices}<br/><br/>${t("ServicesNote")}`
name: "services",
title: `${emoji("🔗")} ${t("CollapseServices")}`,
body: `${enabledServices}<br/><br/>${t("ServicesNote")}`
}, {
"name": "support",
"title": t("CollapseSupport"),
"body": `${t("FollowSupport")}<br/>
${socialLink(emoji("🐘"), "mastodon", authorInfo.support.mastodon.handle, authorInfo.support.mastodon.url)}
${socialLink(emoji("🐦"), "twitter", authorInfo.support.twitter.handle, authorInfo.support.twitter.url)}<br/>
name: "keyboard",
title: `${emoji("⌨")} ${t("CollapseKeyboard")}`,
body:
`${t("KeyboardShortcutsIntro")}
${keyboardShortcuts([{
items: [{
combo: "Shift+D",
name: t("PasteFromClipboard")
}, {
combo: "Shift+K",
name: t("ModeToggleAuto")
}, {
combo: "Shift+L",
name: t("ModeToggleAudio")
}]
}, {
items: [{
combo: "Ctrl+V",
name: t("KeyboardShortcutQuickPaste")
}, {
combo: "Esc",
name: t("KeyboardShortcutClear")
}, {
combo: "Esc",
name: t("KeyboardShortcutClosePopup")
}]
}, {
items: [{
combo: "Shift+B",
name: t("AboutTab")
}, {
combo: "Shift+N",
name: t("ChangelogTab")
}, {
combo: "Shift+M",
name: t("TitlePopupSettings")
}]
}])}`
}, {
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(emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo)}<br/>
${socialLink(
emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo
)}<br/>
${t("SupportNote")}`
}, {
"name": "privacy",
"title": t("CollapsePrivacy"),
"body": t("PrivacyPolicy")
name: "privacy",
title: `${emoji("🔒")} ${t("CollapsePrivacy")}`,
body: t("PrivacyPolicy")
}, {
name: "legal",
title: `${emoji("📑")} ${t("CollapseLegal")}`,
body: t("FairUse")
}])
+ `${process.env.DEPLOYMENT_ID && process.env.INTERNAL_IP ? '<a id="hop-attribution" class="explanation" href="https://hop.io/" target="_blank">powered by hop.io</a>' : ''}`
}]
})
}, {
@ -115,15 +176,27 @@ export default function(obj) {
content: popup({
name: "changelog",
header: {
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: `${emoji("🪄", 30)} ${t('TitlePopupChangelog')}`
},
body: [{
text: `<div class="category-title">${t('ChangelogLastMajor')}</div>`,
raw: true
}, {
text: changelogManager("banner") ? `<div class="changelog-banner"><img class="changelog-img" src="${changelogManager("banner")}" onerror="this.style.display='none'"></img></div>`: '',
text: changelogManager("banner") ?
`<div class="changelog-banner">
<img class="changelog-img" ` +
`src="${changelogManager("banner")["url"]}" ` +
`width="${changelogManager("banner")["width"]}" ` +
`height="${changelogManager("banner")["height"]}" ` +
`onerror="this.style.opacity=0" loading="lazy">`+
`</img>
</div>`: '',
raw: true
}, {
text: changelogManager("version"),
classes: ["changelog-tags"],
nopadding: true
}, {
text: changelogManager("title"),
classes: ["changelog-subtitle"],
@ -131,19 +204,26 @@ export default function(obj) {
}, {
text: changelogManager("content")
}, {
text: `${sep()}<span class="text-backdrop">${obj.hash}:</span> ${com[0]}`,
text: sep(),
raw: true
},{
text: `<a class="text-backdrop changelog-tag-version" href="${repo}/commit/${obj.hash}">#${obj.hash}</a>`,
classes: ["changelog-tags"],
nopadding: true
}, {
text: com[0],
classes: ["changelog-subtitle"],
nopadding: true
}, {
text: com[1]
}, {
text: backdropLink(`${repo}/commits`, t('LinkGitHubChanges')),
classes: ["bottom-link"]
}, {
text: `<div class="category-title">${t('ChangelogOlder')}</div>`,
raw: true
}, {
text: `<div id="changelog-history"><button class="switch bottom-margin" onclick="loadOnDemand('changelog-history', '0')">${t("ChangelogPressToExpand")}</button></div>`,
text: `
<div id="changelog-history">
<button class="switch bottom-margin" onclick="loadOnDemand('changelog-history', '0')">${t("ChangelogPressToExpand")}</button>
</div>`,
raw: true
}]
})
@ -153,14 +233,21 @@ export default function(obj) {
content: popup({
name: "donate",
header: {
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: emoji("💸", 30) + t('TitlePopupDonate')
},
body: [{
text: `<div class="category-title">${t('DonateSub')}</div>`,
raw: true
}, {
text: `<div class="changelog-banner"><img class="changelog-img" src="updateBanners/catsleep.webp" onerror="this.style.display='none'"></img></div>`,
text: `<div class="changelog-banner">
<img class="changelog-img" ` +
`src="updateBanners/catsleep.webp"` +
`width="480" ` +
`height="270" ` +
`onerror="this.style.opacity=0" loading="lazy">`+
`</img>
</div>`,
raw: true
}, {
text: t('DonateExplanation')
@ -188,7 +275,7 @@ export default function(obj) {
})}
${multiPagePopup({
name: "settings",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
header: {
aboveTitle: {
text: `v.${version}-${obj.hash}${platform} (${obj.branch})`,
@ -206,33 +293,37 @@ export default function(obj) {
name: "vQuality",
explanation: t('SettingsQualityDescription'),
items: [{
"action": "max",
"text": "4320p+"
action: "max",
text: "8k+"
}, {
"action": "2160",
"text": "2160p"
action: "2160",
text: "4k"
}, {
"action": "1440",
"text": "1440p"
action: "1440",
text: "1440p"
}, {
"action": "1080",
"text": "1080p"
action: "1080",
text: "1080p"
}, {
"action": "720",
"text": "720p"
action: "720",
text: "720p"
}, {
"action": "480",
"text": "480p"
action: "480",
text: "480p"
}, {
"action": "360",
"text": "360p"
action: "360",
text: "360p"
}]
})
})
+ settingsCategory({
name: "tiktok",
title: "tiktok & douyin",
body: checkbox("disableTikTokWatermark", t('SettingsRemoveWatermark'), 3)
title: "tiktok",
body: checkbox([{
action: "disableTikTokWatermark",
name: t("SettingsRemoveWatermark"),
padding: "no-margin"
}])
})
+ settingsCategory({
name: t('SettingsCodecSubtitle'),
@ -240,14 +331,14 @@ export default function(obj) {
name: "vCodec",
explanation: t('SettingsCodecDescription'),
items: [{
"action": "h264",
"text": "h264 (mp4)"
action: "h264",
text: "h264 (mp4)"
}, {
"action": "av1",
"text": "av1 (mp4)"
action: "av1",
text: "av1 (mp4)"
}, {
"action": "vp9",
"text": "vp9 (webm)"
action: "vp9",
text: "vp9 (webm)"
}]
})
})
@ -257,11 +348,11 @@ export default function(obj) {
name: "vimeoDash",
explanation: t('SettingsVimeoPreferDescription'),
items: [{
"action": "false",
"text": "progressive"
action: "false",
text: "progressive"
}, {
"action": "true",
"text": "dash"
action: "true",
text: "dash"
}]
})
})
@ -271,31 +362,44 @@ export default function(obj) {
content: settingsCategory({
name: "general",
title: t('SettingsFormatSubtitle'),
body:
switcher({
name: "aFormat",
explanation: t('SettingsAudioFormatDescription'),
items: audioFormats
}) + sep(0) + checkbox("muteAudio", t('SettingsVideoMute'), 3) + explanation(t('SettingsVideoMuteExplanation'))
}) + settingsCategory({
name: "dub",
title: t("SettingsAudioDub"),
body: switcher({
name: "dubLang",
explanation: t('SettingsAudioDubDescription'),
items: [{
"action": "original",
"text": t('SettingsDubDefault')
}, {
"action": "auto",
"text": t('SettingsDubAuto')
}]
})
}) + settingsCategory({
name: "tiktok",
title: "tiktok & douyin",
body: checkbox("fullTikTokAudio", t('SettingsAudioFullTikTok'), 3) + explanation(t('SettingsAudioFullTikTokDescription'))
})
body: switcher({
name: "aFormat",
explanation: t('SettingsAudioFormatDescription'),
items: audioFormats
})
+ sep(0)
+ checkbox([{
action: "muteAudio",
name: t("SettingsVideoMute"),
padding: "no-margin"
}])
+ explanation(t('SettingsVideoMuteExplanation'))
})
+ settingsCategory({
name: "dub",
title: t("SettingsAudioDub"),
body: switcher({
name: "dubLang",
explanation: t('SettingsAudioDubDescription'),
items: [{
action: "original",
text: t('SettingsDubDefault')
}, {
action: "auto",
text: t('SettingsDubAuto')
}]
})
})
+ settingsCategory({
name: "tiktok",
title: "tiktok",
body: checkbox([{
action: "fullTikTokAudio",
name: t("SettingsAudioFullTikTok"),
padding: "no-margin"
}])
+ explanation(t('SettingsAudioFullTikTokDescription'))
})
}, {
name: "other",
title: `${emoji("🪅")} ${t('SettingsOtherTab')}`,
@ -304,122 +408,189 @@ export default function(obj) {
title: t('SettingsAppearanceSubtitle'),
body: switcher({
name: "theme",
subtitle: t('SettingsThemeSubtitle'),
items: [{
"action": "auto",
"text": t('SettingsThemeAuto')
action: "auto",
text: t('SettingsThemeAuto')
}, {
"action": "dark",
"text": t('SettingsThemeDark')
action: "dark",
text: t('SettingsThemeDark')
}, {
"action": "light",
"text": t('SettingsThemeLight')
action: "light",
text: t('SettingsThemeLight')
}]
}) + checkbox("alwaysVisibleButton", t('SettingsKeepDownloadButton'), 4, t('AccessibilityKeepDownloadButton'))
}) + settingsCategory({
})
})
+ settingsCategory({
name: "accessibility",
title: t('Accessibility'),
body: checkbox([{
action: "alwaysVisibleButton",
name: t("SettingsKeepDownloadButton"),
aria: t("AccessibilityKeepDownloadButton")
}, {
action: "reduceTransparency",
name: t("SettingsReduceTransparency")
}, {
action: "disableAnimations",
name: t("SettingsDisableAnimations"),
padding: "no-margin"
}])
})
+ settingsCategory({
name: "miscellaneous",
title: t('Miscellaneous'),
body: checkbox("disableChangelog", t('SettingsDisableNotifications')) + `${!isIOS ? checkbox("downloadPopup", t('SettingsEnableDownloadPopup'), 1, t('AccessibilityEnableDownloadPopup')) : ''}`
body: checkbox([{
action: "downloadPopup",
name: t("SettingsEnableDownloadPopup"),
aria: t("AccessibilityEnableDownloadPopup")
}, {
action: "disableMetadata",
name: t("SettingsDisableMetadata")
}, {
action: "disableChangelog",
name: t("SettingsDisableNotifications"),
padding: "no-margin"
}])
})
}],
})}
${popup({
name: "download",
standalone: true,
header: {
closeAria: t('AccessibilityClosePopup'),
subtitle: t('TitlePopupDownload')
},
body: switcher({
name: "download",
subtitle: t('DownloadPopupWayToSave'),
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
items: `<a id="pd-download" class="switch full" target="_blank" href="/">${t('Download')}</a>
<div id="pd-share" class="switch full">${t('ShareURL')}</div>
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
})
}]
})}
${popupWithBottomButtons({
name: "picker",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
header: {
title: `<div id="picker-title"></div>`,
title: `${emoji("🧮", 30)} <div id="picker-title"></div>`,
explanation: `<div id="picker-subtitle"></div>`,
},
buttons: [`<a id="picker-download" class="switch" target="_blank" href="/">${t('ImagePickerDownloadAudio')}</a>`],
content: '<div id="picker-holder"></div>'
})}
${popup({
name: "error",
standalone: true,
buttonOnly: true,
classes: ["small"],
buttonText: t('ErrorPopupCloseButton'),
header: {
closeAria: t('AccessibilityClosePopup'),
title: t('TitlePopupError'),
emoji: emoji("☹️", 64, 1),
},
body: `<div id="desc-error" class="desc-padding subtext"></div>`
})}
<div id="popup-backdrop" style="visibility: hidden;" onclick="hideAllPopups()"></div>
<div id="cobalt-main-box" class="center" style="visibility: hidden;">
<div id="logo">${appName}</div>
<div id="download-area">
<div id="top">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<button id="url-clear" onclick="clearInput()" style="display:none;">x</button>
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${t('AccessibilityDownloadButton')}">
</div>
<div id="bottom">
<button id="paste" class="switch" onclick="pasteClipboard()" aria-label="${t('PasteFromClipboard')}">${emoji("📋", 22)} ${t('PasteFromClipboard')}</button>
${switcher({
name: "audioMode",
noParent: true,
items: [{
"action": "false",
"text": `${emoji("✨")} ${t("ModeToggleAuto")}`
}, {
"action": "true",
"text": `${emoji("🎶")} ${t("ModeToggleAudio")}`
}]
})}
<div id="popup-download-container" class="popup-from-bottom">
${popup({
name: "download",
standalone: true,
buttonOnly: true,
classes: ["small"],
header: {
closeAria: t('AccessibilityGoBack'),
emoji: emoji("🐱", 78, 1, 1),
title: t('TitlePopupDownload')
},
body: switcher({
name: "download",
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
items: `<a id="pd-download" class="switch full" target="_blank" href="/"><span>${t('Download')}</span></a>
<div id="pd-share" class="switch full">${t('ShareURL')}</div>
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
}),
buttonText: t('PopupCloseDone')
})}
</div>
<div id="popup-error-container" class="popup-from-bottom">
${popup({
name: "error",
standalone: true,
buttonOnly: true,
classes: ["small"],
header: {
title: t('TitlePopupError'),
emoji: emoji("😿", 78, 1, 1),
},
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: "✨",
text: t("UrgentNewDomain"),
visible: true,
action: "popup('about', 1, 'changelog')"
})}
<div id="cobalt-main-box" class="center">
<div id="logo">${t("AppTitleCobalt")}</div>
<div id="download-area">
<div id="top">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<button id="url-clear" onclick="clearInput()" style="display:none;">x</button>
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${t('AccessibilityDownloadButton')}">
</div>
<div id="bottom">
<button id="paste" class="switch" onclick="pasteClipboard()" aria-label="${t('PasteFromClipboard')}">${emoji("📋", 22)} ${t('PasteFromClipboard')}</button>
${switcher({
name: "audioMode",
noParent: true,
items: [{
action: "false",
text: `${emoji("✨")} ${t("ModeToggleAuto")}`
}, {
action: "true",
text: `${emoji("🎶")} ${t("ModeToggleAudio")}`
}]
})}
</div>
</div>
</div>
<footer id="footer">
${footerButtons([{
name: "about",
type: "popup",
text: `${emoji("🐲" , 22)} ${t('AboutTab')}`,
aria: t('AccessibilityOpenAbout')
}, {
name: "about",
type: "popup",
context: "donate",
text: `${emoji("💖", 22)} ${t('Donate')}`,
aria: t('AccessibilityOpenDonate')
}, {
name: "settings",
type: "popup",
text: `${emoji("⚙️", 22)} ${t('TitlePopupSettings')}`,
aria: t('AccessibilityOpenSettings')
}])}
</footer>
</div>
<footer id="footer" style="visibility: hidden;">
${/* big action buttons are ALWAYS either first or last, because usual buttons are bundled in pairs and are sandwiched between bigger buttons for mobile view */
footerButtons([{
name: "about",
type: "popup",
text: `${emoji("🐲" , 22)} ${t('AboutTab')}`,
aria: t('AccessibilityOpenAbout')
}, {
name: "about",
type: "popup",
context: "donate",
text: `${emoji("💖", 22)} ${t('Donate')}`,
aria: t('AccessibilityOpenDonate')
}, {
name: "settings",
type: "popup",
text: `${emoji("⚙️", 22)} ${t('TitlePopupSettings')}`,
aria: t('AccessibilityOpenSettings')
}])}
</footer>
</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${isIOS ? "IOS" : ""}` : "PC"}`) + "`" + `,
};</script>
<script type="text/javascript">
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>`;
</html>
`
} catch (err) {
return `${t('ErrorPageRenderFail', obj.hash)}`;
}

View file

@ -0,0 +1,37 @@
import { strict as assert } from 'node:assert';
export default class Cookie {
constructor(input) {
assert(typeof input === 'object');
this._values = {};
this.set(input)
}
set(values) {
Object.entries(values).forEach(
([ key, value ]) => this._values[key] = value
)
}
unset(keys) {
for (const key of keys) delete this._values[key]
}
static fromString(str) {
const obj = {};
str.split('; ').forEach(cookie => {
const key = cookie.split('=')[0];
const value = cookie.split('=').splice(1).join('=');
obj[key] = value
})
return new Cookie(obj)
}
toString() {
return Object.entries(this._values).map(([ name, value ]) => `${name}=${value}`).join('; ')
}
toJSON() {
return this.toString()
}
values() {
return Object.freeze({ ...this._values })
}
}

View file

@ -0,0 +1,5 @@
{
"instagram": [
"mid=replace; ig_did=this; csrftoken=cookie"
]
}

View file

@ -0,0 +1,62 @@
import Cookie from './cookie.js';
import { readFile, writeFile } from 'fs/promises';
import { parse as parseSetCookie, splitCookiesString } from 'set-cookie-parser';
const WRITE_INTERVAL = 60000,
cookiePath = process.env.cookiePath,
COUNTER = Symbol('counter');
let cookies = {}, dirty = false, intervalId;
const setup = async () => {
try {
if (!cookiePath) return;
cookies = await readFile(cookiePath, 'utf8');
cookies = JSON.parse(cookies);
intervalId = setInterval(writeChanges, WRITE_INTERVAL)
} catch { /* no cookies for you */ }
}
setup();
function writeChanges() {
if (!dirty) return;
dirty = false;
writeFile(cookiePath, JSON.stringify(cookies, null, 4)).catch(() => {
clearInterval(intervalId)
})
}
export function getCookie(service) {
if (!cookies[service] || !cookies[service].length) return;
let n;
if (cookies[service][COUNTER] === undefined) {
n = cookies[service][COUNTER] = 0
} else {
++cookies[service][COUNTER]
n = (cookies[service][COUNTER] %= cookies[service].length)
}
const cookie = cookies[service][n];
if (typeof cookie === 'string') cookies[service][n] = Cookie.fromString(cookie);
return cookies[service][n]
}
export function updateCookie(cookie, headers) {
if (!cookie) return;
const parsed = parseSetCookie(
splitCookiesString(headers.get('set-cookie')),
{ decodeValues: false }
), values = {}
cookie.unset(parsed.filter(c => c.expires < new Date()).map(c => c.name));
parsed.filter(c => !c.expires || c.expires > new Date()).forEach(c => values[c.name] = c.value);
cookie.set(values);
if (Object.keys(values).length) dirty = true
}

View file

@ -0,0 +1,40 @@
export default function (inHost, inURL) {
let host = String(inHost);
let url = String(inURL);
switch(host) {
case "youtube":
if (url.startsWith("https://youtube.com/live/") || url.startsWith("https://www.youtube.com/live/")) {
url = url.split("?")[0].replace("www.", "");
url = `https://youtube.com/watch?v=${url.replace("https://youtube.com/live/", "")}`
}
break;
case "youtu":
if (url.startsWith("https://youtu.be/")) {
host = "youtube";
url = `https://youtube.com/watch?v=${url.replace("https://youtu.be/", "")}`
}
break;
case "vxtwitter":
case "x":
if (url.startsWith("https://x.com/")) {
host = "twitter";
url = url.replace("https://x.com/", "https://twitter.com/")
}
if (url.startsWith("https://vxtwitter.com/")) {
host = "twitter";
url = url.replace("https://vxtwitter.com/", "https://twitter.com/")
}
break;
case "tumblr":
if (!url.includes("blog/view")) {
if (url.slice(-1) === '/') url = url.slice(0, -1);
url = url.replace(url.split('/')[5], '')
}
break;
}
return {
host: host,
url: url
}
}

View file

@ -17,11 +17,13 @@ import vimeo from "./services/vimeo.js";
import soundcloud from "./services/soundcloud.js";
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";
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) });
@ -111,6 +113,16 @@ export default async function (host, patternMatch, url, lang, obj) {
case "vine":
r = await vine({ id: patternMatch["id"] });
break;
case "pinterest":
r = await pinterest({ id: patternMatch["id"] });
break;
case "streamable":
r = await streamable({
id: patternMatch["id"],
quality: obj.vQuality,
isAudioOnly: isAudioOnly,
});
break;
case "twitch":
r = await twitch({
vodId: patternMatch["video"] ? patternMatch["video"] : false,
@ -119,7 +131,6 @@ export default async function (host, patternMatch, url, lang, obj) {
isAudioOnly: obj.isAudioOnly,
format: obj.vFormat
});
break;
default:
return apiJSON(0, { t: errorUnsupported(lang) });
}
@ -129,7 +140,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.ip, 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,22 +2,23 @@ import { audioIgnore, services, supportedAudio } from "../config.js";
import { apiJSON } from "../sub/utils.js";
import loc from "../../localization/manager.js";
export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMuted) {
export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted, disableMetadata) {
let action,
responseType = 2,
defaultParams = {
u: r.urls,
service: host,
ip: ip,
filename: r.filename,
fileMetadata: !disableMetadata ? r.fileMetadata : false
},
params = {}
if (!isAudioOnly && !r.picker && !isAudioMuted) action = "video";
if (r.isM3U8) action = "singleM3U8";
if (isAudioOnly && !r.picker) action = "audio";
if (r.picker) action = "picker";
if (isAudioMuted) action = "muteVideo";
if (r.isPhoto) action = "photo";
else if (r.picker) action = "picker"
else if (isAudioMuted) action = "muteVideo";
else if (isAudioOnly) action = "audio";
else if (r.isM3U8) action = "singleM3U8";
else action = "video";
if (action === "picker" || action === "audio") {
defaultParams.filename = r.audioFilename;
@ -26,13 +27,16 @@ export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMute
}
switch (action) {
case "photo":
responseType = 1;
break;
case "video":
switch (host) {
case "bilibili":
params = { type: "render", time: r.time };
params = { type: "render" };
break;
case "youtube":
params = { type: r.type, time: r.time };
params = { type: r.type };
break;
case "reddit":
responseType = r.typeId;
@ -56,6 +60,8 @@ export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMute
case "instagram":
case "tumblr":
case "twitter":
case "pinterest":
case "streamable":
responseType = 1;
break;
}
@ -69,6 +75,7 @@ export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMute
u: Array.isArray(r.urls) ? r.urls[0] : r.urls,
mute: true
}
if (host === "reddit" && r.typeId === 1) responseType = 1;
break;
case "picker":
@ -113,9 +120,11 @@ export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMute
processType = "bridge"
}
}
if ((audioFormat === "best" && services[host]["bestAudio"])
|| services[host]["bestAudio"] && (audioFormat === services[host]["bestAudio"])) {
if (host === "tumblr" && !r.filename && (audioFormat === "best" || audioFormat === "mp3")) {
audioFormat = "mp3";
processType = "bridge"
}
if ((audioFormat === "best" && services[host]["bestAudio"]) || (services[host]["bestAudio"] && (audioFormat === services[host]["bestAudio"]))) {
audioFormat = services[host]["bestAudio"];
processType = "bridge"
} else if (audioFormat === "best") {
@ -135,8 +144,7 @@ export default function(r, host, ip, audioFormat, isAudioOnly, lang, isAudioMute
type: processType,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls,
audioFormat: audioFormat,
copy: copy,
fileMetadata: r.fileMetadata ? r.fileMetadata : false
copy: copy
}
break;
default:

View file

@ -11,17 +11,16 @@ export default async function(obj) {
let streamData = JSON.parse(html.split('<script>window.__playinfo__=')[1].split('</script>')[0]);
if (streamData.data.timelength > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let video = streamData["data"]["dash"]["video"].filter((v) => {
if (!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
}).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
let video = streamData["data"]["dash"]["video"].filter(v =>
!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")
).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
let audio = streamData["data"]["dash"]["audio"].filter((a) => {
if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
}).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
let audio = streamData["data"]["dash"]["audio"].filter(a =>
!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")
).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
return {
urls: [video[0]["baseUrl"], audio[0]["baseUrl"]],
time: streamData.data.timelength,
audioFilename: `bilibili_${obj.id}_audio`,
filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4`
};

View file

@ -1,34 +1,100 @@
import got from "got";
import { createStream } from "../../stream/manage.js";
import { genericUserAgent } from "../../config.js";
import { getCookie, updateCookie } from '../cookie/manager.js';
export default async function(obj) {
// i hate this implementation but fetch doesn't work here for some reason (i personally blame facebook)
let html;
let data;
try {
html = await got.get(`https://www.instagram.com/p/${obj.id}/`)
html.on('error', () => {
html = false;
});
html = html ? html.body : false;
const url = new URL('https://www.instagram.com/graphql/query/');
url.searchParams.set('query_hash', 'b3055c01b4b222b8a47dc12b090e4e64')
url.searchParams.set('variables', JSON.stringify({
child_comment_count: 3,
fetch_comment_count: 40,
has_threaded_comments: true,
parent_comment_count: 24,
shortcode: obj.id
}))
const cookie = getCookie('instagram');
data = await fetch(url, {
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'User-Agent': genericUserAgent,
'X-Ig-App-Id': '936619743392459',
'X-Asbd-Id': '129477',
'x-ig-www-claim': cookie?._wwwClaim || '0',
'x-csrftoken': cookie?.values()?.csrftoken,
'x-requested-with': 'XMLHttpRequest',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'upgrade-insecure-requests': '1',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9,en;q=0.8',
cookie
}
})
if (data.headers.get('X-Ig-Set-Www-Claim') && cookie) {
cookie._wwwClaim = data.headers.get('X-Ig-Set-Www-Claim');
}
updateCookie(cookie, data.headers);
data = (await data.json()).data;
} catch (e) {
html = false;
data = false;
}
if (!html) return { error: 'ErrorCouldntFetch' };
if (!html.includes('application/ld+json')) return { error: 'ErrorEmptyDownload' };
if (!data) return { error: 'ErrorCouldntFetch' };
let single, multiple = [], postInfo = JSON.parse(html.split('script type="application/ld+json"')[1].split('">')[1].split('</script>')[0]);
if (postInfo.video.length > 1) {
for (let i in postInfo.video) { multiple.push({type: "video", thumb: postInfo.video[i]["thumbnailUrl"], url: postInfo.video[i]["contentUrl"]}) }
} else if (postInfo.video.length === 1) {
single = postInfo.video[0]["contentUrl"]
let single, multiple = [];
const sidecar = data?.shortcode_media?.edge_sidecar_to_children;
if (sidecar) {
sidecar.edges.forEach(e => {
if (e.node?.is_video) {
multiple.push({
type: "video",
// thumbnails have `Cross-Origin-Resource-Policy` set to `same-origin`, so we need to proxy them
thumb: createStream({
service: "instagram",
type: "default",
u: e.node?.display_url,
filename: "image.jpg"
}),
url: e.node?.video_url
})
} else {
multiple.push({
type: "photo",
thumb: createStream({
service: "instagram",
type: "default",
u: e.node?.display_url,
filename: "image.jpg"
}),
url: e.node?.display_url
})
}
})
} else if (data?.shortcode_media?.video_url) {
single = data.shortcode_media.video_url
} else if (data?.shortcode_media?.display_url) {
return {
urls: data?.shortcode_media?.display_url,
isPhoto: true
}
} else {
return { error: 'ErrorEmptyDownload' }
}
if (single) {
return { urls: single, filename: `instagram_${obj.id}.mp4`, audioFilename: `instagram_${obj.id}_audio` }
} else if (multiple) {
return {
urls: single,
filename: `instagram_${obj.id}.mp4`,
audioFilename: `instagram_${obj.id}_audio`
}
} else if (multiple.length) {
return { picker: multiple }
} else {
return { error: 'ErrorEmptyDownload' }

View file

@ -0,0 +1,24 @@
import { maxVideoDuration } from "../../config.js";
export default async function(obj) {
const pinId = obj.id.split('--').reverse()[0];
if (!(/^\d+$/.test(pinId))) return { error: 'ErrorCantGetID' };
let data = await fetch(`https://www.pinterest.com/resource/PinResource/get?data=${encodeURIComponent(JSON.stringify({
options: {
field_set_key: "unauth_react_main_pin",
id: pinId
}
}))}`).then((r) => { return r.json() }).catch(() => { return false });
if (!data) return { error: 'ErrorCouldntFetch' };
data = data["resource_response"]["data"];
let video = null;
if (data.videos !== null) video = data.videos.video_list.V_720P;
else if (data.story_pin_data !== null) video = data.story_pin_data.pages[0].blocks[0].video.video_list.V_EXP7;
if (!video) return { error: 'ErrorEmptyDownload' };
if (video.duration > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
return { urls: video.url, filename: `pinterest_${pinId}.mp4`, audioFilename: `pinterest_${pinId}_audio` }
}

View file

@ -11,17 +11,25 @@ export default async function(obj) {
if (!("reddit_video" in data["secure_media"])) return { error: 'ErrorEmptyDownload' };
if (data["secure_media"]["reddit_video"]["duration"] * 1000 > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
audio = video.match('.mp4') ? `${video.split('_')[0]}_audio.mp4` : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
await fetch(audio, { method: "HEAD" }).then((r) => {if (Number(r.status) !== 200) audio = ''}).catch(() => {audio = ''});
let audio = false,
video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
audioFileLink = video.match('.mp4') ? `${video.split('_')[0]}_audio.mp4` : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
let id = data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3];
if (!audio.length > 0) return { typeId: 1, urls: video };
await fetch(audioFileLink, { method: "HEAD" }).then((r) => { if (Number(r.status) === 200) audio = true }).catch(() => { audio = false });
// fallback for videos with differentiating audio quality
if (!audio) {
audioFileLink = `${video.split('_')[0]}_AUDIO_128.mp4`
await fetch(audioFileLink, { method: "HEAD" }).then((r) => { if (Number(r.status) === 200) audio = true }).catch(() => { audio = false });
}
let id = video.split('/')[3];
if (!audio) return { typeId: 1, urls: video };
return {
typeId: 2,
type: "render",
urls: [video, audio],
urls: [video, audioFileLink],
audioFilename: `reddit_${id}_audio`,
filename: `reddit_${id}.mp4`
};

View file

@ -1,4 +1,5 @@
import { maxVideoDuration } from "../../config.js";
import { cleanString } from "../../sub/utils.js";
let cachedID = {};
@ -34,28 +35,33 @@ async function findClientID() {
}
export default async function(obj) {
let html;
if (!obj.author && !obj.song && obj.shortLink) {
html = await fetch(`https://on.soundcloud.com/${obj.shortLink}/`).then((r) => { return r.status === 404 ? false : r.text() }).catch(() => { return false });
}
if (obj.author && obj.song) {
html = await fetch(`https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}`).then((r) => { return r.text() }).catch(() => { return false });
}
if (!html) return { error: 'ErrorCouldntFetch' };
if (!(html.includes('<script>window.__sc_hydration = ')
&& html.includes('"format":{"protocol":"progressive","mime_type":"audio/mpeg"},')
&& html.includes('{"hydratable":"sound","data":'))) {
return { error: ['ErrorBrokenLink', 'soundcloud'] }
}
let json = JSON.parse(html.split('{"hydratable":"sound","data":')[1].split('}];</script>')[0])
if (!json["media"]["transcodings"]) return { error: 'ErrorEmptyDownload' };
let clientId = await findClientID();
if (!clientId) return { error: 'ErrorSoundCloudNoClientId' };
let fileUrlBase = json.media.transcodings[0]["url"].replace("/hls", "/progressive"),
let link;
if (obj.shortLink && !obj.author && !obj.song) {
link = await fetch(`https://on.soundcloud.com/${obj.shortLink}/`, { redirect: "manual" }).then((r) => {
if (r.status === 302 && r.headers.get("location").startsWith("https://soundcloud.com/")) {
return r.headers.get("location").split('?', 1)[0]
}
return false
}).catch(() => { return false });
}
if (!link && obj.author && obj.song) {
link = `https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}`
}
if (!link) return { error: 'ErrorCouldntFetch' };
let json = await fetch(`https://api-v2.soundcloud.com/resolve?url=${link}&client_id=${clientId}`).then((r) => {
return r.status === 200 ? r.json() : false
}).catch(() => { return false });
if (!json) return { error: 'ErrorCouldntFetch' };
if (!json["media"]["transcodings"]) return { error: 'ErrorEmptyDownload' };
let fileUrlBase = json.media.transcodings.filter(v => v.preset === "opus_0_0")[0]["url"],
fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
if (fileUrl.substring(0, 54) !== "https://api-v2.soundcloud.com/media/soundcloud:tracks:") return { error: 'ErrorEmptyDownload' };
if (json.duration > maxVideoDuration) return { error: ['ErrorLengthAudioConvert', maxVideoDuration / 60000] };
@ -67,8 +73,8 @@ export default async function(obj) {
urls: file,
audioFilename: `soundcloud_${json.id}`,
fileMetadata: {
title: json.title,
artist: json.user.username,
title: cleanString(json.title.replace(/\p{Emoji}/gu, '').trim()),
artist: cleanString(json.user.username.replace(/\p{Emoji}/gu, '').trim()),
}
}
}

View file

@ -0,0 +1,19 @@
export default async function(obj) {
let video = await fetch(`https://api.streamable.com/videos/${obj.id}`).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
if (!video) return { error: 'ErrorEmptyDownload' };
let best = video.files['mp4-mobile'];
if (video.files.mp4 && (obj.isAudioOnly || obj.quality === "max" || obj.quality >= Number("720"))) {
best = video.files.mp4;
}
if (best) return {
urls: best.url,
filename: `streamable_${obj.id}_${best.width}x${best.height}.mp4`,
audioFilename: `streamable_${obj.id}_audio`,
fileMetadata: {
title: video.title
}
}
return { error: 'ErrorEmptyDownload' }
}

View file

@ -17,7 +17,7 @@ function selector(j, h, id) {
let t;
switch (h) {
case "tiktok":
t = j["aweme_list"].filter((v) => { if (v["aweme_id"] === id) return true })[0];
t = j["aweme_list"].filter(v => v["aweme_id"] === id)[0];
break;
case "douyin":
t = j['aweme_detail'];
@ -92,7 +92,7 @@ export default async function(obj) {
let imageLinks = [];
for (let i in images) {
let sel = obj.host === "tiktok" ? images[i]["display_image"]["url_list"] : images[i]["url_list"];
sel = sel.filter((p) => { if (p.includes(".jpeg?")) return true; })
sel = sel.filter(p => p.includes(".jpeg?"))
imageLinks.push({url: sel[0]})
}
return {

View file

@ -8,7 +8,21 @@ export default async function(obj) {
}).then((r) => { return r.text() }).catch(() => { return false });
if (!html) return { error: 'ErrorCouldntFetch' };
if (!html.includes('property="og:video" content="https://va.media.tumblr.com/')) return { error: 'ErrorEmptyDownload' };
return { urls: `https://va.media.tumblr.com/${html.split('property="og:video" content="https://va.media.tumblr.com/')[1].split('"')[0]}`, filename: `tumblr_${obj.id}.mp4`, audioFilename: `tumblr_${obj.id}_audio` }
let r;
if (html.includes('property="og:video" content="https://va.media.tumblr.com/')) {
r = {
urls: `https://va.media.tumblr.com/${html.split('property="og:video" content="https://va.media.tumblr.com/')[1].split('"')[0]}`,
filename: `tumblr_${obj.id}.mp4`,
audioFilename: `tumblr_${obj.id}_audio`
}
} else if (html.includes('property="og:audio" content="https://a.tumblr.com/')) {
r = {
urls: `https://a.tumblr.com/${html.split('property="og:audio" content="https://a.tumblr.com/')[1].split('"')[0]}`,
audioFilename: `tumblr_${obj.id}`,
isAudioOnly: true
}
} else r = { error: 'ErrorEmptyDownload' };
return r;
}

View file

@ -1,22 +1,22 @@
import { genericUserAgent } from "../../config.js";
function bestQuality(arr) {
return arr.filter((v) => { if (v["content_type"] === "video/mp4") return true }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"].split("?")[0]
return arr.filter(v => v["content_type"] === "video/mp4").sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"]
}
const apiURL = "https://api.twitter.com"
export default async function(obj) {
let _headers = {
"user-agent": genericUserAgent,
"authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA",
// ^ no explicit content, but with multi media support
"host": "api.twitter.com",
"x-twitter-client-language": "en",
"x-twitter-active-user": "yes",
"Accept-Language": "en"
"accept-language": "en"
};
let conversationURL = `${apiURL}/2/timeline/conversation/${obj.id}.json?cards_platform=Web-12&tweet_mode=extended&include_cards=1&include_ext_media_availability=true&include_ext_sensitive_media_warning=true&simple_quoted_tweet=true&trim_user=1`;
let activateURL = `${apiURL}/1.1/guest/activate.json`;
let activateURL = `https://api.twitter.com/1.1/guest/activate.json`;
let graphqlTweetURL = `https://twitter.com/i/api/graphql/0hWvDhmW8YQ-S_ib3azIrw/TweetResultByRestId`;
let graphqlSpaceURL = `https://twitter.com/i/api/graphql/Gdz2uCtmIGMmhjhHG3V7nA/AudioSpaceById`;
let req_act = await fetch(activateURL, {
method: "POST",
@ -24,40 +24,39 @@ export default async function(obj) {
}).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
if (!req_act) return { error: 'ErrorCouldntFetch' };
_headers["host"] = "twitter.com";
_headers["content-type"] = "application/json";
_headers["x-guest-token"] = req_act["guest_token"];
_headers["cookie"] = `guest_id=v1%3A${req_act["guest_token"]};`;
_headers["cookie"] = `guest_id=v1%3A${req_act["guest_token"]}`;
if (!obj.spaceId) {
let conversation = await fetch(conversationURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch((e) => { return false });
if (!conversation || !conversation.globalObjects.tweets[obj.id]) {
_headers.authorization = "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw";
// ^ explicit content, but no multi media support
delete _headers["x-guest-token"];
delete _headers["cookie"];
req_act = await fetch(activateURL, {
method: "POST",
headers: _headers
}).then((r) => { return r.status === 200 ? r.json() : false}).catch(() => { return false });
if (!req_act) return { error: 'ErrorCouldntFetch' };
_headers["x-guest-token"] = req_act["guest_token"];
_headers['cookie'] = `guest_id=v1%3A${req_act["guest_token"]};`;
conversation = await fetch(conversationURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false });
if (obj.id) {
let query = {
variables: {"tweetId": obj.id, "withCommunity": false, "includePromotedContent": false, "withVoice": false},
features: {"creator_subscriptions_tweet_preview_api_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_media_download_video_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_enhance_cards_enabled":false}
}
if (!conversation || !conversation.globalObjects.tweets[obj.id]) return { error: 'ErrorTweetUnavailable' };
query.variables = new URLSearchParams(JSON.stringify(query.variables)).toString().slice(0, -1);
query.features = new URLSearchParams(JSON.stringify(query.features)).toString().slice(0, -1);
query = `${graphqlTweetURL}?variables=${query.variables}&features=${query.features}`;
let baseMedia, baseTweet = conversation.globalObjects.tweets[obj.id];
if (baseTweet.retweeted_status_id_str && conversation.globalObjects.tweets[baseTweet.retweeted_status_id_str].extended_entities) {
baseMedia = conversation.globalObjects.tweets[baseTweet.retweeted_status_id_str].extended_entities
let TweetResultByRestId = await fetch(query, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch((e) => { return false });
// {"data":{"tweetResult":{"result":{"__typename":"TweetUnavailable","reason":"Protected"}}}}
if (!TweetResultByRestId || TweetResultByRestId.data.tweetResult.result.__typename !== "Tweet") return { error: 'ErrorTweetUnavailable' };
let baseMedia,
baseTweet = TweetResultByRestId.data.tweetResult.result.legacy;
if (baseTweet.retweeted_status_result && baseTweet.retweeted_status_result.result.legacy.extended_entities.media) {
baseMedia = baseTweet.retweeted_status_result.result.legacy.extended_entities
} else if (baseTweet.extended_entities && baseTweet.extended_entities.media) {
baseMedia = baseTweet.extended_entities
}
if (!baseMedia) return { error: 'ErrorNoVideosInTweet' };
let single, multiple = [], media = baseMedia["media"];
media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true })
media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true });
if (media.length > 1) {
for (let i in media) { multiple.push({type: "video", thumb: media[i]["media_url_https"], url: bestQuality(media[i]["video_info"]["variants"])}) }
} else if (media.length === 1) {
@ -73,7 +72,9 @@ export default async function(obj) {
} else {
return { error: 'ErrorNoVideosInTweet' }
}
} else {
}
// spaces no longer work with guest authorization
if (obj.spaceId) {
_headers["host"] = "twitter.com";
_headers["content-type"] = "application/json";
@ -83,7 +84,7 @@ export default async function(obj) {
}
query.variables = new URLSearchParams(JSON.stringify(query.variables)).toString().slice(0, -1);
query.features = new URLSearchParams(JSON.stringify(query.features)).toString().slice(0, -1);
query = `https://twitter.com/i/api/graphql/Gdz2uCtmIGMmhjhHG3V7nA/AudioSpaceById?variables=${query.variables}&features=${query.features}`;
query = `${graphqlSpaceURL}?variables=${query.variables}&features=${query.features}`;
let AudioSpaceById = await fetch(query, { headers: _headers }).then((r) => {return r.status === 200 ? r.json() : false}).catch((e) => { return false });
if (!AudioSpaceById) return { error: 'ErrorEmptyDownload' };

View file

@ -1,5 +1,6 @@
import { maxVideoDuration } from "../../config.js";
// vimeo you're fucked in the head for this
const resolutionMatch = {
"3840": "2160",
"2732": "1440",
@ -11,7 +12,6 @@ const resolutionMatch = {
"640": "360",
"426": "240"
}
// ^ vimeo you're fucked in the head for this ^
const qualityMatch = {
"2160": "4K",
@ -64,7 +64,7 @@ export default async function(obj) {
let videoUrl, audioUrl, baseUrl = masterJSONURL.split("/sep/")[0];
switch (type) {
case "parcel":
let masterJSON_Audio = masterJSON.audio.sort((a, b) => Number(b.bitrate) - Number(a.bitrate)).filter((a) => { if (a['mime_type'] === "audio/mp4") return true }),
let masterJSON_Audio = masterJSON.audio.sort((a, b) => Number(b.bitrate) - Number(a.bitrate)).filter(a => a['mime_type'] === "audio/mp4"),
bestAudio = masterJSON_Audio[0];
videoUrl = `${baseUrl}/parcel/video/${bestVideo.index_segment.split('?')[0]}`,
audioUrl = `${baseUrl}/parcel/audio/${bestAudio.index_segment.split('?')[0]}`;

View file

@ -1,59 +1,38 @@
import { xml2json } from "xml-js";
import { genericUserAgent, maxVideoDuration } from "../../config.js";
const representationMatch = {
"2160": 7,
"1440": 6,
"1080": 5,
"720": 4,
"480": 3,
"360": 2,
"240": 1,
"144": 0
}, resolutionMatch = {
"3840": "2160",
"2560": "1440",
"1920": "1080",
"1280": "720",
"852": "480",
"640": "360",
"426": "240",
// "256": "144"
}
const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240"];
export default async function(o) {
let html, url, filename = `vk_${o.userId}_${o.videoId}_`;
let html, url,
quality = o.quality === "max" ? 2160 : o.quality,
filename = `vk_${o.userId}_${o.videoId}_`;
html = await fetch(`https://vk.com/video${o.userId}_${o.videoId}`, {
headers: { "user-agent": genericUserAgent }
}).then((r) => { return r.text() }).catch(() => { return false });
if (!html) return { error: 'ErrorCouldntFetch' };
if (!html.includes(`{"lang":`)) return { error: 'ErrorEmptyDownload' };
let quality = o.quality === "max" ? 7 : representationMatch[o.quality],
js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
if (Number(js.mvData.is_active_live) !== 0) return { error: 'ErrorLiveVideo' };
if (js.mvData.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
if (js.player.params[0]["manifest"]) {
let mpd = JSON.parse(xml2json(js.player.params[0]["manifest"], { compact: true, spaces: 4 })),
repr = mpd.MPD.Period.AdaptationSet.Representation ? mpd.MPD.Period.AdaptationSet.Representation : mpd.MPD.Period.AdaptationSet[0]["Representation"],
bestQuality = repr[repr.length - 1],
resolutionPick = Number(bestQuality._attributes.width) > Number(bestQuality._attributes.height) ? 'width': 'height';
if (Number(bestQuality._attributes.id) > Number(quality)) bestQuality = repr[quality];
url = js.player.params[0][`url${resolutionMatch[bestQuality._attributes[resolutionPick]]}`];
filename += `${bestQuality._attributes.width}x${bestQuality._attributes.height}.mp4`
} else if (js.player.params[0]["url240"]) { // fallback for when video is too old
url = js.player.params[0]["url240"];
filename += `320x240.mp4`
for (let i in resolutions) {
if (js.player.params[0][`url${resolutions[i]}`]) {
quality = resolutions[i];
break
}
}
if (Number(quality) > Number(o.quality)) quality = o.quality;
url = js.player.params[0][`url${quality}`];
filename += `${quality}p.mp4`
if (url && filename) return {
urls: url,
filename: filename
};
}
return { error: 'ErrorEmptyDownload' }
}

View file

@ -1,5 +1,6 @@
import { Innertube } from 'youtubei.js';
import { maxVideoDuration } from '../../config.js';
import { cleanString } from '../../sub/utils.js';
const yt = await Innertube.create();
@ -23,6 +24,10 @@ const c = {
export default async function(o) {
let info, isDubbed, quality = o.quality === "max" ? "9000" : o.quality; //set quality 9000(p) to be interpreted as max
function qual(i) {
return i['quality_label'].split('p')[0].split('s')[0]
}
try {
info = await yt.getBasicInfo(o.id, 'ANDROID');
} catch (e) {
@ -30,22 +35,23 @@ export default async function(o) {
}
if (!info) return { error: 'ErrorCantConnectToServiceAPI' };
if (info.playability_status.status !== 'OK') return { error: 'ErrorYTUnavailable' };
if (info.basic_info.is_live) return { error: 'ErrorLiveVideo' };
let bestQuality, hasAudio, adaptive_formats = info.streaming_data.adaptive_formats.filter((e) => {
if (e["mime_type"].includes(c[o.format].codec) || e["mime_type"].includes(c[o.format].aCodec)) return true
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
let bestQuality, hasAudio, adaptive_formats = info.streaming_data.adaptive_formats.filter(e =>
e["mime_type"].includes(c[o.format].codec) || e["mime_type"].includes(c[o.format].aCodec)
).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
bestQuality = adaptive_formats.find(i => i["has_video"]);
hasAudio = adaptive_formats.find(i => i["has_audio"]);
if (bestQuality) bestQuality = bestQuality['quality_label'].split('p')[0];
if (bestQuality) bestQuality = qual(bestQuality);
if (!bestQuality && !o.isAudioOnly || !hasAudio) return { error: 'ErrorYTTryOtherCodec' };
if (info.basic_info.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let checkBestAudio = (i) => (i["has_audio"] && !i["has_video"]),
audio = adaptive_formats.find(i => checkBestAudio(i) && i["is_original"]);
audio = adaptive_formats.find(i => checkBestAudio(i) && !i["is_dubbed"]);
if (o.dubLang) {
let dubbedAudio = adaptive_formats.find(i => checkBestAudio(i) && i["language"] === o.dubLang);
@ -54,35 +60,38 @@ export default async function(o) {
isDubbed = true
}
}
if (hasAudio && o.isAudioOnly) {
let r = {
type: "render",
isAudioOnly: true,
urls: audio.url,
audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`,
fileMetadata: {
title: info.basic_info.title,
artist: info.basic_info.author.replace("- Topic", "").trim(),
}
};
if (info.basic_info.short_description && info.basic_info.short_description.startsWith("Provided to YouTube by")) {
let descItems = info.basic_info.short_description.split("\n\n")
r.fileMetadata.album = descItems[2]
r.fileMetadata.copyright = descItems[3]
if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim();
};
return r
let fileMetadata = {
title: cleanString(info.basic_info.title.replace(/\p{Emoji}/gu, '').trim()),
artist: cleanString(info.basic_info.author.replace("- Topic", "").replace(/\p{Emoji}/gu, '').trim()),
}
let checkSingle = (i) => ((i['quality_label'].split('p')[0] === quality || i['quality_label'].split('p')[0] === bestQuality) && i["mime_type"].includes(c[o.format].codec)),
checkBestVideo = (i) => (i["has_video"] && !i["has_audio"] && i['quality_label'].split('p')[0] === bestQuality),
checkRightVideo = (i) => (i["has_video"] && !i["has_audio"] && i['quality_label'].split('p')[0] === quality);
if (info.basic_info.short_description && info.basic_info.short_description.startsWith("Provided to YouTube by")) {
let descItems = info.basic_info.short_description.split("\n\n");
fileMetadata.album = descItems[2];
fileMetadata.copyright = descItems[3];
if (descItems[4].startsWith("Released on:")) {
fileMetadata.date = descItems[4].replace("Released on: ", '').trim()
}
};
if (hasAudio && o.isAudioOnly) return {
type: "render",
isAudioOnly: true,
urls: audio.url,
audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`,
fileMetadata: fileMetadata
}
let checkSingle = (i) => ((qual(i) === quality || qual(i) === bestQuality) && i["mime_type"].includes(c[o.format].codec)),
checkBestVideo = (i) => (i["has_video"] && !i["has_audio"] && qual(i) === bestQuality),
checkRightVideo = (i) => (i["has_video"] && !i["has_audio"] && qual(i) === quality);
if (!o.isAudioOnly && !o.isAudioMuted && o.format === 'h264') {
let single = info.streaming_data.formats.find(i => checkSingle(i));
if (single) return {
type: "bridge",
urls: single.url,
filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}`
filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}`,
fileMetadata: fileMetadata
}
};
@ -90,7 +99,8 @@ export default async function(o) {
if (video && audio) return {
type: "render",
urls: [video.url, audio.url],
filename: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}`:''}.${c[o.format].container}`
filename: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}`:''}.${c[o.format].container}`,
fileMetadata: fileMetadata
};
return { error: 'ErrorYTTryOtherCodec' }

View file

@ -2,7 +2,7 @@
"audioIgnore": ["vk"],
"config": {
"bilibili": {
"alias": "bilibili (.com only)",
"alias": "bilibili.com videos",
"patterns": ["video/:id"],
"enabled": true
},
@ -12,7 +12,7 @@
"enabled": true
},
"twitter": {
"alias": "twitter posts & spaces & voice",
"alias": "twitter videos & voice",
"patterns": [":user/status/:id", ":user/status/:id/video/:v", "i/spaces/:spaceId"],
"enabled": true
},
@ -22,8 +22,8 @@
"enabled": true
},
"youtube": {
"alias": "youtube videos & shorts & music",
"patterns": ["watch?v=:id"],
"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
@ -43,7 +43,7 @@
"enabled": false
},
"vimeo": {
"patterns": [":id"],
"patterns": [":id", "video/:id"],
"enabled": true,
"bestAudio": "mp3"
},
@ -53,7 +53,7 @@
"enabled": true
},
"instagram": {
"alias": "instagram reels & video posts",
"alias": "instagram reels & posts",
"patterns": ["reels/:id", "reel/:id", "p/:id"],
"enabled": true
},
@ -63,7 +63,17 @@
"patterns": ["v/:id"],
"enabled": true
},
"twitch": {
"pinterest": {
"alias": "pinterest videos & stories",
"patterns": ["pin/:id"],
"enabled": true
},
"streamable": {
"alias": "streamable.com",
"patterns": [":id", "o/:id", "e/:id", "s/:id"],
"enabled": true
},
"twitch": {
"alias": "twitch vods & videos & clips",
"tld": "tv",
"patterns": ["videos/:video", ":channel/clip/:clip"],

View file

@ -23,12 +23,16 @@ export const testers = {
"vimeo": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length <= 11)),
"soundcloud": (patternMatch) => ((patternMatch["author"] && patternMatch["song"]
&& (patternMatch["author"].length + patternMatch["song"].length) <= 96) || (patternMatch["shortLink"] && patternMatch["shortLink"].length <= 32)),
"soundcloud": (patternMatch) => (patternMatch["author"]?.length <= 25 && patternMatch["song"]?.length <= 255)
|| (patternMatch["shortLink"] && patternMatch["shortLink"].length <= 32),
"instagram": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 12),
"vine": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 12),
"pinterest": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length <= 128),
"streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length === 6),
"twitch": (patternMatch) => ((patternMatch["channel"] && patternMatch["clip"] && patternMatch["clip"].length <= 100 || patternMatch["video"] && patternMatch["video"].length <= 10)),
}

View file

@ -3,50 +3,105 @@ import { createInterface } from "readline";
import { Cyan, Bright } from "./sub/consoleText.js";
import { execSync } from "child_process";
import { version } from "../modules/config.js";
let envPath = './.env';
let q = `${Cyan('?')} \x1b[1m`;
let ob = {}
let ob = {};
let rl = createInterface({ input: process.stdin, output: process.stdout });
let final = () => {
if (existsSync(envPath)) {
unlinkSync(envPath)
}
if (existsSync(envPath)) unlinkSync(envPath);
for (let i in ob) {
appendFileSync(envPath, `${i}=${ob[i]}\n`)
}
console.log(Bright("\nAwesome! I've created a fresh .env file for you."))
console.log(`${Bright("Now I'll run")} ${Cyan("npm install")} ${Bright("to install all dependencies. It shouldn't take long.\n\n")}`)
console.log(Bright("\nAwesome! I've created a fresh .env file for you."));
console.log(`${Bright("Now I'll run")} ${Cyan("npm install")} ${Bright("to install all dependencies. It shouldn't take long.\n\n")}`);
execSync('npm install', { stdio: [0, 1, 2] });
console.log(`\n\n${Cyan("All done!\n")}`)
console.log(Bright("You can re-run this script at any time to update the configuration."))
console.log(Bright("\nYou're now ready to start cobalt. Simply run ") + Cyan("npm start") + Bright('!\nHave fun :)'))
console.log(`\n\n${Cyan("All done!\n")}`);
console.log(Bright("You can re-run this script at any time to update the configuration."));
console.log(Bright("\nYou're now ready to start cobalt. Simply run ") + Cyan("npm start") + Bright('!\nHave fun :)'));
rl.close()
}
console.log(
`${Cyan("Welcome to cobalt!")}\n${Bright("Let's start by creating a new ")}${Cyan(".env")}${Bright(" file. You can always change it later.")}`
`${Cyan(`Hey, this is cobalt v.${version}!`)}\n${Bright("Let's start by creating a new ")}${Cyan(".env")}${Bright(" file. You can always change it later.")}`
)
console.log(
Bright("\nWhat's the domain this instance will be running on? (localhost)\nExample: co.wukko.me")
`\n${Bright("⚠️ Please notice that since v.6.0 cobalt is hosted in two parts. API and web app are now separate.\nMerged hosting is no longer available.")}`
)
function setup() {
console.log(Bright("\nWhat kind of server will this instance be?\nOptions: api, web."));
rl.question(q, r1 => {
ob['selfURL'] = `http://localhost:9000/`
ob['port'] = 9000
if (r1) ob['selfURL'] = `https://${r1}/`
rl.question(q, r1 => {
switch (r1.toLowerCase()) {
case 'api':
console.log(Bright("\nCool! What's the domain this API instance will be running on? (localhost)\nExample: co.wuk.sh"));
console.log(Bright("\nGreat! Now, what's the port it'll be running on? (9000)"))
rl.question(q, apiURL => {
ob['apiURL'] = `http://localhost:9000/`;
ob['apiPort'] = 9000;
if (apiURL && apiURL !== "localhost") ob['apiURL'] = `https://${apiURL.toLowerCase()}/`;
rl.question(q, r2 => {
if (r2) ob['port'] = r2
if (!r1 && r2) ob['selfURL'] = `http://localhost:${r2}/`
console.log(Bright("\nGreat! Now, what port will it be running on? (9000)"));
console.log(Bright("\nWould you like to enable CORS? It allows other websites and extensions to use your instance's API.\ny/n (n)"))
rl.question(q, apiPort => {
if (apiPort) ob['apiPort'] = apiPort;
if (apiPort && (apiURL === "localhost" || !apiURL)) ob['apiURL'] = `http://localhost:${apiPort}/`;
rl.question(q, r3 => {
if (r3.toLowerCase() !== 'y') ob['cors'] = '0'
final()
})
});
})
console.log(Bright("\nWhat will your instance's name be? Usually it's something like eu-nl aka region-country. (local)"));
rl.question(q, apiName => {
ob['apiName'] = apiName.toLowerCase();
if (!apiName || apiName === "local") ob['apiName'] = "local";
console.log(Bright("\nOne last thing: would you like to enable CORS? It allows other websites and extensions to use your instance's API.\ny/n (n)"));
rl.question(q, apiCors => {
let answCors = apiCors.toLowerCase().trim();
if (answCors !== "y" && answCors !== "yes") ob['cors'] = '0'
final()
})
})
});
})
break;
case 'web':
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/`;
ob['webPort'] = 9001;
if (webURL && webURL !== "localhost") ob['webURL'] = `https://${webURL.toLowerCase()}/`;
console.log(
Bright("\nGreat! Now, what port will it be running on? (9001)")
)
rl.question(q, webPort => {
if (webPort) ob['webPort'] = webPort;
if (webPort && (webURL === "localhost" || !webURL)) ob['webURL'] = `http://localhost:${webPort}/`;
console.log(
Bright("\nOne last thing: what default API domain should be used? (co.wuk.sh)\nIf it's hosted locally, make sure to include the port:") + Cyan(" localhost:9000")
);
rl.question(q, apiURL => {
ob['apiURL'] = `https://${apiURL.toLowerCase()}/`;
if (apiURL.includes(':')) ob['apiURL'] = `http://${apiURL.toLowerCase()}/`;
if (!apiURL) ob['apiURL'] = "https://co.wuk.sh/";
final()
})
});
});
break;
default:
console.log(Bright("\nThis is not an option. Try again."));
setup()
}
})
}
setup()

View file

@ -15,7 +15,7 @@ streamCache.on("expired", (key) => {
export function createStream(obj) {
let streamID = nanoid(),
exp = Math.floor(new Date().getTime()) + streamLifespan,
ghmac = sha256(`${streamID},${obj.ip},${obj.service},${exp}`, streamSalt);
ghmac = sha256(`${streamID},${obj.service},${exp}`, streamSalt);
if (!streamCache.has(streamID)) {
streamCache.set(streamID, {
@ -25,7 +25,6 @@ export function createStream(obj) {
urls: obj.u,
filename: obj.filename,
hmac: ghmac,
ip: obj.ip,
exp: exp,
isAudioOnly: !!obj.isAudioOnly,
audioFormat: obj.audioFormat,
@ -39,22 +38,20 @@ export function createStream(obj) {
exp = streamInfo.exp;
ghmac = streamInfo.hmac;
}
return `${process.env.selfURL}api/stream?t=${streamID}&e=${exp}&h=${ghmac}`;
return `${process.env.apiURL || process.env.selfURL}api/stream?t=${streamID}&e=${exp}&h=${ghmac}`;
}
export function verifyStream(ip, id, hmac, exp) {
export function verifyStream(id, hmac, exp) {
try {
if (id.length === 21) {
let streamInfo = streamCache.get(id);
if (!streamInfo) return { error: 'this stream token does not exist', status: 400 };
let ghmac = sha256(`${id},${ip},${streamInfo.service},${exp}`, streamSalt);
if (String(hmac) === ghmac && String(exp) === String(streamInfo.exp) && ghmac === String(streamInfo.hmac)
&& String(ip) === streamInfo.ip && Number(exp) > Math.floor(new Date().getTime())) {
return streamInfo;
}
let streamInfo = streamCache.get(id.toString());
if (!streamInfo) return { error: "this download link has expired or doesn't exist. go back and try again!", status: 400 };
let ghmac = sha256(`${id},${streamInfo.service},${exp}`, streamSalt);
if (String(hmac) === ghmac && String(exp) === String(streamInfo.exp) && ghmac === String(streamInfo.hmac)
&& Number(exp) > Math.floor(new Date().getTime())) {
return streamInfo;
}
return { error: 'Unauthorized', status: 401 };
return { error: "i couldn't verify if you have access to this download. go back and try again!", status: 401 };
} catch (e) {
return { status: 500, body: { status: "error", text: "Internal Server Error" } };
}

View file

@ -1,28 +1,21 @@
import { apiJSON } from "../sub/utils.js";
import { verifyStream } from "./manage.js";
import { streamAudioOnly, streamDefault, streamLiveRender, streamVideoOnly } from "./types.js";
export default function(res, ip, id, hmac, exp) {
export default async function(res, streamInfo) {
try {
let streamInfo = verifyStream(ip, id, hmac, exp);
if (streamInfo.error) {
res.status(streamInfo.status).json(apiJSON(0, { t: streamInfo.error }).body);
return;
}
if (streamInfo.isAudioOnly && streamInfo.type !== "bridge") {
streamAudioOnly(streamInfo, res);
return;
}
switch (streamInfo.type) {
case "render":
streamLiveRender(streamInfo, res);
await streamLiveRender(streamInfo, res);
break;
case "videoM3U8":
case "mute":
streamVideoOnly(streamInfo, res);
break;
default:
streamDefault(streamInfo, res);
await streamDefault(streamInfo, res);
break;
}
} catch (e) {

View file

@ -1,41 +1,45 @@
import { spawn } from "child_process";
import ffmpeg from "ffmpeg-static";
import got from "got";
import { ffmpegArgs, genericUserAgent } from "../config.js";
import { getThreads, metadataManager, msToTime } from "../sub/utils.js";
import { getThreads, metadataManager } from "../sub/utils.js";
import { request } from 'undici';
export function streamDefault(streamInfo, res) {
function fail(res) {
if (!res.headersSent) res.sendStatus(500);
return res.destroy();
}
export async function streamDefault(streamInfo, res) {
try {
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1];
let regFilename = !streamInfo.mute ? streamInfo.filename : `${streamInfo.filename.split('.')[0]}_mute.${format}`;
res.setHeader('Content-disposition', `attachment; filename="${streamInfo.isAudioOnly ? `${streamInfo.filename}.${streamInfo.audioFormat}` : regFilename}"`);
const stream = got.get(streamInfo.urls, {
headers: {
"user-agent": genericUserAgent
},
isStream: true
});
stream.pipe(res).on('error', () => {
res.destroy();
});
stream.on('error', () => {
res.destroy();
});
stream.on('aborted', () => {
res.destroy();
const { body: stream, headers } = await request(streamInfo.urls, {
headers: { 'user-agent': genericUserAgent },
maxRedirections: 16
});
res.setHeader('content-type', headers['content-type']);
res.setHeader('content-length', headers['content-length']);
stream.pipe(res).on('error', () => fail(res));
stream.on('error', () => fail(res));
stream.on('aborted', () => fail(res));
} catch (e) {
res.destroy();
fail(res);
}
}
export function streamLiveRender(streamInfo, res) {
export async function streamLiveRender(streamInfo, res) {
try {
if (streamInfo.urls.length !== 2) {
res.destroy();
return;
}
let audio = got.get(streamInfo.urls[1], { isStream: true });
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
if (streamInfo.urls.length !== 2) return fail(res);
let { body: audio } = await request(streamInfo.urls[1], {
maxRedirections: 16
});
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1],
args = [
'-loglevel', '-8',
'-threads', `${getThreads()}`,
'-i', streamInfo.urls[0],
@ -43,8 +47,9 @@ export function streamLiveRender(streamInfo, res) {
'-map', '0:v',
'-map', '1:a',
];
args = args.concat(ffmpegArgs[format])
if (streamInfo.time) args.push('-t', msToTime(streamInfo.time));
args = args.concat(ffmpegArgs[format]);
if (streamInfo.metadata) args = args.concat(metadataManager(streamInfo.metadata));
args.push('-f', format, 'pipe:4');
let ffmpegProcess = spawn(ffmpeg, args, {
windowsHide: true,
@ -57,24 +62,24 @@ export function streamLiveRender(streamInfo, res) {
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
res.on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
ffmpegProcess.stdio[4].pipe(res).on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
audio.pipe(ffmpegProcess.stdio[3]).on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
audio.on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
audio.on('aborted', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
ffmpegProcess.on('disconnect', () => ffmpegProcess.kill());
@ -84,11 +89,11 @@ export function streamLiveRender(streamInfo, res) {
res.on('close', () => ffmpegProcess.kill());
ffmpegProcess.on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
} catch (e) {
res.destroy();
fail(res);
}
}
export function streamAudioOnly(streamInfo, res) {
@ -132,10 +137,10 @@ export function streamAudioOnly(streamInfo, res) {
res.on('close', () => ffmpegProcess.kill());
ffmpegProcess.on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
} catch (e) {
res.destroy();
fail(res);
}
}
export function streamVideoOnly(streamInfo, res) {
@ -168,9 +173,9 @@ export function streamVideoOnly(streamInfo, res) {
res.on('close', () => ffmpegProcess.kill());
ffmpegProcess.on('error', () => {
ffmpegProcess.kill();
res.destroy();
fail(res);
});
} catch (e) {
res.destroy();
fail(res);
}
}

View file

@ -1,13 +1,15 @@
import { createStream } from "../stream/manage.js";
let apiVar = {
const apiVar = {
allowed: {
vCodec: ["h264", "av1", "vp9"],
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 = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '='];
export function apiJSON(type, obj) {
try {
@ -27,7 +29,7 @@ export function apiJSON(type, obj) {
switch (obj.service) {
case "douyin":
case "tiktok":
audio = createStream(obj)
audio = obj.u
pickerType = "images"
break;
}
@ -47,22 +49,7 @@ export function metadataManager(obj) {
for (let i in keys) { if (tags.includes(keys[i])) commands.push('-metadata', `${keys[i]}=${obj[keys[i]]}`) }
return commands;
}
export function msToTime(d) {
let milliseconds = parseInt((d % 1000) / 100, 10),
seconds = parseInt((d / 1000) % 60, 10),
minutes = parseInt((d / (1000 * 60)) % 60, 10),
hours = parseInt((d / (1000 * 60 * 60)) % 24, 10),
r;
hours = (hours < 10) ? `0${hours}` : hours;
minutes = (minutes < 10) ? `0${minutes}` : minutes;
seconds = (seconds < 10) ? `0${seconds}` : seconds;
r = `${hours}:${minutes}:${seconds}`;
if (milliseconds) r += `.${milliseconds}`;
return r;
}
export function cleanURL(url, host) {
let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@"]
switch(host) {
case "vk":
url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0];
@ -72,6 +59,8 @@ export function cleanURL(url, host) {
break;
case "tiktok":
url = url.replace(/@([a-zA-Z]+(\.[a-zA-Z]+)+)/, "@a")
case "pinterest":
url = url.replace(/:\/\/(?:www.)pinterest(?:\.[a-z.]+)/, "://pinterest.com")
default:
url = url.split('?')[0];
if (url.substring(url.length - 1) === "/") url = url.substring(0, url.length - 1);
@ -89,6 +78,12 @@ export function cleanURL(url, host) {
}
return url.slice(0, 128)
}
export function cleanString(string) {
for (let i in forbiddenCharsString) {
string = string.replaceAll(forbiddenCharsString[i], '')
}
return string;
}
export function verifyLanguageCode(code) {
return RegExp(/[a-z]{2}/).test(String(code.slice(0, 2).toLowerCase())) ? String(code.slice(0, 2).toLowerCase()) : "en"
}
@ -109,13 +104,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])) {
@ -154,3 +150,8 @@ export function getThreads() {
return '0'
}
}
export function cleanHTML(html) {
let clean = html.replace(/ {4}/g, '');
clean = clean.replace(/\n/g, '');
return clean
}

View file

@ -36,8 +36,8 @@
"status": "redirect"
}
}, {
"name": "picker: mixed media (2 videos)",
"url": "https://twitter.com/taehyungsflow/status/1583411488433516544",
"name": "picker: mixed media (3 videos)",
"url": "https://twitter.com/DankGameAlert/status/1584726006094794774",
"params": {
"aFormat": "mp3",
"isAudioOnly": false,
@ -97,15 +97,7 @@
}
}, {
"name": "retweeted video",
"url": "https://twitter.com/winload_exe/status/1639005390854602758",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "retweeted video",
"url": "https://twitter.com/winload_exe/status/1639005390854602758",
"url": "https://twitter.com/uwukko/status/1696901469633421344",
"params": {},
"expected": {
"code": 200,
@ -119,9 +111,25 @@
"code": 200,
"status": "redirect"
}
}, {
"name": "twitter voice + x.com link",
"url": "https://x.com/eggsaladscreams/status/1693089534886506756?s=46",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "vxtwitter link",
"url": "https://vxtwitter.com/dustbin_nie/status/1624596567188717568?s=20",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "retweeted video, isAudioOnly",
"url": "https://twitter.com/winload_exe/status/1633091769482063874",
"url": "https://twitter.com/hugekiwinuts/status/1618671150829309953?s=46&t=gItGzgwGQQJJaJrO6qc1Pg",
"params": {
"aFormat": "mp3",
"isAudioOnly": false,
@ -446,6 +454,25 @@
"code": 200,
"status": "stream"
}
}, {
"name": "vr 360, av1, max",
"url": "https://www.youtube.com/watch?v=hEdzv7D4CbQ",
"params": {
"vCodec": "vp9",
"vQuality": "max"
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "live link, defaults",
"url": "https://www.youtube.com/live/ENxZS6PUDuI?feature=shared",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "inexistent video",
"url": "https://youtube.com/watch?v=gnjuHYWGEW",
@ -491,6 +518,16 @@
"code": 200,
"status": "stream"
}
}, {
"name": "4k video",
"url": "https://vk.com/video-1112285_456248465",
"params": {
"vQuality": "max"
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "ancient video (fallback to 240p)",
"url": "https://vk.com/video-1959_28496479",
@ -717,6 +754,24 @@
"code": 200,
"status": "redirect"
}
}, {
"name": "tumblr audio",
"url": "https://rf9weu8hjf789234hf9.tumblr.com/post/172006661342/everyone-thats-made-a-video-out-of-this-without",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "tumblr video converted to audio",
"url": "https://garfield-69.tumblr.com/post/696499862852780032",
"params": {
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}],
"vimeo": [{
"name": "4k progressive",
@ -761,7 +816,7 @@
}],
"reddit": [{
"name": "video with audio",
"url": "https://www.reddit.com/r/catvideos/comments/b2rygq/my_new_kittens_1st_day_checking_out_his_new_home/?utm_source=share&utm_medium=web2x&context=3",
"url": "https://www.reddit.com/r/TikTokCringe/comments/wup1fg/id_be_escaping_at_the_first_chance_i_got/?utm_source=share&utm_medium=web2x&context=3",
"params": {},
"expected": {
"code": 200,
@ -769,7 +824,7 @@
}
}, {
"name": "video with audio (isAudioOnly)",
"url": "https://www.reddit.com/r/catvideos/comments/b2rygq/my_new_kittens_1st_day_checking_out_his_new_home/?utm_source=share&utm_medium=web2x&context=3",
"url": "https://www.reddit.com/r/TikTokCringe/comments/wup1fg/id_be_escaping_at_the_first_chance_i_got/?utm_source=share&utm_medium=web2x&context=3",
"params": {
"isAudioOnly": true
},
@ -779,7 +834,7 @@
}
}, {
"name": "video with audio (isAudioMuted)",
"url": "https://www.reddit.com/r/catvideos/comments/b2rygq/my_new_kittens_1st_day_checking_out_his_new_home/?utm_source=share&utm_medium=web2x&context=3",
"url": "https://www.reddit.com/r/TikTokCringe/comments/wup1fg/id_be_escaping_at_the_first_chance_i_got/?utm_source=share&utm_medium=web2x&context=3",
"params": {
"isAudioMuted": true
},
@ -803,10 +858,26 @@
"code": 200,
"status": "redirect"
}
}, {
"name": "different audio link, live render",
"url": "https://www.reddit.com/r/TikTokCringe/comments/15hce91/asian_daddy_kink/",
"params": {},
"expected": {
"code": 200,
"status": "stream"
}
}],
"instagram": [{
"name": "several videos in a post (picker)",
"url": "https://www.instagram.com/p/CqifaD0qiDt/",
"name": "single photo post",
"url": "https://www.instagram.com/p/CwIgW8Yu5-I/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "various picker (photos + video)",
"url": "https://www.instagram.com/p/CvYrSgnsKjv/",
"params": {},
"expected": {
"code": 200,
@ -864,6 +935,22 @@
"code": 400,
"status": "error"
}
}, {
"name": "post info in an array (for whatever reason??)",
"url": "https://www.instagram.com/reel/CrVB9tatUDv/?igshid=blaBlABALALbLABULLSHIT==",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "prone to get rate limited",
"url": "https://www.instagram.com/reel/CrO-T7Qo6rq/?igshid=fuckYouNoTrackingIdForYou==",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}],
"vine": [{
"name": "regular vine link (9+10=21)",
@ -894,6 +981,96 @@
"status": "stream"
}
}],
"pinterest": [{
"name": "regular video",
"url": "https://www.pinterest.com/pin/70437485604616/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "regular video (isAudioOnly)",
"url": "https://www.pinterest.com/pin/70437485604616/",
"params": {
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "regular video (isAudioMuted)",
"url": "https://www.pinterest.com/pin/70437485604616/",
"params": {
"isAudioMuted": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "regular video (.ca TLD)",
"url": "https://www.pinterest.ca/pin/70437485604616/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "story",
"url": "https://www.pinterest.com/pin/gadget-cool-products-amazon-product-technology-kitchen-gadgets--1084663891475263837/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}],
"streamable": [{
"name": "regular video",
"url": "https://streamable.com/03r3c2",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "embedded link",
"url": "https://streamable.com/e/rsmo56",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "regular video (isAudioOnly)",
"url": "https://streamable.com/03r3c2",
"params": {
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "regular video (isAudioMuted)",
"url": "https://streamable.com/03r3c2",
"params": {
"isAudioMuted": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "inexistent video",
"url": "https://streamable.com/XXXXXX",
"params": {},
"expected": {
"code": 400,
"status": "error"
}
}],
"twitch": [{
"name": "clip",
"url": "https://twitch.tv/rtgame/clip/TubularInventiveSardineCorgiDerp-PM47mJQQ2vsL5B5G",
@ -951,4 +1128,4 @@
"status": "stream"
}
}]
}
}