From 71cc5305b40fc03d8cad77971525fc3192e63922 Mon Sep 17 00:00:00 2001 From: wukko Date: Mon, 8 May 2023 14:40:38 +0600 Subject: [PATCH 01/12] 5.5.1 - updated readme: added info about new services, sorted the list alphabetically, replaced emoji with checkmarks, added info about ncsd (#101), and more. - fixed typos in vk and setup modules. - removed unused variables from css. --- README.md | 101 +++++++++++++------------- package.json | 2 +- src/front/cobalt.css | 5 -- src/modules/processing/services/vk.js | 2 +- src/modules/setup.js | 4 +- 5 files changed, 53 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index b1e636af..72041af6 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,60 @@ # cobalt -Best way to save what you love. +Best way to save what you love. +Main instance: [co.wukko.me](https://co.wukko.me/) -Live: [co.wukko.me](https://co.wukko.me/) - -![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") +![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) ## What's cobalt? cobalt is a 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. +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. ## Supported services -| Service | Video + Audio | Only audio | Additional features | -| -------- | :---: | :---: | :----- | -| Twitter | ✅ | ✅ | Ability to save multiple videos/GIFs from a single tweet. | -| Twitter Spaces | ❌️ | ✅ | Audio metadata. | -| YouTube & Shorts | ✅ | ✅ | Support for 8K, 4K, HDR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. | -| YouTube Music | ❌ | ✅ | Audio metadata. | -| Reddit | ✅ | ✅ | GIFs and videos. | -| TikTok | ✅ | ✅ | Video downloads with or without watermark; image slideshow downloads without watermark. Full audio downloads. | -| SoundCloud | ❌ | ✅ | Audio metadata, downloads from private links. | -| bilibili.com | ✅ | ✅ | | -| Tumblr | ✅ | ✅ | | -| Vimeo | ✅ | ❌️ | | -| VK Videos & Clips | ✅ | ❌️ | | +| Service | Video + Audio | Only audio | Only video | Additional notes or features | +| -------- | :---: | :---: | :---: | :----- | +| bilibili.com | [x] | [x] | [x] | | +| Instagram | [x] | [x] | [x] | Ability to pick what to save from multi-media posts. | +| Instagram Reels | [x] | [x] | [x] | | +| Reddit | [x] | [x] | [x] | Support for GIFs and videos. | +| SoundCloud | - | [x] | - | Audio metadata, downloads from private links. | +| TikTok | [x] | [x] | [x] | Supports downloads of: videos with or without watermark, images from slideshow without watermark, full (original) audios. | +| Tumblr | [x] | [x] | [x] | | +| Twitter | [x] | [x] | [x] | Ability to pick what to save from multi-media tweets. | +| Twitter Spaces | [x] | [x] | [x] | Audio metadata with all participants and other info. | +| Vimeo | [x] | [x] | [x] | Audio downloads are only available for dash files. | +| Vine Archive | [x] | [x] | [x] | | +| VK Videos | [x] | [ ] | [ ] | | +| VK Clips | [x] | [ ] | [ ] | | +| YouTube Videos & Shorts | [x] | [x] | [x] | Support for 8K, 4K, HDR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. | +| YouTube Music | - | [x] | - | Audio metadata. | + +This list is not final and keeps expanding over time, make sure to check it once in a while! ## cobalt API -cobalt has an open API that you can use 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. +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: -- All text is **ALWAYS** stylized as **lowercase** unless it's STRESSED LIKE THIS or is an internal value like `{ContactLink}` or `{appName}`. +- 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 formal language. Leave it for big and classy tech companies. Use informal language wherever possible. -- Keep translations lively, friendly, and fun. Translate strings as if the user was your buddy. + *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. -- You can add wordplays or puns if it feels natural to do so. - 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. +- Check if there are issues in UI with your localization and optimize it accordingly. If impossible, open an issue. - Be nice. ## Host an instance yourself -You might find cobalt's source code a bit messy, but I do my best to improve it with every commit. - ### Requirements -- Node.js 17.5 or above +- Node.js 18 or above - git -### npm modules -- cors -- dotenv -- esbuild -- express -- express-rate-limit -- ffmpeg-static -- got -- node-cache -- url-pattern -- xml-js -- youtubei.js - Setup script installs all needed `npm` dependencies, but you have to install `Node.js` and `git` yourself. 1. Clone the repo: `git clone https://github.com/wukko/cobalt` @@ -72,20 +62,27 @@ Setup script installs all needed `npm` dependencies, but you have to install `No 3. Run cobalt via `npm start` 4. Done. +### 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)): + +```bash +sudo apt install nscd +sudo service nscd start +``` + ### Docker -It's also possible to host cobalt via a Docker image, but in that case you'd need to set all environment variables by yourself. -That includes: -| Variable | Example | -| -------- | :--- | -| `selfURL` | `https://co.wukko.me/` | -| `port` | `9000` | -| `streamSalt` | `randomly generated sha512 hash` | -| `cors` | `0` | +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` | ## Disclaimer -cobalt is my passion project, so update release schedule depends solely on my motivation, free time, and mood. Don't expect any consistency in that. +cobalt is my passion project, so update schedule depends solely on my free time, motivation, and mood. +Don't expect any consistency in that. ## License -cobalt is under [AGPL-3.0](https://github.com/wukko/cobalt/blob/current/LICENSE) license. - +cobalt is under [AGPL-3.0](https://github.com/wukko/cobalt/blob/current/LICENSE) license. [Fluent Emoji](https://github.com/microsoft/fluentui-emoji) used in the project is under [MIT](https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE) license. diff --git a/package.json b/package.json index fd8d37d7..e94f1b11 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "5.5", + "version": "5.5.1", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index 11d41aa0..a0fb336b 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -7,7 +7,6 @@ --padding-1: 0.75rem; --line-height: 1.65rem; --red: rgb(255, 0, 61); - --color: rgb(107, 67, 139); --gap: 0.6rem; } @media (prefers-color-scheme: dark) { @@ -19,7 +18,6 @@ --accent-unhover: rgb(100, 100, 100); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(0, 0, 0); - --checkmark: url(vectorIcons/checkmark_b.svg); } } @media (prefers-color-scheme: light) { @@ -31,7 +29,6 @@ --accent-unhover: rgb(190, 190, 190); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(255, 255, 255); - --checkmark: url(vectorIcons/checkmark.svg); } } [data-theme="dark"] { @@ -42,7 +39,6 @@ --accent-unhover: rgb(100, 100, 100); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(0, 0, 0); - --checkmark: url(vectorIcons/checkmark_b.svg); } [data-theme="light"] { --accent: rgb(25, 25, 25); @@ -52,7 +48,6 @@ --accent-unhover: rgb(190, 190, 190); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(255, 255, 255); - --checkmark: url(vectorIcons/checkmark.svg); } html, body { diff --git a/src/modules/processing/services/vk.js b/src/modules/processing/services/vk.js index 4be85342..dc1ad39d 100644 --- a/src/modules/processing/services/vk.js +++ b/src/modules/processing/services/vk.js @@ -44,7 +44,7 @@ export default async function(o) { 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` + 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"]; diff --git a/src/modules/setup.js b/src/modules/setup.js index 4fca6c53..2740a770 100644 --- a/src/modules/setup.js +++ b/src/modules/setup.js @@ -1,6 +1,6 @@ import { existsSync, unlinkSync, appendFileSync } from "fs"; import { createInterface } from "readline"; -import { Cyan, Bright, Green } from "./sub/consoleText.js"; +import { Cyan, Bright } from "./sub/consoleText.js"; import { execSync } from "child_process"; let envPath = './.env'; @@ -42,7 +42,7 @@ rl.question(q, r1 => { if (r2) ob['port'] = r2 if (!r1 && r2) ob['selfURL'] = `http://localhost:${r2}/` - console.log(Bright("\nWould you like to enable CORS? It allows other websites and extensions to use your instance's API.\n y/n (n)")) + 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, r3 => { if (r3.toLowerCase() !== 'y') ob['cors'] = '0' From a66a1d3fde2c4132b0ea4c150668ceda642233a8 Mon Sep 17 00:00:00 2001 From: wukko Date: Mon, 8 May 2023 14:46:49 +0600 Subject: [PATCH 02/12] fix checkmarks in readme well that's just rude --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 72041af6..da708ba9 100644 --- a/README.md +++ b/README.md @@ -15,21 +15,21 @@ Paste the link, get the video, move on. It's that simple. Just how it should be. ## Supported services | Service | Video + Audio | Only audio | Only video | Additional notes or features | | -------- | :---: | :---: | :---: | :----- | -| bilibili.com | [x] | [x] | [x] | | -| Instagram | [x] | [x] | [x] | Ability to pick what to save from multi-media posts. | -| Instagram Reels | [x] | [x] | [x] | | -| Reddit | [x] | [x] | [x] | Support for GIFs and videos. | -| SoundCloud | - | [x] | - | Audio metadata, downloads from private links. | -| TikTok | [x] | [x] | [x] | Supports downloads of: videos with or without watermark, images from slideshow without watermark, full (original) audios. | -| Tumblr | [x] | [x] | [x] | | -| Twitter | [x] | [x] | [x] | Ability to pick what to save from multi-media tweets. | -| Twitter Spaces | [x] | [x] | [x] | Audio metadata with all participants and other info. | -| Vimeo | [x] | [x] | [x] | Audio downloads are only available for dash files. | -| Vine Archive | [x] | [x] | [x] | | -| VK Videos | [x] | [ ] | [ ] | | -| VK Clips | [x] | [ ] | [ ] | | -| YouTube Videos & Shorts | [x] | [x] | [x] | Support for 8K, 4K, HDR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. | -| YouTube Music | - | [x] | - | Audio metadata. | +| bilibili.com | ☑️ | ☑️ | ☑️ | | +| Instagram | ☑️ | ☑️ | ☑️ | Ability to pick what to save from multi-media posts. | +| Instagram Reels | ☑️ | ☑️ | ☑️ | | +| Reddit | ☑️ | ☑️ | ☑️ | Support for GIFs and videos. | +| SoundCloud | ➖ | ☑️ | ➖ | Audio metadata, downloads from private links. | +| 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. | +| 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 Music | ➖ | ☑️ | ➖ | Audio metadata. | This list is not final and keeps expanding over time, make sure to check it once in a while! From 995e95790cf43e18735e5052ebc3b319a3a94e65 Mon Sep 17 00:00:00 2001 From: wukko Date: Mon, 8 May 2023 14:47:26 +0600 Subject: [PATCH 03/12] one more checkmark replacement in readme --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index da708ba9..45919594 100644 --- a/README.md +++ b/README.md @@ -15,21 +15,21 @@ Paste the link, get the video, move on. It's that simple. Just how it should be. ## Supported services | 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 Reels | ☑️ | ☑️ | ☑️ | | -| Reddit | ☑️ | ☑️ | ☑️ | Support for GIFs and videos. | -| SoundCloud | ➖ | ☑️ | ➖ | Audio metadata, downloads from private links. | -| 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. | -| 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 Music | ➖ | ☑️ | ➖ | Audio metadata. | +| bilibili.com | ✅ | ✅ | ✅ | | +| Instagram | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media posts. | +| Instagram Reels | ✅ | ✅ | ✅ | | +| Reddit | ✅ | ✅ | ✅ | Support for GIFs and videos. | +| SoundCloud | ➖ | ✅ | ➖ | Audio metadata, downloads from private links. | +| 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. | +| 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 Music | ➖ | ✅ | ➖ | Audio metadata. | This list is not final and keeps expanding over time, make sure to check it once in a while! From df73b8c8608013b2548180f0f31c3c3c664c22e3 Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 9 May 2023 10:18:06 +0600 Subject: [PATCH 04/12] oops --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45919594..881a6347 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Paste the link, get the video, move on. It's that simple. Just how it should be. | 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. | +| Twitter Spaces | ➖ | ✅ | ➖ | Audio metadata with all participants and other info. | | Vimeo | ✅ | ✅ | ✅ | Audio downloads are only available for dash files. | | Vine Archive | ✅ | ✅ | ✅ | | | VK Videos | ✅ | ❌ | ❌ | | From 64987c6494c5aeb13228b7d508d6ce5a552e33df Mon Sep 17 00:00:00 2001 From: wukko Date: Sat, 13 May 2023 18:04:43 +0600 Subject: [PATCH 05/12] experiment: added threads to ffmpeg args --- src/modules/stream/types.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index a4eb233f..8540dacb 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -38,6 +38,7 @@ export function streamLiveRender(streamInfo, res) { let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ '-loglevel', '-8', + '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, '-i', streamInfo.urls[0], '-i', 'pipe:3', '-map', '0:v', @@ -95,6 +96,7 @@ export function streamAudioOnly(streamInfo, res) { try { let args = [ '-loglevel', '-8', + '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, '-i', streamInfo.urls ] if (streamInfo.metadata) { @@ -141,6 +143,7 @@ export function streamVideoOnly(streamInfo, res) { try { let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ '-loglevel', '-8', + '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, '-i', streamInfo.urls, '-c', 'copy' ] From de3b0cdfd28a5ef9b7db8cc6b19a2ffa02f0823f Mon Sep 17 00:00:00 2001 From: wukko Date: Sat, 13 May 2023 18:12:09 +0600 Subject: [PATCH 06/12] last commit was extremely unsafe --- src/modules/stream/types.js | 9 ++++----- src/modules/sub/utils.js | 12 ++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index 8540dacb..6fee9a43 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -2,7 +2,7 @@ import { spawn } from "child_process"; import ffmpeg from "ffmpeg-static"; import got from "got"; import { ffmpegArgs, genericUserAgent } from "../config.js"; -import { metadataManager, msToTime } from "../sub/utils.js"; +import { getThreads, metadataManager, msToTime } from "../sub/utils.js"; export function streamDefault(streamInfo, res) { try { @@ -35,10 +35,9 @@ export function streamLiveRender(streamInfo, res) { return; } let audio = got.get(streamInfo.urls[1], { isStream: true }); - let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ '-loglevel', '-8', - '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, + '-threads', `${getThreads()}`, '-i', streamInfo.urls[0], '-i', 'pipe:3', '-map', '0:v', @@ -96,7 +95,7 @@ export function streamAudioOnly(streamInfo, res) { try { let args = [ '-loglevel', '-8', - '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, + '-threads', `${getThreads()}`, '-i', streamInfo.urls ] if (streamInfo.metadata) { @@ -143,7 +142,7 @@ export function streamVideoOnly(streamInfo, res) { try { let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ '-loglevel', '-8', - '-threads', `${process.env.ffmpegThreads ? process.env.ffmpegThreads : '0'}`, + '-threads', `${getThreads()}`, '-i', streamInfo.urls, '-c', 'copy' ] diff --git a/src/modules/sub/utils.js b/src/modules/sub/utils.js index 27a17b82..48e6b7c7 100644 --- a/src/modules/sub/utils.js +++ b/src/modules/sub/utils.js @@ -139,3 +139,15 @@ export function checkJSONPost(obj) { export function getIP(req) { return req.header('cf-connecting-ip') ? req.header('cf-connecting-ip') : req.ip; } +export function getThreads() { + try { + if (process.env.ffmpegThreads && process.env.ffmpegThreads.length <= 3 + && (Number(process.env.ffmpegThreads) >= 0 && Number(process.env.ffmpegThreads) <= 256)) { + return process.env.ffmpegThreads + } else { + return '0' + } + } catch (e) { + return '0' + } +} From a2e72122856850e5583232623bbdd4ab59a99b2d Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 16 May 2023 21:20:00 +0600 Subject: [PATCH 07/12] fixed the overlapping issue with short soundcloud links ids of some of new links overlap with old ones, so i decided to remove support for old shareable links. keeping support for soundcloud.app.goo.gl links makes no sense, you can't even get them from any of the apps anymore. --- src/modules/processing/services/soundcloud.js | 3 +-- src/test/tests.json | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/modules/processing/services/soundcloud.js b/src/modules/processing/services/soundcloud.js index 0c9434d9..f285f56c 100644 --- a/src/modules/processing/services/soundcloud.js +++ b/src/modules/processing/services/soundcloud.js @@ -36,8 +36,7 @@ async function findClientID() { export default async function(obj) { let html; if (!obj.author && !obj.song && obj.shortLink) { - html = await fetch(`https://soundcloud.app.goo.gl/${obj.shortLink}/`).then((r) => { return r.status === 404 ? false : r.text() }).catch(() => { return false }); - if (!html) html = await fetch(`https://on.soundcloud.com/${obj.shortLink}/`).then((r) => { return r.status === 404 ? false : r.text() }).catch(() => { return false }) + 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 }); diff --git a/src/test/tests.json b/src/test/tests.json index 158a0ea5..131e1614 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -280,6 +280,14 @@ "code": 200, "status": "stream" } + }, { + "name": "on.soundcloud link", + "url": "https://on.soundcloud.com/AG4c", + "params": {}, + "expected": { + "code": 200, + "status": "stream" + } }], "youtube": [{ "name": "4k video (h264, 1440)", From ece4899415cef3a212d9f1a24d5e590d8874cb1d Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 16 May 2023 21:31:11 +0600 Subject: [PATCH 08/12] updated tests for soundcloud --- src/test/tests.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/tests.json b/src/test/tests.json index 131e1614..fe241685 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -282,6 +282,14 @@ } }, { "name": "on.soundcloud link", + "url": "https://on.soundcloud.com/wLZre", + "params": {}, + "expected": { + "code": 200, + "status": "stream" + } + }, { + "name": "on.soundcloud link, different stream type", "url": "https://on.soundcloud.com/AG4c", "params": {}, "expected": { From d85205649e209f709262da6ceb7849385d2f10a1 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 May 2023 02:13:11 +0600 Subject: [PATCH 09/12] 5.6: tiny quality of life improvements - remember celebratory emoji changes? they've been fixed, and are now dynamically loaded! - changelog history now lets you try to load it again if first attempt failed for whatever reason. - added glow to the donation button to make it more visible. - cleaned up frontend js a little bit. - updated some links in tests. --- package.json | 4 +- src/cobalt.js | 19 +++- src/front/cobalt.css | 18 +++ src/front/cobalt.js | 169 ++++++++++++++++------------- src/localization/languages/en.json | 2 +- src/localization/manager.js | 2 +- src/modules/pageRender/elements.js | 28 +++-- src/modules/pageRender/page.js | 4 +- src/test/tests.json | 14 ++- 9 files changed, 164 insertions(+), 96 deletions(-) diff --git a/package.json b/package.json index e94f1b11..f0a2e512 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "5.5.1", + "version": "5.6", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", @@ -34,6 +34,6 @@ "node-cache": "^5.1.2", "url-pattern": "1.0.3", "xml-js": "^1.6.11", - "youtubei.js": "^5.0.0" + "youtubei.js": "^5.1.0" } } diff --git a/src/cobalt.js b/src/cobalt.js index 118ddfb1..990f5d31 100644 --- a/src/cobalt.js +++ b/src/cobalt.js @@ -23,6 +23,7 @@ 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"; if (process.env.selfURL && process.env.port) { const commitHash = shortCommit(); @@ -138,21 +139,29 @@ if (process.env.selfURL && process.env.port) { break; case 'onDemand': if (req.query.blockId) { - let blockId = req.query.blockId.slice(0, 3) + let blockId = req.query.blockId.slice(0, 3); let r, j; switch(blockId) { - case "0": + 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; } - res.status(j.status).json(j.body); + 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); + let j = apiJSON(0, { t: "no block id" }); + res.status(j.status).json(j.body) } break; default: diff --git a/src/front/cobalt.css b/src/front/cobalt.css index a0fb336b..f7034a66 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -8,6 +8,7 @@ --line-height: 1.65rem; --red: rgb(255, 0, 61); --gap: 0.6rem; + --rainbow-gradient: linear-gradient(161deg,#ffe454,#ff6964,#fe85e5,#bd26fe,#587ae9,#8ded95); } @media (prefers-color-scheme: dark) { :root { @@ -655,6 +656,19 @@ button:active, display: block; text-align: right; } +#about-donate-footer::before { + content: ""; + position: absolute; + height: 110%; + width: 32%; + background: var(--rainbow-gradient); + z-index: -2; + filter: blur(5px); + opacity: 0.65; +} +#about-donate-footer:active::before { + opacity: 0; +} /* adapt the page according to screen size */ @media screen and (min-width: 2300px) { html { @@ -797,6 +811,10 @@ button:active, flex-direction: column; align-items: stretch; } + #about-donate-footer::before { + height: 50%; + width: 50%; + } .footer-pair .footer-button { width: 100%!important; } diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 8b8d9732..adc84a31 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -1,13 +1,11 @@ -let ua = navigator.userAgent.toLowerCase(); -let isIOS = ua.match("iphone os"); -let isMobile = ua.match("android") || ua.match("iphone os"); -let version = 26; -let regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/); -let notification = `
` +const ua = navigator.userAgent.toLowerCase(); +const isIOS = ua.match("iphone os"); +const isMobile = ua.match("android") || ua.match("iphone os"); +const version = 26; +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 = `
`; -let store = {} - -let switchers = { +const switchers = { "theme": ["auto", "light", "dark"], "vCodec": ["h264", "av1", "vp9"], "vQuality": ["1080", "max", "2160", "1440", "720", "480", "360"], @@ -15,11 +13,15 @@ let switchers = { "dubLang": ["original", "auto"], "vimeoDash": ["false", "true"], "audioMode": ["false", "true"] -} -let checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio"]; -let exceptions = { // used for mobile devices +}; +const checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio"]; +const exceptions = { // used for mobile devices "vQuality": "720" -} +}; + +const apiURL = ''; + +let store = {}; function eid(id) { return document.getElementById(id) @@ -333,65 +335,83 @@ async function download(url) { if (url.includes("youtube.com/") || url.includes("/youtu.be/")) req.vCodec = sGet("vCodec").slice(0, 4); if ((url.includes("tiktok.com/") || url.includes("douyin.com/")) && sGet("disableTikTokWatermark") === "true") req.isNoTTWatermark = true; } - await fetch('/api/json', { method: "POST", body: JSON.stringify(req), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }).then(async (r) => { - let j = await r.json(); - if (j.status !== "error" && j.status !== "rate-limit") { - if (j.url || j.picker) { - switch (j.status) { - case "redirect": - changeDownloadButton(2, '>>>'); - setTimeout(() => { changeButton(1); }, 1500); - sGet("downloadPopup") === "true" ? popup('download', 1, j.url) : window.open(j.url, '_blank'); - 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()); - } else if (j.picker) { + + let j = await fetch(`${apiURL}/api/json`, { + method: "POST", + body: JSON.stringify(req), + headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } + }).then((r) => { return r.json() }).catch((e) => { return false }); + if (!j) { + internetError(); + return + } + + if (j && j.status !== "error" && j.status !== "rate-limit") { + if (j.text && (!j.url || !j.picker)) { + if (j.status === "success") { + changeButton(2, j.text) + } else changeButton(0, loc.noURLReturned); + } + switch (j.status) { + case "redirect": + changeDownloadButton(2, '>>>'); + setTimeout(() => { changeButton(1); }, 1500); + sGet("downloadPopup") === "true" ? popup('download', 1, j.url) : window.open(j.url, '_blank'); + 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, { arr: j.picker, type: j.pickerType }); + popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType }); setTimeout(() => { changeButton(1) }, 2500); } else { - changeButton(0, loc.noURLReturned); + changeButton(0, jp.text); } - break; - case "stream": - changeDownloadButton(2, '?..') - fetch(`${j.url}&p=1`).then(async (res) => { - let jp = await res.json(); - if (jp.status === "continue") { - changeDownloadButton(2, '>>>'); window.location.href = j.url; - setTimeout(() => { changeButton(1) }, 2500); - } else { - changeButton(0, jp.text); - } - }).catch((error) => internetError()); - break; - case "success": - changeButton(2, j.text); - break; - default: - changeButton(0, loc.unknownStatus); - break; + }).catch((error) => internetError()); + } else if (j.picker) { + changeDownloadButton(2, '>>>'); + popup('picker', 1, { arr: j.picker, type: j.pickerType }); + setTimeout(() => { changeButton(1) }, 2500); + } else { + changeButton(0, loc.noURLReturned); } - } else { - if (j.status === "success") { - changeButton(2, j.text) - } else changeButton(0, loc.noURLReturned); - } - } else { - changeButton(0, j.text); + break; + case "stream": + changeDownloadButton(2, '?..') + fetch(`${j.url}&p=1`).then(async (res) => { + let jp = await res.json(); + if (jp.status === "continue") { + changeDownloadButton(2, '>>>'); window.location.href = j.url; + setTimeout(() => { changeButton(1) }, 2500); + } else { + changeButton(0, jp.text); + } + }).catch((error) => internetError()); + break; + case "success": + changeButton(2, j.text); + break; + default: + changeButton(0, loc.unknownStatus); + break; } - }).catch((error) => internetError()); + } else if (j && j.text) { + changeButton(0, j.text); + } +} +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 }); + if (j && j.status === "success" && j.text) { + eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('🐲', j.text); + } + } catch (e) { + eid("about-footer").innerHTML = bac; + } } async function loadOnDemand(elementId, blockId) { store.historyButton = eid(elementId).innerHTML; @@ -401,17 +421,15 @@ async function loadOnDemand(elementId, blockId) { if (store.historyContent) { j = store.historyContent; } else { - await fetch(`/api/onDemand?blockId=${blockId}`).then(async (r) => { - j = await r.json(); - if (j.status === "success") store.historyContent = j; - }) - } - if (j.status === "success" && j.status !== "rate-limit") { - if (j.text) { - eid(elementId).innerHTML = `${j.text}`; + j = await fetch(`${apiURL}/api/onDemand?blockId=${blockId}`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false }); + if (j && j.status === "success") { + store.historyContent = j; } else { - throw new Error() + throw new Error(); } + } + if (j.text) { + eid(elementId).innerHTML = `${j.text}`; } else { throw new Error() } @@ -431,6 +449,7 @@ window.onload = () => { 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)) { diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index a0c3febb..20ed2e5e 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -94,7 +94,7 @@ "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 completely free to use. but turns out developing and keeping up a web service used by over 80 thousand people is not that easy.\n\nif you ever found {appName} useful and want to keep it online, or simply want to thank the developer, consider chipping in! every cent helps and is VERY appreciated :D", + "DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's completely free to use. 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. donations are more appreciated than ever.\n\ni am yet to earn anything from {appName}, everything goes back to users, so you're essentially helping everyone.", "DonateVia": "donate via", "DonateHireMe": "...or you can hire me :)", "SettingsVideoMute": "mute audio", diff --git a/src/localization/manager.js b/src/localization/manager.js index 65c048c8..8809d1a5 100644 --- a/src/localization/manager.js +++ b/src/localization/manager.js @@ -2,7 +2,7 @@ import * as fs from "fs"; import { appName, repo } from "../modules/config.js"; import loadJson from "../modules/sub/loadJSON.js"; -const locPath = './src/localization/languages' +const locPath = './src/localization/languages'; let loc = {} let languages = []; diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index dc016124..32ccd1db 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -1,4 +1,5 @@ import { celebrations } from "../config.js"; +import emoji from "../emoji.js"; export function switcher(obj) { let items = ``; @@ -151,12 +152,20 @@ export function footerButtons(obj) { items += ``; break; case "popup": - let context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : '' - let context2 = obj[i+1] && obj[i+1]["context"] ? `, '${obj[i+1]["context"]}'` : '' + let buttonName = obj[i]["context"] ? `${obj[i]["name"]}-${obj[i]["context"]}` : obj[i]["name"], + context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : '', + buttonName2, + context2; + + if (obj[i+1]) { + buttonName2 = obj[i+1]["context"] ? `${obj[i+1]["name"]}-${obj[i+1]["context"]}` : obj[i+1]["name"]; + context2 = obj[i+1]["context"] ? `, '${obj[i+1]["context"]}'` : ''; + } + items += ` `; i++; break; @@ -169,7 +178,12 @@ export function explanation(text) { return `
${text}
` } export function celebrationsEmoji() { - let n = new Date().toISOString().split('T')[0].split('-'); - let dm = `${n[1]}-${n[2]}`; - return Object.keys(celebrations).includes(dm) ? celebrations[dm] : "🐲"; + try { + let n = new Date().toISOString().split('T')[0].split('-'); + let dm = `${n[1]}-${n[2]}`; + let f = Object.keys(celebrations).includes(dm) ? celebrations[dm] : "🐲"; + return f != "🐲" ? emoji(f, 22) : false; + } catch (e) { + return false + } } diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index e2df0af7..83232fb0 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -1,4 +1,4 @@ -import { backdropLink, celebrationsEmoji, checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink } from "./elements.js"; +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 { getCommitInfo } from "../sub/currentCommit.js"; import loc from "../../localization/manager.js"; @@ -392,7 +392,7 @@ export default function(obj) { footerButtons([{ name: "about", type: "popup", - text: `${emoji(celebrationsEmoji() , 22)} ${t('AboutTab')}`, + text: `${emoji("🐲" , 22)} ${t('AboutTab')}`, aria: t('AccessibilityOpenAbout') }, { name: "about", diff --git a/src/test/tests.json b/src/test/tests.json index fe241685..17ed2c41 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -36,8 +36,8 @@ "status": "redirect" } }, { - "name": "picker: mixed media (3 gifs + image)", - "url": "https://twitter.com/emerald_pedrod/status/1582418163521581063?s=20", + "name": "picker: mixed media (2 videos)", + "url": "https://twitter.com/taehyungsflow/status/1583411488433516544", "params": { "aFormat": "mp3", "isAudioOnly": false, @@ -633,7 +633,7 @@ } }, { "name": "images", - "url": "https://vt.tiktok.com/ZS8JP89eB/", + "url": "https://www.tiktok.com/@matryoshk4/video/7231234675476532526", "params": {}, "expected": { "code": 200, @@ -820,6 +820,14 @@ "code": 200, "status": "redirect" } + }, { + "name": "regular video", + "url": "https://www.instagram.com/p/CmCVWoIr9OH/", + "params": {}, + "expected": { + "code": 200, + "status": "redirect" + } }, { "name": "reel (isAudioOnly)", "url": "https://www.instagram.com/reel/CoEBV3eM4QR/", From 61357c76f278ed33476a30d07748286f4539a86f Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 May 2023 02:31:22 +0600 Subject: [PATCH 10/12] fix --- src/front/cobalt.js | 21 ++++++++++----------- src/modules/pageRender/elements.js | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/front/cobalt.js b/src/front/cobalt.js index adc84a31..cd77b2d2 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -414,25 +414,24 @@ async function loadCelebrationsEmoji() { } } async function loadOnDemand(elementId, blockId) { + let j = {}; store.historyButton = eid(elementId).innerHTML; - let j = {} - eid(elementId).innerHTML = "..." + eid(elementId).innerHTML = "..."; + try { if (store.historyContent) { j = store.historyContent; } else { - j = await fetch(`${apiURL}/api/onDemand?blockId=${blockId}`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false }); - if (j && j.status === "success") { - store.historyContent = j; - } else { - throw new Error(); - } + await fetch(`${apiURL}/api/onDemand?blockId=${blockId}`).then(async(r) => { + j = await r.json(); + if (j && j.status === "success") { + store.historyContent = j; + } else throw new Error(); + }).catch(() => { throw new Error() }); } if (j.text) { eid(elementId).innerHTML = `${j.text}`; - } else { - throw new Error() - } + } else throw new Error() } catch (e) { eid(elementId).innerHTML = store.historyButton; internetError() diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index 32ccd1db..49549857 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -131,8 +131,8 @@ export function popupWithBottomButtons(obj) { export function backdropLink(link, text) { return `${text}` } -export function socialLink(emoji, name, handle, url) { - return `` +export function socialLink(emji, name, handle, url) { + return `` } export function settingsCategory(obj) { return `
From 0ea28783be39bf2a8302ad3a1ceda4b13e5c37fa Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 May 2023 22:50:11 +0600 Subject: [PATCH 11/12] new donations icon --- src/front/emoji/money_bag.svg | 4 ---- src/front/emoji/sparkling_heart.svg | 5 +++++ src/modules/emoji.js | 4 ++-- src/modules/pageRender/page.js | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 src/front/emoji/money_bag.svg create mode 100644 src/front/emoji/sparkling_heart.svg diff --git a/src/front/emoji/money_bag.svg b/src/front/emoji/money_bag.svg deleted file mode 100644 index 561ee98a..00000000 --- a/src/front/emoji/money_bag.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/front/emoji/sparkling_heart.svg b/src/front/emoji/sparkling_heart.svg new file mode 100644 index 00000000..b5dd6eb2 --- /dev/null +++ b/src/front/emoji/sparkling_heart.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/emoji.js b/src/modules/emoji.js index 59214288..284404d4 100644 --- a/src/modules/emoji.js +++ b/src/modules/emoji.js @@ -1,7 +1,6 @@ const names = { "🎶": "musical_notes", "🎬": "clapper_board", - "💰": "money_bag", "🎉": "party_popper", "❓": "question_mark", "✨": "sparkles", @@ -23,7 +22,8 @@ const names = { "🐦": "bird", "🐙": "octopus", "🔮": "crystal_ball", - "💪": "biceps" + "💪": "biceps", + "💖": "sparkling_heart" } let sizing = { 22: 0.4, diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index 83232fb0..d506890a 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -149,7 +149,7 @@ export default function(obj) { }) }, { name: "donate", - title: `${emoji("💰")} ${t('DonationsTab')}`, + title: `${emoji("💖")} ${t('DonationsTab')}`, content: popup({ name: "donate", header: { @@ -398,7 +398,7 @@ export default function(obj) { name: "about", type: "popup", context: "donate", - text: `${emoji("💰", 22)} ${t('Donate')}`, + text: `${emoji("💖", 22)} ${t('Donate')}`, aria: t('AccessibilityOpenDonate') }, { name: "settings", From fa4e418e36cbe9d3007e9e0e37175219a7abee75 Mon Sep 17 00:00:00 2001 From: wukko Date: Thu, 18 May 2023 23:05:29 +0600 Subject: [PATCH 12/12] 5.7: ui improvements - padding (everywhere) has been slightly reduced to fit in more content. - padding is now consistent across ui. - added more info to the "how to save" popup for ios devices. - crypto wallet press-to-copy buttons now look like buttons. - improved looks for smallest screens (iphone 5, 5s, se, etc). --- package.json | 2 +- src/config.json | 3 + src/front/cobalt.css | 98 +++++++++++++++++++++++------- src/localization/languages/en.json | 2 +- src/localization/languages/ru.json | 4 +- src/localization/manager.js | 4 +- src/modules/config.js | 3 +- src/modules/pageRender/elements.js | 2 +- 8 files changed, 89 insertions(+), 29 deletions(-) diff --git a/package.json b/package.json index f0a2e512..1382b1f5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "5.6", + "version": "5.7", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/config.json b/src/config.json index 5635c179..2871c3ea 100644 --- a/src/config.json +++ b/src/config.json @@ -27,6 +27,9 @@ "boosty": "https://boosty.to/wukko" } }, + "links": { + "saveToGalleryShortcut": "https://www.icloud.com/shortcuts/6d4fe6e5bade4150b8759ce20720c7a3" + }, "celebrations": { "01-01": "🎄", "02-17": "😺", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index f7034a66..9f9c8a9c 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -7,7 +7,8 @@ --padding-1: 0.75rem; --line-height: 1.65rem; --red: rgb(255, 0, 61); - --gap: 0.6rem; + --gap: 0.5rem; + --gap-no-icon: 0.6rem; --rainbow-gradient: linear-gradient(161deg,#ffe454,#ff6964,#fe85e5,#bd26fe,#587ae9,#8ded95); } @media (prefers-color-scheme: dark) { @@ -19,6 +20,7 @@ --accent-unhover: rgb(100, 100, 100); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(0, 0, 0); + --glow-transparency: 0.45; } } @media (prefers-color-scheme: light) { @@ -30,6 +32,7 @@ --accent-unhover: rgb(190, 190, 190); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(255, 255, 255); + --glow-transparency: 0.6; } } [data-theme="dark"] { @@ -40,6 +43,7 @@ --accent-unhover: rgb(100, 100, 100); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(0, 0, 0); + --glow-transparency: 0.45; } [data-theme="light"] { --accent: rgb(25, 25, 25); @@ -49,6 +53,7 @@ --accent-unhover: rgb(190, 190, 190); --accent-unhover-2: rgb(110, 110, 110); --background: rgb(255, 255, 255); + --glow-transparency: 0.6; } html, body { @@ -83,7 +88,7 @@ a { align-items: center; flex-direction: row; flex-wrap: nowrap; - padding: 0.55rem 1rem 0.55rem 0.7rem; + padding: calc(var(--gap) - 0.1rem) calc(var(--gap)*2 - 0.2rem) calc(var(--gap) - 0.1rem) var(--gap); width: auto; margin-right: var(--padding-1); margin-bottom: var(--padding-1); @@ -223,7 +228,7 @@ button:active, color: var(--accent); } #url-input-area { - background: var(--background); + background: none; padding: 0 1rem; width: 100%; color: var(--accent); @@ -345,7 +350,7 @@ button:active, } .changelog-subtitle { font-size: 1.1rem; - padding-bottom: 0.7rem; + padding-bottom: var(--gap-no-icon); } .changelog-banner { width: 100%; @@ -437,7 +442,7 @@ button:active, color: var(--accent-unhover-2); border-bottom: 0.05rem solid var(--accent-unhover-2); padding-bottom: 0.25rem; - margin-bottom: 1rem; + margin-bottom: calc(var(--gap-no-icon)*1.5); } .category-title { text-align: left; @@ -470,7 +475,7 @@ button:active, margin-top: 0.5rem; } .explanation { - margin-top: 1rem; + margin-top: 0.8rem; width: 100%; font-size: 0.8rem; text-align: left; @@ -481,7 +486,7 @@ button:active, color: var(--accent-unhover-2); } .switch { - padding: 0.7rem; + padding: var(--gap-no-icon); width: 100%; text-align: left; color: var(--accent); @@ -511,6 +516,13 @@ button:active, overflow-x: scroll; scrollbar-width: none; } +.switches .switch { + padding-left: calc(var(--gap-no-icon) + 0.1rem); + padding-right: calc(var(--gap-no-icon) + 0.1rem); +} +#popup-settings .switches .switch { + text-align: center; +} .autowidth { width: auto; } @@ -520,12 +532,12 @@ button:active, .text-to-copy { user-select: text; -webkit-user-select: text; - border: var(--border-15); + background: var(--accent-button-bg); padding: var(--padding-1); overflow: auto; } #close-button { - max-width: 2.8rem; + max-width: 2.6rem; margin-left: var(--padding-1); background: var(--background); border: var(--border-15); @@ -536,7 +548,7 @@ button:active, float: right; position: absolute; right: 0; - height: 2.8rem; + height: 2.6rem; } .popup-tab-content { display: none; @@ -548,7 +560,7 @@ button:active, width: 100%; } .popup-tabs { - margin-top: 0.8rem; + margin-top: 0.9rem; } .emoji { margin-right: 0.4rem; @@ -664,7 +676,7 @@ button:active, background: var(--rainbow-gradient); z-index: -2; filter: blur(5px); - opacity: 0.65; + opacity: var(--glow-transparency); } #about-donate-footer:active::before { opacity: 0; @@ -763,9 +775,14 @@ button:active, } } @media screen and (max-width: 320px) { + :root { + --gap: 0.38rem; + --gap-no-icon: 0.38rem; + --line-height: 1.2rem; + } #popup-title { - font-size: 1.3rem; - line-height: 2rem; + font-size: 1.07rem; + line-height: 1.5rem; } .footer-button, #audioMode-false, @@ -779,22 +796,61 @@ button:active, #paste .emoji { margin-right: 0; } - .switch, .checkbox, .category-title, .subtitle, #popup-desc { - font-size: .75rem; + .switch, + .checkbox, + .category-title, + .subtitle, + #popup-desc, + .collapse-title { + font-size: .7rem; + } + .collapse-header { + padding: 0.5rem; + } + #popup-above-title, + #url-input-area { + font-size: 0.6rem; } .explanation { - font-size: .77rem; - margin-top: 0.8rem; + font-size: .6rem; + margin-top: 0.5rem; + line-height: 1rem!important; } #popup-desc { - line-height: 1.4rem; + line-height: 1.2rem; + font-size: .64rem; } .changelog-subtitle, #popup-subtitle { - font-size: 0.9rem!important; + font-size: 0.8rem!important; } .category-title { margin-bottom: 0.8rem; } + .emoji { + height: 18px; + width: 18px; + } + .desc-padding { + padding-bottom: 0.8rem; + } + #logo { + font-size: 0.8rem; + } + .popup, + .popup.scrollable, + .popup.small { + height: 98%; + } + [type=checkbox] { + width: 15px; + height: 15px; + border: 0.12rem solid var(--accent); + } + [type=checkbox]:before { + transform: scaleY(.8)scaleX(.7)rotate(45deg); + left: 3.4px; + top: -2px; + } } @media screen and (max-width: 720px) { #cobalt-main-box #bottom { @@ -804,7 +860,7 @@ button:active, width: 100%; } #footer { - bottom: 4%; + bottom: 4.9%; transform: translate(-50%, 0%); } #footer-buttons { diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 20ed2e5e..4f052578 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -47,7 +47,7 @@ "SettingsQualityDescription": "if selected quality isn't available, closest one is used instead.", "LinkGitHubChanges": ">> 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": "press and hold the download button, hide the video preview, and then select \"download linked file\" to save.", + "DownloadPopupDescriptionIOS": "easiest way to save videos on ios:\n1. add this siri shortcut.\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\".", "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", diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index d971c261..7f59f356 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -47,7 +47,7 @@ "SettingsQualityDescription": "если выбранное качество недоступно, то выбирается ближайшее к нему.", "LinkGitHubChanges": ">> смотри предыдущие изменения на github", "NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.", - "DownloadPopupDescriptionIOS": "зажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.", + "DownloadPopupDescriptionIOS": "наиболее простой метод скачивания видео на ios:\n1. добавь этот сценарий siri.\n2. нажми \"поделиться\" выше и выбери \"save to photos\" в открывшемся окне.\nесли появляется окно с запросом разрешения, то прочитай его, потом нажми \"всегда разрешать\".\n\nальтернативный метод: зажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.\nпотом открой загрузки в safari, выбери скачанный файл, нажми иконку \"поделиться\", и, наконец, нажми \"сохранить видео\".", "DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода скачивания файла в настройках.", "DownloadPopupWayToSave": "выбери, как сохранить", "ClickToCopy": "нажми, чтобы скопировать", @@ -94,7 +94,7 @@ "ChangelogPressToHide": "скрыть", "Donate": "задонатить", "DonateSub": "ты можешь помочь!", - "DonateExplanation": "{appName} не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает совершенно бесплатно. но оказывается, что разработка и поддержка сервиса, которым пользуются более 80 тысяч людей, обходится довольно трудно.\n\nесли {appName} тебе помог и ты хочешь поблагодарить разработчика, то это можно сделать через донаты! каждый рубль помогает мне, моим котам, и {appName}! спасибо :)", + "DonateExplanation": "{appName} не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает совершенно бесплатно. но оказывается, что разработка и поддержка сервиса, которым пользуются более 150 тысяч людей, обходится довольно затратно.\n\nесли {appName} тебе помог и ты хочешь поблагодарить разработчика, то это можно сделать через донаты! каждый рубль помогает мне, моим котам, и {appName}! спасибо :)", "DonateVia": "открыть", "DonateHireMe": "...или же ты можешь пригласить меня на работу :)", "SettingsVideoMute": "убрать аудио", diff --git a/src/localization/manager.js b/src/localization/manager.js index 8809d1a5..2f5a334a 100644 --- a/src/localization/manager.js +++ b/src/localization/manager.js @@ -1,5 +1,5 @@ import * as fs from "fs"; -import { appName, repo } from "../modules/config.js"; +import { appName, links, repo } from "../modules/config.js"; import loadJson from "../modules/sub/loadJSON.js"; const locPath = './src/localization/languages'; @@ -19,7 +19,7 @@ export function loadLoc() { loadLoc(); export function replaceBase(s) { - return s.replace(/\n/g, '
').replace(/{appName}/g, appName).replace(/{repo}/g, repo).replace(/\*;/g, "•"); + return s.replace(/\n/g, '
').replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut).replace(/{appName}/g, appName).replace(/{repo}/g, repo).replace(/\*;/g, "•"); } export function replaceAll(lang, str, string, replacement) { let s = replaceBase(str[string]) diff --git a/src/modules/config.js b/src/modules/config.js index 64dfca7d..00b95667 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -16,4 +16,5 @@ export const donations = config.donations, ffmpegArgs = config.ffmpegArgs, supportedAudio = config.supportedAudio, - celebrations = config.celebrations + celebrations = config.celebrations, + links = config.links diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index 49549857..cdf155ed 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -12,7 +12,7 @@ export function switcher(obj) { } } - if (obj.noParent) return `
${items}
`; + if (obj.noParent) return `
${items}
`; return `
${obj.subtitle ? `
${obj.subtitle}
` : ``}
${items}