diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 00000000..e5429401
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,32 @@
+---
+name: bug report
+about: report an issue with downloads or something else
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**bug description**
+a clear and concise description of what the bug is.
+
+**reproduction steps**
+steps to reproduce the behavior:
+1. go to '...'
+2. click on '....'
+3. download this video: **[link here]**
+4. see error
+
+**screenshots**
+if applicable, add screenshots or screen recordings to help explain your problem.
+
+**links**
+if applicable, add links that cause the issue. more = better.
+
+**platform**
+- OS [e.g. iOS, windows]
+- browser [e.g. chrome, safari, firefox]
+- version [e.g. 115]
+
+**additional context**
+add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644
index 00000000..18307f4f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature-request.md
@@ -0,0 +1,17 @@
+---
+name: feature request
+about: suggest a feature for cobalt
+title: ''
+labels: feature request
+assignees: ''
+
+---
+
+**describe the feature you'd like to see**
+a clear and concise description of what you want to happen.
+
+**describe alternatives you've considered**
+a clear and concise description of any alternative solutions or features you've considered.
+
+**additional context**
+add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index d70b1821..8b7042d9 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -29,20 +29,22 @@ jobs:
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: Get release metadata
+ id: release-meta
+ run: |
+ version=$(cat package.json | jq -r .version)
+ echo "commit_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ echo "version=$version" >> $GITHUB_OUTPUT
+ echo "major_version=$(echo "$version" | cut -d. -f1)" >> $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 }}
+ type=raw,value=${{ steps.release-meta.outputs.version }}
+ type=raw,value=${{ steps.release-meta.outputs.major_version }}
+ type=raw,value=${{ steps.release-meta.outputs.version }}-${{ steps.release-meta.outputs.commit_short }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
diff --git a/.gitignore b/.gitignore
index 887344cc..a21273d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
+# os stuff
+.DS_Store
+desktop.ini
+
# npm
node_modules
package-lock.json
diff --git a/README.md b/README.md
index be0429df..16998478 100644
--- a/README.md
+++ b/README.md
@@ -1,78 +1,80 @@
# cobalt
-Best way to save what you love.
-Live web app: [cobalt.tools](https://cobalt.tools/)
+best way to save what you love: [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")
+![cobalt logo with repeated logo (double arrow) pattern background](https://raw.githubusercontent.com/wukko/cobalt/current/src/front/icons/pattern.png "cobalt logo with repeated logo (double arrow) pattern background")
-[![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 media 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***.
-## What's cobalt?
-cobalt is social and media platform downloader that doesn't piss you off.
+paste the link, get the file, 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
+this list is not final and keeps expanding over time. if support for a service you want is missing, create an issue (or a pull request 👀).
-## Supported services
-| Service | Video + Audio | Only audio | Only video | Additional notes or features |
-| -------- | :---: | :---: | :---: | :----- |
-| bilibili.com | ✅ | ✅ | ✅ | |
-| 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. |
-| Rutube | ✅ | ✅ | ✅ | |
-| SoundCloud | ➖ | ✅ | ➖ | Audio metadata, downloads from private links. |
-| Streamable | ✅ | ✅ | ✅ | |
-| TikTok | ✅ | ✅ | ✅ | Supports downloads of: videos with or without watermark, images from slideshow without watermark, full (original) audios. |
-| Tumblr | ✅ | ✅ | ✅ | Support for audio file downloads. |
-| Twitch Clips | ✅ | ✅ | ✅ | |
-| Twitter/X * | ✅ | ✅ | ✅ | Ability to pick what to save from multi-media tweets. |
-| Vimeo | ✅ | ✅ | ✅ | Audio downloads are only available for dash files. |
-| Vine Archive | ✅ | ✅ | ✅ | |
-| VK Videos | ✅ | ❌ | ❌ | |
-| VK Clips | ✅ | ❌ | ❌ | |
-| YouTube Videos & Shorts | ✅ | ✅ | ✅ | Support for 8K, 4K, HDR, VR, and high FPS videos. Audio metadata & dubs. h264/av1/vp9 codecs. |
-| YouTube Music | ➖ | ✅ | ➖ | Audio metadata. |
+| service | video + audio | only audio | only video | metadata | rich file names |
+| :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: |
+| bilibili.com | ✅ | ✅ | ✅ | ➖ | ➖ |
+| instagram posts & stories | ✅ | ✅ | ✅ | ➖ | ➖ |
+| instagram reels | ✅ | ✅ | ✅ | ➖ | ➖ |
+| pinterest | ✅ | ✅ | ✅ | ➖ | ➖ |
+| reddit | ✅ | ✅ | ✅ | ❌ | ❌ |
+| rutube | ✅ | ✅ | ✅ | ✅ | ✅ |
+| soundcloud | ➖ | ✅ | ➖ | ✅ | ✅ |
+| streamable | ✅ | ✅ | ✅ | ➖ | ➖ |
+| tiktok | ✅ | ✅ | ✅ | ❌ | ❌ |
+| tumblr | ✅ | ✅ | ✅ | ➖ | ➖ |
+| twitch clips | ✅ | ✅ | ✅ | ✅ | ✅ |
+| twitter/x | ✅ | ✅ | ✅ | ➖ | ➖ |
+| vimeo | ✅ | ✅ | ✅ | ✅ | ✅ |
+| vine archive | ✅ | ✅ | ✅ | ➖ | ➖ |
+| vk videos & clips | ✅ | ❌ | ❌ | ✅ | ✅ |
+| youtube videos, shorts & music | ✅ | ✅ | ✅ | ✅ | ✅ |
-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.
+| emoji | meaning |
+| :-----: | :---------------------- |
+| ✅ | supported |
+| ➖ | impossible/unreasonable |
+| ❌ | not supported |
-## 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.
-Feel free to use the main API instance ([co.wuk.sh](https://co.wuk.sh/)) in your projects.
+### additional notes or features (per service)
+| service | notes or features |
+| :-------- | :----- |
+| instagram | supports photos, videos, and stories. lets you pick what to save from multi-media posts. |
+| pinterest | supports videos and stories. |
+| reddit | supports gifs and videos. |
+| soundcloud | supports private links. |
+| tiktok | supports videos with or without watermark, images from slideshow without watermark, and full (original) audios. |
+| twitter/x | lets you pick what to save from multi-media posts. may not be 100% reliable due to current management. |
+| vimeo | audio downloads are only available for dash. |
+| youtube | supports videos, music, and shorts. 8K, 4K, HDR, VR, and high FPS videos. rich metadata & dubs. h264/av1/vp9 codecs. |
-## Host an instance yourself
-### Requirements
-- Node.js 18 or above
-- git
+## cobalt api
+cobalt has an open api that you can use in projects *for completely free~*. it's easy and straightforward to use, [check out the docs](https://github.com/wukko/cobalt/blob/current/docs/api.md) to learn how to use it.
-Setup script installs all needed `npm` dependencies, but you have to install `Node.js` and `git` yourself.
+you can use the main api instance ([co.wuk.sh](https://co.wuk.sh/)) in your projects.
-1. Clone the repo: `git clone https://github.com/wukko/cobalt`
-2. Run setup script and follow instructions: `npm run setup`
-3. Run cobalt via `npm start`
-4. Done.
+## how to run your own instance
+if you want to run your own instance for whatever purpose, [follow this guide](https://github.com/wukko/cobalt/blob/current/docs/run-an-instance.md).
+it's *highly* recommended to use a docker compose method unless you run for developing/debugging purposes.
-You need to host API and web app separately since v.6.0. Setup script will help you with that!
+## sponsors
+cobalt is sponsored by [royalehosting.net](https://royalehosting.net/), all main instances are currently hosted on their network :)
-### 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)):
+## ethics and disclaimer
+cobalt is a tool for easing content downloads from internet and takes ***zero liability***. you are responsible for what you download, how you use and distribute that content. please be mindful when using content of others and always credit original creators. fair use and credits benefit everyone.
-```bash
-sudo apt install nscd
-sudo service nscd start
-```
+cobalt is ***NOT*** a piracy tool and cannot be used as such. it can only download free, publicly accessible content. such content can be easily downloaded through any browser's dev tools. pressing one button is easier, so i made a convenient, ad-less tool for such repeated actions.
-### Docker
-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.
+cobalt is my passion project, update schedule depends solely on my free time, motivation, and mood. don't expect any consistency in update releases.
-## Disclaimer
-cobalt is my passion project, so update schedule depends solely on my free time, motivation, and mood.
-Don't expect any consistency in that.
+## cobalt licenses
+cobalt code is licensed 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.
+update banners and various assets of cobalt branding included within the repo are *not* covered by the AGPL-3.0 license and cannot be used using same terms.
+
+## 3rd party licenses
+[Fluent Emoji by Microsoft](https://github.com/microsoft/fluentui-emoji) (used in cobalt) is under [MIT](https://github.com/microsoft/fluentui-emoji/blob/main/LICENSE) license.
+
+[Noto Sans Mono](https://fonts.google.com/noto/specimen/Noto+Sans+Mono/) fonts (used in cobalt) are licensed under the [OFL](https://fonts.google.com/noto/specimen/Noto+Sans+Mono/about) license.
+
+many update banners were taken from [tenor.com](https://tenor.com/).
\ No newline at end of file
diff --git a/crowdin.yml b/crowdin.yml
deleted file mode 100644
index ba9e2f42..00000000
--- a/crowdin.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-files:
- - source: /src/localization/languages/en.json
- translation: /src/localization/languages/%two_letters_code%.json
diff --git a/docs/API.md b/docs/API.md
deleted file mode 100644
index 7d5fa7d4..00000000
--- a/docs/API.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# cobalt API Documentation
-This document provides info about methods and acceptable variables for all cobalt API requests.
-
-```
-⚠️ 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.
-
-Request Body Type: ``application/json``
-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 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 |
-|:---------------|:-----------|:--------------------------------------------------------------|
-| ``status`` | ``string`` | ``error / redirect / stream / success / rate-limit / picker`` |
-| ``text`` | ``string`` | Text |
-| ``url`` | ``string`` | Direct link to a file / link to cobalt's live render |
-| ``pickerType`` | ``string`` | ``various / images`` |
-| ``picker`` | ``array`` | Array of picker items |
-| ``audio`` | ``string`` | Direct link to a file / link to cobalt's live render |
-
-### Picker Item Variables
-Item type: ``object``
-| key | type | variables | description |
-|:---------------|:-----------|:------------------------------------------------|:--------------------------------------------|
-| ``type`` | ``string`` | ``video`` | Used only if ``pickerType`` is ``various``. |
-| ``url`` | ``string`` | Direct link to a file / link to cobalt's live render | |
-| ``thumb`` | ``string`` | Item thumbnail that's displayed in the picker | Used only for ``video`` type. |
-
-## GET: ``/api/stream``
-Content live render streaming endpoint.
-
-### Request Query Variables
-| key | variables | description |
-|:--------|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------|
-| ``p`` | ``1`` | Used for probing whether user is rate limited. |
-| ``t`` | Stream token | Unique stream ID. Used for retrieving cached stream info data. |
-| ``h`` | HMAC | Hashed combination of: (hashed) ip address, stream token, expiry timestamp, and service name. Used for verification of stream. |
-| ``e`` | Expiry timestamp | |
-
-## GET: ``/api/serverInfo``
-Returns current basic server info.
-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`` | ``int`` | CORS status |
-| ``startTime`` | ``string`` | Server start time |
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 00000000..6d8cc697
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,80 @@
+# cobalt api documentation
+this document provides info about methods and acceptable variables for all cobalt api requests.
+
+```
+👍 you can use co.wuk.sh instance in your projects for free, just don't be an asshole.
+```
+
+## POST: `/api/json`
+cobalt's main processing endpoint.
+
+request body type: `application/json`
+response body type: `application/json`
+
+```
+⚠️ you must include Accept and Content-Type headers with every POST /api/json request.
+
+Accept: application/json
+Content-Type: application/json
+```
+
+### request body variables
+| key | type | variables | default | description |
+|:------------------|:----------|:-----------------------------------|:----------|:--------------------------------------------------------------------------------|
+| `url` | `string` | 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` | |
+| `filenamePattern` | `string` | `classic / pretty / basic / nerdy` | `classic` | changes the way files are named. previews can be seen in the web app. |
+| `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 header for youtube video audio tracks when `true`. |
+| `disableMetadata` | `boolean` | `true / false` | `false` | disables file metadata when set to `true`. |
+| `twitterGif` | `boolean` | `true / false` | `false` | changes whether twitter gifs are converted to .gif |
+
+### response body variables
+| key | type | variables |
+|:-------------|:---------|:------------------------------------------------------------|
+| `status` | `string` | `error / redirect / stream / success / rate-limit / picker` |
+| `text` | `string` | various text, mostly used for errors |
+| `url` | `string` | direct link to a file or a link to cobalt's live render |
+| `pickerType` | `string` | `various / images` |
+| `picker` | `array` | array of picker items |
+| `audio` | `string` | direct link to a file or a link to cobalt's live render |
+
+### picker item variables
+item type: `object`
+
+| key | type | variables | description |
+|:--------|:---------|:--------------------------------------------------------|:---------------------------------------|
+| `type` | `string` | `video` | used only if `pickerType`is `various`. |
+| `url` | `string` | direct link to a file or a link to cobalt's live render | |
+| `thumb` | `string` | item thumbnail that's displayed in the picker | used only for `video` type. |
+
+## GET: `/api/stream`
+cobalt's live render (or stream) endpoint. used for sending various media content over to the user.
+
+### request query variables
+| key | variables | description |
+|:-----|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------|
+| `p` | `1` | used for probing whether user is rate limited. |
+| `t` | stream token | unique stream id. used for retrieving cached stream info data. |
+| `h` | hmac | hashed combination of: (hashed) ip address, stream token, expiry timestamp, and service name. used for verification of stream. |
+| `e` | expiry timestamp | |
+
+## GET: `/api/serverInfo`
+returns current basic server info.
+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` | `int` | cors status |
+| `startTime` | `string` | server start time |
diff --git a/src/modules/processing/cookie/cookies_example.json b/docs/examples/cookies.example.json
similarity index 100%
rename from src/modules/processing/cookie/cookies_example.json
rename to docs/examples/cookies.example.json
diff --git a/docker-compose.example.yml b/docs/examples/docker-compose.example.yml
similarity index 87%
rename from docker-compose.example.yml
rename to docs/examples/docker-compose.example.yml
index a74a89af..b5ce8a30 100644
--- a/docker-compose.example.yml
+++ b/docs/examples/docker-compose.example.yml
@@ -2,7 +2,7 @@ version: '3.5'
services:
cobalt-api:
- image: ghcr.io/wukko/cobalt:latest
+ image: ghcr.io/wukko/cobalt:7
restart: unless-stopped
container_name: cobalt-api
@@ -17,14 +17,13 @@ services:
#- 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.
+ # see cookies_example.json for example file.
labels:
- com.centurylinklabs.watchtower.scope=cobalt
@@ -33,7 +32,7 @@ services:
#- ./cookies.json:/cookies.json
cobalt-web:
- image: ghcr.io/wukko/cobalt:latest
+ image: ghcr.io/wukko/cobalt:7
restart: unless-stopped
container_name: cobalt-web
@@ -48,7 +47,6 @@ services:
#- 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
@@ -63,4 +61,4 @@ services:
restart: unless-stopped
command: --cleanup --scope cobalt --interval 900
volumes:
- - /var/run/docker.sock:/var/run/docker.sock
\ No newline at end of file
+ - /var/run/docker.sock:/var/run/docker.sock
diff --git a/docs/run-an-instance.md b/docs/run-an-instance.md
new file mode 100644
index 00000000..801895dc
--- /dev/null
+++ b/docs/run-an-instance.md
@@ -0,0 +1,49 @@
+# how to host a cobalt instance yourself
+## using docker compose and package from github (recommended)
+to run the cobalt docker package, you need to have `docker` and `docker-compose` installed and configured.
+
+if you need help with installing docker, follow *only the first step* of these tutorials by digitalocean:
+- [how to install docker](https://www.digitalocean.com/community/tutorial-collections/how-to-install-and-use-docker)
+- [how to install docker compose](https://www.digitalocean.com/community/tutorial-collections/how-to-install-docker-compose)
+
+## how to run a cobalt docker package:
+1. create a folder for cobalt config file, something like this:
+ ```sh
+ mkdir cobalt
+ ```
+
+2. go to cobalt folder, and create a docker compose config file:
+ ```sh
+ cd cobalt && nano docker-compose.yml
+ ```
+ i'm using `nano` in this example, it may not be available in your distro. you can use any other text editor.
+
+3. copy and paste the [sample config from here](https://github.com/wukko/cobalt/blob/current/docs/examples/docker-compose.example.yml) for either web or api instance (or both, if you wish) and edit it to your needs.
+ make sure to replace default URLs with your own or cobalt won't work correctly.
+
+4. finally, start the cobalt container (from cobalt directory):
+ ```sh
+ docker compose up -d
+ ```
+
+if you want your instance to support services that require authentication to view public content, create `cookies.json` file in the same directory as `docker-compose.yml`. example cookies file [can be found here](https://github.com/wukko/cobalt/blob/current/docs/examples/cookies.example.json).
+
+cobalt package will update automatically thanks to watchtower.
+
+it's highly recommended to use a reverse proxy (such as nginx) if you want your instance to face the public internet. look up tutorials online.
+
+## using regular node.js (useful for local development)
+setup script installs all needed `npm` dependencies, but you have to install `node.js` *(version 18 or above)* and `git` yourself.
+
+1. clone the repo: `git clone https://github.com/wukko/cobalt`.
+2. run setup script and follow instructions: `npm run setup`. you need to host api and web instances separately, so pick whichever applies.
+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
+```
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 00000000..8241ef98
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,33 @@
+# self-troubleshooting cobalt
+```
+🚧 this page is work-in-progress. expect more guides to be added in the future!
+```
+if any issues occur while using cobalt, you can fix many of them yourself. this document aims to provide guides on how to fix most complicated of them.
+use wiki navigation on right to jump between solutions.
+
+## how to fix clipboard pasting in firefox
+you can fix this issue by changing a single preference in `about:config`.
+
+### steps to enable clipboard functionality
+1. go to `about:config`:
+
+ ![screenshot showing about:config entered into address bar](https://github.com/wukko/cobalt/assets/71202418/9ad78612-a372-4949-aeac-99dfc41e273c)
+
+2. if asked, read what firefox has to say and press "accept the risk and continue".
+ ⚠ tinkering with other preferences may break your browser. **do not** edit them unless you know what you're doing.
+
+ ![screenshot showing about:config security warning that reads: "proceed with caution. changing advanced configuration preferences can impact firefox performance or security." lower there's a pre-checked checkbox that says: "warn me when i attempt to access these preferences". lowest element is a blue button that says "accept the risk and continue"](https://github.com/wukko/cobalt/assets/71202418/02328729-dbfe-4ea4-b2ca-7bcf1998c2ca)
+
+3. search for `dom.events.asyncClipboard.readText`
+
+ ![screenshot showing "dom.events.asyncclipboard.readtext" entered into search on about:config page](https://github.com/wukko/cobalt/assets/71202418/7c7f7e3c-6a6a-40df-8436-277489e72e0b)
+
+4. press the toggle button on very right.
+
+ ![screenshot showing "dom.events.asyncclipboard.readtext" preference on about:config page with highlighted toggle button on very right](https://github.com/wukko/cobalt/assets/71202418/b45db18e-f4bf-4f1c-9a8c-f13a63a21335)
+
+5. "false" should change to "true".
+
+ ![screenshot showing "dom.events.asyncclipboard.readtext" preference on about:config page, this one with "true" text highlighted](https://github.com/wukko/cobalt/assets/71202418/4869b4ff-8385-4cd3-ae59-aa2e03a58b5f)
+
+6. go back to cobalt, reload the page, press `paste and download` button again. this time it works! enjoy simpler downloading experience :)
diff --git a/jsconfig.json b/jsconfig.json
deleted file mode 100644
index 3a840ef5..00000000
--- a/jsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "module": "ESNext",
- "moduleResolution": "Node",
- "target": "ES2020",
- "strictNullChecks": true,
- "strictFunctionTypes": true
- },
- "exclude": [
- "node_modules",
- "**/node_modules/*"
- ]
-}
diff --git a/package.json b/package.json
index 6b1f672b..ab8a4f0c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
- "version": "7.5.1",
+ "version": "7.9.5",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
@@ -12,7 +12,8 @@
"start": "node src/cobalt",
"setup": "node src/modules/setup",
"test": "node src/test/test",
- "build": "node src/modules/buildStatic"
+ "build": "node src/modules/buildStatic",
+ "testFilenames": "node src/test/testFilenamePresets"
},
"repository": {
"type": "git",
@@ -24,6 +25,8 @@
},
"homepage": "https://github.com/wukko/cobalt#readme",
"dependencies": {
+ "abort-controller": "3.0.0",
+ "content-disposition-header": "0.6.0",
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"esbuild": "^0.14.51",
@@ -33,9 +36,10 @@
"hls-parser": "^0.10.7",
"nanoid": "^4.0.2",
"node-cache": "^5.1.2",
+ "psl": "1.9.0",
"set-cookie-parser": "2.6.0",
"undici": "^5.19.1",
"url-pattern": "1.0.3",
- "youtubei.js": "^5.4.0"
+ "youtubei.js": "^6.4.1"
}
}
diff --git a/src/cobalt.js b/src/cobalt.js
index 949cccba..2d90e07e 100644
--- a/src/cobalt.js
+++ b/src/cobalt.js
@@ -21,8 +21,8 @@ app.disable('x-powered-by');
await loadLoc();
-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 apiMode = process.env.apiURL && !process.env.webURL;
+const webMode = process.env.webURL && process.env.apiURL;
if (apiMode) {
const { runAPI } = await import('./core/api.js');
@@ -31,5 +31,9 @@ if (apiMode) {
const { runWeb } = await import('./core/web.js');
await runWeb(express, app, gitCommit, gitBranch, __dirname)
} else {
- console.log(Red(`cobalt wasn't configured yet or configuration is invalid.\n`) + Bright(`please run the setup script to fix this: `) + Green(`npm run setup`))
+ 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`)
+ )
}
diff --git a/src/config.json b/src/config.json
index 10e3286d..ae0c16fc 100644
--- a/src/config.json
+++ b/src/config.json
@@ -1,6 +1,6 @@
{
- "streamLifespan": 20000,
- "maxVideoDuration": 18000000,
+ "streamLifespan": 90000,
+ "maxVideoDuration": 10800000,
"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",
@@ -8,37 +8,55 @@
"contact": "https://wukko.me/contacts",
"support": {
"default": {
+ "email": {
+ "emoji": "📧",
+ "url": "mailto:support@cobalt.tools",
+ "name": "support@cobalt.tools"
+ },
"twitter": {
"emoji": "🐦",
"url": "https://twitter.com/justusecobalt",
- "handle": "@justusecobalt"
- },
- "mastodon": {
- "emoji": "🐘",
- "url": "https://wetdry.world/@cobalt",
- "handle": "@cobalt@wetdry.world"
+ "name": "@justusecobalt"
},
"discord": {
"emoji": "👾",
"url": "https://discord.gg/pQPt8HBUPu",
- "handle": "cobalt community server"
+ "name": "cobalt discord server"
+ }
+ },
+ "ru": {
+ "telegram": {
+ "emoji": "📬",
+ "url": "https://t.me/justusecobalt_ru",
+ "name": "канал в telegram"
+ },
+ "email": {
+ "emoji": "📧",
+ "url": "mailto:support@cobalt.tools",
+ "name": "support@cobalt.tools"
}
}
}
},
"donations": {
"crypto": {
- "bitcoin": "bc1q59jyyjvrzj4c22rkk3ljeecq6jmpyscgz9spnd",
- "ethereum": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
+ "monero": "4B1SNB6s8Pq1hxjNeKPEe8Qa8EP3zdL16Sqsa7QDoJcUecKQzEj9BMxWnEnTGu12doKLJBKRDUqnn6V9qfSdXpXi3Nw5Uod",
"litecoin": "ltc1qvp0xhrk2m7pa6p6z844qcslfyxv4p3vf95rhna",
- "monero": "4B1SNB6s8Pq1hxjNeKPEe8Qa8EP3zdL16Sqsa7QDoJcUecKQzEj9BMxWnEnTGu12doKLJBKRDUqnn6V9qfSdXpXi3Nw5Uod"
+ "ethereum": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
+ "usdt-erc20": "0x4B4cF23051c78c7A7E0eA09d39099621c46bc302",
+ "usdt-trc20": "TVbx7YT3rBfu931Gxko6pRfXtedYqbgnBB",
+ "bitcoin": "bc1qlvcnlnyzfsgnuxyxsv3k0p0q0yln0azjpadyx4",
+ "bitcoin-alt": "18PKf6N2cHrmSzz9ZzTSvDd2jAkqGC7SxA",
+ "ton": "UQA3SO-hHZq1oCCT--u6or6ollB8fd2o52aD8mXiLk9iDZd3"
},
"links": {
"boosty": "https://boosty.to/wukko/donate"
}
},
"links": {
- "saveToGalleryShortcut": "https://www.icloud.com/shortcuts/b401917928fd407daf1db0fd07eb7e78"
+ "saveToGalleryShortcut": "https://www.icloud.com/shortcuts/b401917928fd407daf1db0fd07eb7e78",
+ "statusPage": "https://status.cobalt.tools/",
+ "troubleshootingGuide": "https://github.com/wukko/cobalt/blob/current/docs/troubleshooting.md"
},
"celebrations": {
"01-01": "🎄",
@@ -73,6 +91,17 @@
"mp4": ["-c:v", "copy", "-c:a", "copy", "-movflags", "faststart+frag_keyframe+empty_moov"],
"copy": ["-c:a", "copy"],
"audio": ["-ar", "48000", "-ac", "2", "-b:a", "320k"],
- "m4a": ["-movflags", "frag_keyframe+empty_moov"]
- }
+ "m4a": ["-movflags", "frag_keyframe+empty_moov"],
+ "gif": ["-vf", "scale=-1:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", "-loop", "0"]
+ },
+ "sponsors": [{
+ "name": "royale",
+ "fullName": "RoyaleHosting",
+ "url": "https://royalehosting.net/",
+ "logo": {
+ "width": 605,
+ "height": 136,
+ "scale": 5
+ }
+ }]
}
diff --git a/src/core/api.js b/src/core/api.js
index 84464b56..71ce8d3e 100644
--- a/src/core/api.js
+++ b/src/core/api.js
@@ -97,7 +97,7 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
let chck = checkJSONPost(request);
if (!chck) throw new Error();
- j = await getJSON(chck["url"], lang, chck);
+ j = await getJSON(chck.url, lang, chck);
} else {
j = apiJSON(0, {
t: !contentCon ? "invalid content type header" : loc(lang, 'ErrorNoLink')
@@ -139,9 +139,9 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
version: version,
commit: gitCommit,
branch: gitBranch,
- name: process.env.apiName ? process.env.apiName : "unknown",
+ name: process.env.apiName || "unknown",
url: process.env.apiURL,
- cors: process.env.cors && process.env.cors === "0" ? 0 : 1,
+ cors: process.env?.cors === "0" ? 0 : 1,
startTime: `${startTimestamp}`
});
default:
@@ -167,12 +167,12 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
res.redirect('/api/json')
});
- app.listen(process.env.apiPort, () => {
+ app.listen(process.env.apiPort || 9000, () => {
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`
+ `Port: ${process.env.apiPort || 9000}\n`
)
});
}
diff --git a/src/core/web.js b/src/core/web.js
index c2512c1f..08a6ffed 100644
--- a/src/core/web.js
+++ b/src/core/web.js
@@ -76,12 +76,12 @@ export async function runWeb(express, app, gitCommit, gitBranch, __dirname) {
return res.redirect('/')
});
- app.listen(process.env.webPort, () => {
+ app.listen(process.env.webPort || 9001, () => {
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`
+ `Port: ${process.env.webPort || 9001}\n`
)
})
}
diff --git a/src/front/cobalt.css b/src/front/cobalt.css
index e0e5322e..ff4f50d6 100644
--- a/src/front/cobalt.css
+++ b/src/front/cobalt.css
@@ -9,6 +9,7 @@
--padding-1: 0.75rem;
--line-height: 1.65rem;
--red: rgb(249, 47, 96);
+ --blue: rgb(47, 138, 249);
--gap: 0.5rem;
--gap-no-icon: 0.6rem;
}
@@ -34,13 +35,13 @@
--accent: rgb(25, 25, 25);
--accent-highlight: rgb(25, 25, 25, 4%);
--accent-subtext: rgb(110, 110, 110);
- --accent-hover: rgb(230, 230, 230);
- --accent-hover-elevated: rgb(215, 215, 215);
+ --accent-hover: rgb(225, 225, 225);
+ --accent-hover-elevated: rgb(210, 210, 210);
--accent-hover-transparent: rgba(215, 215, 215, 0.5);
- --accent-button: rgb(225, 225, 225);
- --accent-button-elevated: rgb(210, 210, 210);
- --glass: rgba(230, 230, 230, 0.85);
- --glass-lite: rgba(230, 230, 230, 0.98);
+ --accent-button: rgb(232, 232, 232);
+ --accent-button-elevated: rgb(215, 215, 215);
+ --glass: rgba(232, 232, 232, 0.85);
+ --glass-lite: rgba(232, 232, 232, 0.98);
--subbackground: rgb(240, 240, 240);
--background: rgb(255, 255, 255);
--background-backdrop: rgba(255, 255, 255, 0.5);
@@ -65,13 +66,13 @@
--accent: rgb(25, 25, 25);
--accent-highlight: rgb(25, 25, 25, 4%);
--accent-subtext: rgb(110, 110, 110);
- --accent-hover: rgb(230, 230, 230);
- --accent-hover-elevated: rgb(215, 215, 215);
- --accent-hover-transparent: rgba(219, 219, 219, 0.5);
- --accent-button: rgb(225, 225, 225);
- --accent-button-elevated: rgb(210, 210, 210);
- --glass: rgba(230, 230, 230, 0.85);
- --glass-lite: rgba(230, 230, 230, 0.98);
+ --accent-hover: rgb(225, 225, 225);
+ --accent-hover-elevated: rgb(210, 210, 210);
+ --accent-hover-transparent: rgba(215, 215, 215, 0.5);
+ --accent-button: rgb(232, 232, 232);
+ --accent-button-elevated: rgb(215, 215, 215);
+ --glass: rgba(232, 232, 232, 0.85);
+ --glass-lite: rgba(232, 232, 232, 0.98);
--subbackground: rgb(240, 240, 240);
--background: rgb(255, 255, 255);
--background-backdrop: rgba(255, 255, 255, 0.5);
@@ -106,7 +107,7 @@ a {
color: var(--accent-subtext);
}
.switches::-webkit-scrollbar,
-#popup-content::-webkit-scrollbar {
+.popup-content::-webkit-scrollbar {
display: none;
}
:focus-visible {
@@ -253,19 +254,25 @@ button:active,
}
#cobalt-main-box {
position: fixed;
- width: 60%;
+ width: 40rem;
height: auto;
display: flex;
- flex-direction: row;
+ flex-direction: column;
+ align-content: center;
+ align-items: center;
}
#logo {
text-align: left;
font-size: 1rem;
white-space: nowrap;
- width: 7rem;
height: 2.5rem;
align-items: center;
display: flex;
+ gap: 0.3rem;
+}
+.logo-sub {
+ color: var(--blue);
+ font-size: 0.8rem;
}
#download-area {
display: flex;
@@ -289,7 +296,7 @@ button:active,
}
#url-input-area {
background: none;
- padding: 0 1rem;
+ padding-left: calc(20px + 1.4rem);
width: 100%;
color: var(--accent);
border: 0;
@@ -310,20 +317,34 @@ button:active,
outline: none;
border-bottom: var(--border-10);
}
+#link-icon {
+ display: flex;
+ position: absolute;
+ width: 20px;
+ padding-top: 0.2rem;
+ left: 0.7rem;
+ flex-wrap: nowrap;
+ color: var(--accent-subtext);
+}
#download-button {
height: 2.5rem;
color: var(--accent);
background: none;
border: none;
- font-size: 1.6rem;
+ font-size: 1.8rem;
cursor: pointer;
padding: 0;
- letter-spacing: -0.36rem;
+ letter-spacing: -0.35rem;
+ font-weight: normal!important;
}
#download-button:disabled {
color: var(--accent-subtext);
cursor: not-allowed;
}
+#cobalt-main-box .switch,
+#footer .switch {
+ box-shadow: 0 0 0 0.1rem var(--accent-highlight) inset;
+}
#footer {
bottom: 0;
width: 100%;
@@ -429,31 +450,28 @@ button:active,
.popup.small.visible {
transform: translate(-50%, -50%);
}
-.popup.small #popup-header-contents,
+.popup.small .popup-header-contents,
.popup.small .popup-content-inner,
-.popup.small #popup-header {
+.popup.small .popup-header {
padding: 0;
}
-.popup.small #popup-header {
+.popup.small .popup-header {
position: relative;
border: none;
}
-.popup.small #popup-title {
+.popup.small .popup-title {
margin-bottom: 0.6rem;
}
.popup.small .explanation {
margin-bottom: 0.9rem;
}
-#close-error {
- background: var(--accent);
+.popup.small .close-error.switch {
+ background: var(--accent)!important;
color: var(--background);
}
.popup.scrollable {
height: 95%;
}
-.scrollable .bottom-link {
- padding-bottom: 2rem;
-}
.changelog-subtitle {
font-size: 1.3rem;
padding-bottom: var(--gap-no-icon);
@@ -502,7 +520,7 @@ button:active,
font-size: 1.1rem;
padding-bottom: var(--padding-1);
}
-#popup-desc,
+.popup-desc,
.desc-error,
#popup-info-desc {
width: 100%;
@@ -515,7 +533,7 @@ button:active,
.desc-error {
padding-bottom: 1.5rem;
}
-#popup-title {
+.popup-title {
font-size: 1.5rem;
line-height: 1.2em;
display: flex;
@@ -523,11 +541,11 @@ button:active,
margin-bottom: 0.4rem;
margin-top: 0.4rem;
}
-#popup-above-title {
+.popup-above-title {
color: var(--accent-subtext);
font-size: 0.8rem;
}
-#popup-content {
+.popup-content {
overflow-x: scroll;
overflow-y: auto;
height: 100%;
@@ -546,7 +564,7 @@ button:active,
.bullpadding {
padding-left: 0.58rem;
}
-#popup-header {
+.popup-header {
position: absolute;
z-index: 999;
padding-top: calc(env(safe-area-inset-top)/2 + 1.7rem);
@@ -628,16 +646,16 @@ button:active,
.switch:focus {
box-shadow: var(--inset-focus) inset;
}
-#popup-tabs .switch {
+.popup-tabs .switch {
background: none;
}
-.desktop #popup-tabs .switch:hover,
-#popup-tabs .switch:active {
+.desktop .popup-tabs .switch:hover,
+.popup-tabs .switch:active {
background: var(--accent-hover-transparent);
box-shadow: 0 0 0 0.1rem var(--accent-highlight) inset;
}
.switch[data-enabled="true"],
-#popup-tabs .switch[data-enabled="true"] {
+.popup-tabs .switch[data-enabled="true"] {
color: var(--background);
background: var(--accent)!important;
cursor: default;
@@ -675,20 +693,20 @@ button:active,
padding: var(--gap-no-icon);
overflow: clip;
}
-#back-button {
+.back-button {
padding: 0;
background: none;
max-width: 4rem;
font-size: 1rem;
}
-#back-button svg path,
+.back-button svg path,
.collapse-indicator svg path {
fill: var(--accent);
}
.popup-tab-content[data-enabled="false"] {
display: none;
}
-#popup-tabs {
+.popup-tabs {
z-index: 999;
bottom: 0;
position: absolute;
@@ -725,11 +743,13 @@ button:active,
}
#picker-holder {
display: flex;
- justify-content: space-between;
+ justify-content: start;
flex-wrap: wrap;
align-content: space-around;
padding-top: calc(env(safe-area-inset-top)/2 + 7.6rem);
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 4.8rem);
+ padding-left: 0.2rem;
+ padding-right: 0.2rem;
}
.imageBlock {
width: 100%;
@@ -759,9 +779,6 @@ button:active,
user-select: none;
-webkit-user-select: none;
}
-.collapse-list.last {
- margin-bottom: 1rem;
-}
.collapse-header {
padding: 0.5rem var(--padding-1);
font-size: 0.95rem;
@@ -793,6 +810,7 @@ button:active,
.collapse-body {
display: none;
padding: var(--padding-1);
+ padding-bottom: 1rem;
user-select: text;
-webkit-user-select: text;
}
@@ -805,13 +823,9 @@ button:active,
#pd-share {
display: none;
}
-#about-donate-footer {
- box-shadow: 0 0 0 0.1rem var(--red) inset, 0 0 0.6rem 0 var(--red);
- z-index: 1;
-}
.popup-content-inner,
.tab-content-settings,
-#popup-header-contents {
+.popup-header-contents {
padding-left: 1rem;
padding-right: 1rem;
}
@@ -888,64 +902,111 @@ button:active,
opacity: 1;
transition: opacity 0.2s ease-out;
}
+.no-animation #home {
+ transition: none;
+}
+.sponsored-by-text {
+ text-align: center!important;
+ font-size: .85rem;
+ color: var(--accent-subtext);
+ user-select: none;
+}
+#sponsored-logos {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 0.2rem 1rem;
+ margin-bottom: 1rem;
+}
+.sponsored-logo svg {
+ height: inherit;
+ width: inherit;
+}
+.sponsored-logo svg path {
+ fill: var(--accent-subtext);
+}
+#filename-preview {
+ background: var(--accent-button);
+ margin-top: 0.8rem;
+}
+.filename-item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 1rem;
+ padding: 0.5rem 0.7rem;
+}
+.filename-item.line {
+ border-bottom: 0.1rem solid var(--accent-button-elevated);
+}
+.filename-label {
+ color: var(--accent-subtext);
+ font-size: 0.8rem;
+}
+.filename-container {
+ overflow-wrap: anywhere;
+}
/* rounded corners */
#bottom #paste,
#footer .switch,
#audioMode,
-#popup-content .switches,
+.popup-content .switches,
.checkbox,
.changelog-img,
.changelog-banner,
-#close-error,
+.close-error,
.changelog-tag-version,
#download-switcher .switch,
#popup-about .switch,
-#popup-tabs .switch,
+.popup-tabs .switch,
.text-to-copy,
-.text-to-copy.text-backdrop {
- border-radius: 5px / 6px;
+.text-to-copy.text-backdrop,
+#filename-preview {
+ border-radius: 6px / 7px;
}
[type=checkbox] {
border-radius: 3px / 4px;
}
.popup,
-.scrollable #popup-content {
- border-radius: 8px / 9px;
+.scrollable .popup-content {
+ border-radius: 8px;
}
-#popup-header .glass-bkg {
+.popup-header .glass-bkg {
border-top-left-radius: 8px 9px;
border-top-right-radius: 8px 9px;
border-bottom: var(--accent-highlight) solid 0.1rem;
top: -1px;
}
-#popup-tabs .glass-bkg {
+.popup-tabs .glass-bkg {
border-bottom-left-radius: 8px 9px;
border-bottom-right-radius: 8px 9px;
border-top: var(--accent-highlight) solid 0.1rem;
bottom: -1px;
}
-.switches .first {
- border-top-left-radius: 5px 6px;
- border-bottom-left-radius: 5px 6px;
+.switches :first-child {
+ border-top-left-radius: 6px 7px;
+ border-bottom-left-radius: 6px 7px;
}
-.switches .last {
- border-top-right-radius: 5px 6px;
- border-bottom-right-radius: 5px 6px;
+.switches :last-child {
+ border-top-right-radius: 6px 7px;
+ border-bottom-right-radius: 6px 7px;
}
.text-backdrop {
border-radius: 3px / 4px;
}
-.collapse-list.first,
-.collapse-list.first .collapse-header {
- border-top-left-radius: 6px 7px;
- border-top-right-radius: 6px 7px;
+.collapse-list:first-child,
+.collapse-list:first-child .collapse-header {
+ border-top-left-radius: 7px 8px;
+ border-top-right-radius: 7px 8px;
}
-.collapse-list.last,
-.collapse-list.last .collapse-header {
- border-bottom-left-radius: 6px 7px;
- border-bottom-right-radius: 6px 7px;
+.collapse-list:last-child,
+.collapse-list:last-child .collapse-header {
+ border-bottom-left-radius: 7px 8px;
+ border-bottom-right-radius: 7px 8px;
}
-.collapse-list.last.expanded .collapse-header {
+.collapse-list:last-child.expanded .collapse-header {
border-radius: 0;
}
/* prevent resizing fliecker on ios if web app is installed as standalone */
@@ -964,9 +1025,6 @@ button:active,
}
}
@media screen and (max-width: 1440px) {
- #cobalt-main-box {
- width: 65%;
- }
.popup.small {
width: 30%
}
@@ -980,9 +1038,6 @@ button:active,
}
}
@media screen and (max-width: 1200px) {
- #cobalt-main-box {
- width: 70%;
- }
.popup.small {
width: 35%
}
@@ -991,9 +1046,6 @@ button:active,
}
}
@media screen and (max-width: 1025px) {
- #cobalt-main-box {
- width: 75%;
- }
.popup.small {
width: 40%
}
@@ -1018,14 +1070,14 @@ button:active,
width: calc(100% - 1.3rem);
}
}
-@media screen and (max-width: 720px) {
+@media screen and (max-width: 660px) {
#cobalt-main-box {
width: calc(100% - (0.7rem * 2));
}
#cobalt-main-box #bottom {
- flex-direction: column-reverse;
+ flex-direction: row-reverse;
}
- #cobalt-main-box #bottom button {
+ #cobalt-main-box #bottom #audioMode button, #audioMode {
width: 100%;
}
#footer {
@@ -1056,12 +1108,12 @@ button:active,
padding-top: calc(env(safe-area-inset-bottom)/2 + 1rem);
}
.popup,
- #popup-header .glass-bkg,
- #popup-tabs .glass-bkg,
+ .popup-header .glass-bkg,
+ .popup-tabs .glass-bkg,
.glass-bkg.small {
border-radius: 0;
}
- #popup-tabs .glass-bkg {
+ .popup-tabs .glass-bkg {
bottom: 0;
}
.switches {
@@ -1092,17 +1144,21 @@ button:active,
}
.popup.small.visible {
transform: none;
- transition: transform 200ms cubic-bezier(0.075, 0.82, 0.165, 1), opacity 130ms ease-in-out;
+ transition: transform 210ms cubic-bezier(0.062, 0.82, 0.165, 1), opacity 130ms ease-in-out;
}
- .popup.small #popup-header {
+ .popup.small .popup-header {
background: none;
}
.no-animation .popup.small {
transition: none;
}
- #close-error {
+ .close-error {
bottom: 3rem;
}
+ #picker-holder {
+ padding-left: 0;
+ padding-right: 0;
+ }
#picker-holder::-webkit-scrollbar {
display: none;
}
@@ -1119,22 +1175,14 @@ button:active,
max-height: 100%;
box-shadow: none;
}
- #popup-tabs {
+ .popup-tabs {
padding-bottom: calc(env(safe-area-inset-bottom)/2 + 1.5rem);
}
- .bottom-link {
- padding-bottom: 2rem;
- }
.popup-content-inner,
.tab-content-settings,
.popup-tabs-child,
- #popup-header-contents {
+ .popup-header-contents {
padding-left: 0.7rem;
padding-right: 0.7rem;
}
}
-@media screen and (max-width: 400px) {
- .popup-title {
- line-height: inherit;
- }
-}
\ No newline at end of file
diff --git a/src/front/cobalt.js b/src/front/cobalt.js
index 947d7256..cdf143bc 100644
--- a/src/front/cobalt.js
+++ b/src/front/cobalt.js
@@ -1,4 +1,4 @@
-const version = 37;
+const version = 41;
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
@@ -17,7 +17,8 @@ const switchers = {
"aFormat": ["mp3", "best", "ogg", "wav", "opus"],
"dubLang": ["original", "auto"],
"vimeoDash": ["false", "true"],
- "audioMode": ["false", "true"]
+ "audioMode": ["false", "true"],
+ "filenamePattern": ["classic", "pretty", "basic", "nerdy"]
};
const checkboxes = [
"alwaysVisibleButton",
@@ -29,18 +30,25 @@ const checkboxes = [
"reduceTransparency",
"disableAnimations",
"disableMetadata",
+ "twitterGif",
];
const exceptions = { // used for mobile devices
"vQuality": "720"
};
-const bottomPopups = ["error", "download"]
+const bottomPopups = ["error", "download"];
const pageQuery = new URLSearchParams(window.location.search);
let store = {};
-function changeAPI(url) {
- apiURL = url;
+function fixApiUrl(url) {
+ return url.endsWith('/') ? url.slice(0, -1) : url
+}
+
+let apiURL = fixApiUrl(defaultApiUrl);
+
+function changeApi(url) {
+ apiURL = fixApiUrl(url);
return true
}
function eid(id) {
@@ -127,6 +135,8 @@ function detectColorScheme() {
document.documentElement.setAttribute("data-theme", theme);
}
function changeTab(evnt, tabId, tabClass) {
+ if (tabId === "tab-settings-other") updateFilenamePreview();
+
let tabcontent = document.getElementsByClassName(`tab-content-${tabClass}`);
let tablinks = document.getElementsByClassName(`tab-${tabClass}`);
@@ -194,7 +204,7 @@ function popup(type, action, text) {
store.isPopupOpen = true;
switch (type) {
case "about":
- let tabId = sGet("seenAbout") ? "changelog" : "about";
+ let tabId = sGet("changelogStatus") !== `${version}` ? "changelog" : "about";
if (text) tabId = text;
eid(`tab-button-${type}-${tabId}`).click();
break;
@@ -211,11 +221,11 @@ function popup(type, action, text) {
if (navigator.canShare) eid("pd-share").style.display = "flex";
break;
case "picker":
+ eid("picker-title").innerHTML = loc.MediaPickerTitle;
+ eid("picker-subtitle").innerHTML = isMobile ? loc.MediaPickerExplanationPhone : loc.MediaPickerExplanationPC;
+
switch (text.type) {
case "images":
- 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;
@@ -226,14 +236,11 @@ function popup(type, action, text) {
`` +
- `` +
+ `` +
``
}
break;
default:
- 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) {
@@ -243,7 +250,7 @@ function popup(type, action, text) {
}>` +
`