diff --git a/README.md b/README.md index e37752e2..0964d5dd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # cobalt Best way to save what you love. -Live web app: [co.wukko.me](https://co.wukko.me/) +Live web app: [cobalt.tools](https://cobalt.tools/) ![cobalt logo with repeated logo pattern background](https://raw.githubusercontent.com/wukko/cobalt/current/src/front/icons/pattern.png "cobalt logo with repeated logo pattern background") diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 5900b327..678633e3 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -2,7 +2,7 @@ version: '3.5' services: cobalt-api: - build: . + image: ghcr.io/wukko/cobalt:latest restart: unless-stopped container_name: cobalt-api @@ -24,8 +24,11 @@ services: #- cookiePath=cookies.json # see src/modules/processing/cookie/cookies_example.json for example file. + labels: + - com.centurylinklabs.watchtower.scope=cobalt + cobalt-web: - build: . + image: ghcr.io/wukko/cobalt:latest restart: unless-stopped container_name: cobalt-web @@ -40,6 +43,17 @@ services: environment: - webPort=9001 # replace webURL with your instance's target url in same format - - webURL=https://co.wukko.me/ + - webURL=https://cobalt.tools/ # replace apiURL with preferred api instance url - apiURL=https://co.wuk.sh/ + + labels: + - com.centurylinklabs.watchtower.scope=cobalt + + # update the cobalt image automatically with watchtower + watchtower: + image: ghcr.io/containrrr/watchtower + restart: unless-stopped + command: --cleanup --scope cobalt --interval 900 + volumes: + - /var/run/docker.sock:/var/run/docker.sock \ No newline at end of file diff --git a/docs/API.md b/docs/API.md index 90351e27..9957d517 100644 --- a/docs/API.md +++ b/docs/API.md @@ -4,8 +4,7 @@ This document provides info about methods and acceptable variables for all cobal ``` ⚠️ Main API instance has moved to https://co.wuk.sh/ -Previous API domain will stop redirecting users to correct API instance after July 25th. -Make sure to update your projects in time. +Make sure your projects use the correct API domain. ``` ## POST: ``/api/json`` @@ -15,18 +14,18 @@ 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 & Douyin videos have watermarks. | -| isTTFullAudio | boolean | ``true / false`` | ``false`` | Enables download of original sound used in a TikTok video. | -| isAudioMuted | boolean | ``true / false`` | ``false`` | Disables audio track in video downloads. | -| dubLang | boolean | ``true / false`` | ``false`` | Backend uses Accept-Language for YouTube video audio tracks when ``true``. | -| disableMetadata | boolean | ``true / false`` | ``false`` | Disables file metadata when set to ``true``. | +| key | type | variables | default | description | +|:--------------------|:--------|:----------------------------------|:----------|:-------------------------------------------------------------------------------| +| ``url`` | string | Sharable URL encoded as URI | ``null`` | **Must** be included in every request. | +| ``vCodec`` | string | ``h264 / av1 / vp9`` | ``h264`` | Applies only to YouTube downloads. ``h264`` is recommended for phones. | +| ``vQuality`` | string | ``144 / ... / 2160 / max`` | ``720`` | ``720`` quality is recommended for phones. | +| ``aFormat`` | string | ``best / mp3 / ogg / wav / opus`` | ``mp3`` | | +| ``isAudioOnly`` | boolean | ``true / false`` | ``false`` | | +| ``isNoTTWatermark`` | boolean | ``true / false`` | ``false`` | Changes whether downloaded TikTok videos have watermarks. | +| ``isTTFullAudio`` | boolean | ``true / false`` | ``false`` | Enables download of original sound used in a TikTok video. | +| ``isAudioMuted`` | boolean | ``true / false`` | ``false`` | Disables audio track in video downloads. | +| ``dubLang`` | boolean | ``true / false`` | ``false`` | Backend uses Accept-Language for YouTube video audio tracks when ``true``. | +| ``disableMetadata`` | boolean | ``true / false`` | ``false`` | Disables file metadata when set to ``true``. | ### Response Body Variables | key | type | variables | diff --git a/package.json b/package.json index 2bd5d679..304fa9eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "7.3.1", + "version": "7.4", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index e66cabc4..3c838ef2 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -503,7 +503,7 @@ button:active, padding-bottom: var(--padding-1); } #popup-desc, -#desc-error, +.desc-error, #popup-info-desc { width: 100%; text-align: left; @@ -512,7 +512,7 @@ button:active, user-select: text; -webkit-user-select: text; } -#desc-error { +.desc-error { padding-bottom: 1.5rem; } #popup-title { @@ -837,23 +837,6 @@ button:active, .no-animation #popup-backdrop { transition: none; } -#floating-notification-area { - visibility: visible; - z-index: 999999; - position: absolute; - display: flex; - justify-content: center; - width: 100%; - padding-top: 2rem; -} -.floating-notification { - text-align: center; - padding: 0.6rem 1.2rem; - background: var(--accent-hover-elevated); - display: flex; - box-shadow: 0 0 20px 10px var(--accent-hover); - font-size: 0.85rem; -} .popup-from-bottom { position: fixed; width: 100%; diff --git a/src/front/cobalt.js b/src/front/cobalt.js index c02ae3a4..f58d5554 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -1,3 +1,5 @@ +const version = 36; + const ua = navigator.userAgent.toLowerCase(); const isIOS = ua.match("iphone os"); const isMobile = ua.match("android") || ua.match("iphone os"); @@ -5,7 +7,6 @@ const isSafari = ua.match("safari/"); const isFirefox = ua.match("firefox/"); const isOldFirefox = ua.match("firefox/") && ua.split("firefox/")[1].split('.')[0] < 103; -const version = 35; 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 = `
`; @@ -19,18 +20,23 @@ const switchers = { "audioMode": ["false", "true"] }; const checkboxes = [ + "alwaysVisibleButton", + "disableChangelog", + "downloadPopup", "disableTikTokWatermark", "fullTikTokAudio", "muteAudio", "reduceTransparency", "disableAnimations", - "disableMetadata" + "disableMetadata", ]; const exceptions = { // used for mobile devices "vQuality": "720" }; const bottomPopups = ["error", "download"] +const pageQuery = new URLSearchParams(window.location.search); + let store = {}; function changeAPI(url) { @@ -207,8 +213,8 @@ function popup(type, action, text) { case "picker": switch (text.type) { case "images": - eid("picker-title").innerHTML = loc.pickerImages; - eid("picker-subtitle").innerHTML = loc.pickerImagesExpl; + eid("picker-title").innerHTML = loc.ImagePickerTitle; + eid("picker-subtitle").innerHTML = isMobile ? loc.ImagePickerExplanationPhone : loc.ImagePickerExplanationPC; eid("picker-holder").classList.remove("various"); @@ -225,8 +231,8 @@ function popup(type, action, text) { } break; default: - eid("picker-title").innerHTML = loc.pickerDefault; - eid("picker-subtitle").innerHTML = loc.pickerDefaultExpl; + eid("picker-title").innerHTML = loc.MediaPickerTitle; + eid("picker-subtitle").innerHTML = isMobile ? loc.MediaPickerExplanationPhone : loc.MediaPickerExplanationPC; eid("picker-holder").classList.add("various"); @@ -296,10 +302,10 @@ function loadSettings() { eid("downloadPopup").checked = true; } if (sGet("reduceTransparency") === "true" || isOldFirefox) { - eid("cobalt-body").classList.toggle('no-transparency'); + eid("cobalt-body").classList.add('no-transparency'); } if (sGet("disableAnimations") === "true") { - eid("cobalt-body").classList.toggle('no-animation'); + eid("cobalt-body").classList.add('no-animation'); } for (let i = 0; i < checkboxes.length; i++) { if (sGet(checkboxes[i]) === "true") eid(checkboxes[i]).checked = true; @@ -334,7 +340,7 @@ function internetError() { eid("url-input-area").disabled = false changeDownloadButton(2, '!!'); setTimeout(() => { changeButton(1); }, 2500); - popup("error", 1, loc.noInternet); + popup("error", 1, loc.ErrorNoInternet); } function resetSettings() { localStorage.clear(); @@ -348,13 +354,13 @@ async function pasteClipboard() { download(eid("url-input-area").value); } } catch (e) { - let errorMessage = loc.featureErrorGeneric; + let errorMessage = loc.FeatureErrorGeneric; let doError = true; let error = String(e).toLowerCase(); - if (error.includes("denied")) errorMessage = loc.clipboardErrorNoPermission; + if (error.includes("denied")) errorMessage = loc.ClipboardErrorNoPermission; if (error.includes("dismissed") || isIOS) doError = false; - if (error.includes("function") && isFirefox) errorMessage = loc.clipboardErrorFirefox; + if (error.includes("function") && isFirefox) errorMessage = loc.ClipboardErrorFirefox; if (doError) popup("error", 1, errorMessage); } @@ -401,7 +407,7 @@ async function download(url) { if (j.text && (!j.url || !j.picker)) { if (j.status === "success") { changeButton(2, j.text) - } else changeButton(0, loc.noURLReturned); + } else changeButton(0, loc.ErrorNoUrlReturned); } switch (j.status) { case "redirect": @@ -419,7 +425,7 @@ async function download(url) { popup('picker', 1, { arr: j.picker, type: j.pickerType }); setTimeout(() => { changeButton(1) }, 2500); } else { - changeButton(0, loc.noURLReturned); + changeButton(0, loc.ErrorNoUrlReturned); } break; case "stream": @@ -441,7 +447,7 @@ async function download(url) { changeButton(2, j.text); break; default: - changeButton(0, loc.unknownStatus); + changeButton(0, loc.ErrorUnknownStatus); break; } } else if (j && j.text) { @@ -476,7 +482,7 @@ async function loadOnDemand(elementId, blockId) { }).catch(() => { throw new Error() }); } if (j.text) { - eid(elementId).innerHTML = `${j.text}`; + eid(elementId).innerHTML = `${j.text}`; } else throw new Error() } catch (e) { eid(elementId).innerHTML = store.historyButton; @@ -486,26 +492,68 @@ async function loadOnDemand(elementId, blockId) { function restoreUpdateHistory() { eid("changelog-history").innerHTML = store.historyButton; } +function unpackSettings(b64) { + let changed = null; + try { + let settingsToImport = JSON.parse(atob(b64)); + let currentSettings = JSON.parse(JSON.stringify(localStorage)); + for (let s in settingsToImport) { + if (checkboxes.includes(s) && (settingsToImport[s] === "true" || settingsToImport[s] === "false") + && currentSettings[s] !== settingsToImport[s]) { + sSet(s, settingsToImport[s]); + changed = true + } + if (switchers[s] && switchers[s].includes(settingsToImport[s]) + && currentSettings[s] !== settingsToImport[s]) { + sSet(s, settingsToImport[s]); + changed = true + } + } + } catch (e) { + changed = false; + } + return changed +} window.onload = () => { + loadCelebrationsEmoji(); + loadSettings(); detectColorScheme(); + changeDownloadButton(0, '>>'); - notificationCheck(); - loadCelebrationsEmoji(); + eid("url-input-area").value = ""; + if (isIOS) { sSet("downloadPopup", "true"); eid("downloadPopup-chkbx").style.display = "none"; } - eid("url-input-area").value = ""; eid("home").style.visibility = 'visible'; eid("home").classList.toggle("visible"); - let urlQuery = new URLSearchParams(window.location.search).get("u"); - if (urlQuery !== null && regex.test(urlQuery)) { - eid("url-input-area").value = urlQuery; - button(); + if (pageQuery.has("u") && regex.test(pageQuery.get("u"))) { + eid("url-input-area").value = pageQuery.get("u"); + button() } + if (pageQuery.has("migration")) { + if (pageQuery.has("settingsData") && !sGet("migrated")) { + let setUn = unpackSettings(pageQuery.get("settingsData")); + if (setUn !== null) { + if (setUn) { + sSet("migrated", "true") + eid("desc-migration").innerHTML += `

${loc.DataTransferSuccess}` + } else { + eid("desc-migration").innerHTML += `

${loc.DataTransferError}` + } + } + } + loadSettings(); + detectColorScheme(); + popup("migration", 1); + } + window.history.replaceState(null, '', window.location.pathname); + + notificationCheck(); } eid("url-input-area").addEventListener("keydown", (e) => { button(); diff --git a/src/front/emoji/3d/cat_grin.svg b/src/front/emoji/3d/cat_grin.svg new file mode 100644 index 00000000..be6e29d0 --- /dev/null +++ b/src/front/emoji/3d/cat_grin.svg @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/front/emoji/cat_grin.svg b/src/front/emoji/cat_grin.svg new file mode 100644 index 00000000..4b7cbb06 --- /dev/null +++ b/src/front/emoji/cat_grin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/front/updateBanners/newdomain.webp b/src/front/updateBanners/newdomain.webp new file mode 100644 index 00000000..256784a2 Binary files /dev/null and b/src/front/updateBanners/newdomain.webp differ diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 3be93dbc..7025f222 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -138,6 +138,11 @@ "FairUse": "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.\n\ncobalt does not log any info about you, it's impossible for me to snitch on you, but please be mindful when using content of others and always credit original creators!\n\nwhen used in education purposes (lecture, homework, etc) please attach the source link.\n\nfair use and credits benefit everyone.", "UrgentFeatureUpdate71": "more supported services!", "UrgentThanks": "thank you for support!", - "SettingsDisableMetadata": "don't add metadata" + "SettingsDisableMetadata": "don't add metadata", + "UrgentNewDomain": "new domain, same cobalt", + "NewDomainWelcomeTitle": "hey there!", + "NewDomainWelcome": "cobalt is moving! same features, same owner, simply a more rememberable domain. and still no ads.\n\ncobalt.tools is the new main domain, aka where you are now. make sure to update your bookmarks and reinstall the web app!", + "DataTransferSuccess": "btw, your settings have been transferred automatically :)", + "DataTransferError": "something went wrong when transferring your preferences. you'll have to open settings and configure cobalt by hand." } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index 333305c2..1bb67579 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -139,6 +139,11 @@ "FairUse": "кобальт - это инструмент для облегчения скачивания контента из интернета, и он не несёт никакой ответственности. ты несёшь ответственность за то, что скачиваешь, как используешь и распространяешь скачанный контент.\n\nкобальт не собирает никакой информации о тебе, и не может донести на тебя, но, пожалуйста, будь сознателен при использовании чужого контента и всегда указывай авторов!\n\nпри использовании в образовательных целях (лекции, домашние задания и т.д.), пожалуйста, прикладывай ссылку на источник.\n\nчестное использование и указание авторства выгодно всем.", "UrgentFeatureUpdate71": "расширение поддержки сервисов!", "UrgentThanks": "спасибо за поддержку!", - "SettingsDisableMetadata": "не добавлять метаданные" + "SettingsDisableMetadata": "не добавлять метаданные", + "UrgentNewDomain": "новый домен, тот же кобальт", + "NewDomainWelcomeTitle": "привет!", + "NewDomainWelcome": "кобальт переезжает! те же функции, тот же владелец, просто более запоминающийся домен. по-прежнему без рекламы.\n\ncobalt.tools - новый основной домен, т.е. где ты сейчас находишься. не забудь обновить закладки и переустановить веб-приложение!", + "DataTransferSuccess": "кстати, твои настройки были перенесены автоматически :)", + "DataTransferError": "при переносе настроек что-то пошло не так. придётся зайти в настройки и настроить кобальт вручную." } } diff --git a/src/modules/changelog/changelog.json b/src/modules/changelog/changelog.json index c452ab9d..57363b3e 100644 --- a/src/modules/changelog/changelog.json +++ b/src/modules/changelog/changelog.json @@ -1,5 +1,16 @@ { "current": { + "version": "7.4", + "date": "September 9, 2023", + "title": "new domain, what's coming in future, bug fixes, and more!", + "banner": { + "file": "newdomain.webp", + "width": 960, + "height": 540 + }, + "content": "cobalt is finally moving to its own domain! many of you have been anticipating this, and many kept forgetting the link due to how cryptic it was.\n\nwell, worry no more - cobalt.tools is here.\n\nif you haven't yet, open co.wukko.me to transfer your settings here! no additional action from you is required. just open the old link and cobalt will do everything for you :)\n\nmake sure to update your bookmarks and reinstall the web app!\n\nhere's what domain change means:\n*; still no ads, same owner, same features, same reliability. just a way more rememberable link (it's literally two words).\n*; cobalt.tools makes it clear that cobalt is a tool and that it's \"cobalt\", not \"wukko\".\n*; i can host various versions of cobalt on subdomains without links looking awkward.\n*; i can host cobalt-related websites without polluting my personal domain's dns (such as crowdin).\n*; i stand by same privacy policies (and in fact am using the same exact server as before).\n\nthe domain change is required for the future of cobalt.\n\nhere's what's coming soon:\n*; support for many top-requested sites, such as (but not limited to) twitch and niconico.\n*; education version of cobalt, as often requested by students and educators.\n*; major localization system upgrade, allowing for simpler community contributions.\n*; region-specific versions with 100% translations and tweaks.\n*; native clients for desktop and mobile (not sure about this one, i'm no superman).\n*; ...and more!\n\nnow, here's what's new in 7.4:\n*; tabs in popups now scroll to top on tab bar tap.\n*; padding across web app was tuned.\n*; (obviously) a migration agent. soon will be used for importing and exporting settings.\n*; some minor clean ups in codebase.\n\nif you want to help cobalt achieve goals listed above, consider donating! donations are the only way i can keep cobalt ad-less, powerful, (basically) limitless, and also 100% free.\n\nin fact, donations have helped me grow cobalt more than i've ever anticipated. just imagine how much better it will be in a year.\n\ngo to donations down below to find ways to donate!\n\nthank you for reading through all of this. i hope you enjoy this update and have a great day :D" + }, + "history": [{ "version": "7.2 & 7.3", "date": "September 6, 2023", "title": "extended video length limit, metadata toggle, ui improvements, and more!", @@ -9,8 +20,7 @@ "height": 280 }, "content": "this update gives cobalt a sharp look in chromium browsers and makes it even more useful than before. check out the full changelog below!\n\nservice improvements:\n*; increased video length limit from 3 hours to 5 hours. feel free to download lectures you need :)\n*; you can now disable file metadata in settings.\n*; fixed a bug which previously caused some downloads to end up being 0 bytes.\n\nui improvements:\n*; fixed clickable area for urgent notice (text on top).\n*; fixed blurry header in chrome.\n*; fixed blurry tab bar in chrome.\n*; fixed blurry switches in chrome.\n*; fixed weirdly rounded corners in popups.\n*; fixed 1px gap on edges of various elements in popup in chrome.\n*; fixed overscrolling in other settings tab on ios.\n*; fixed unexpected button highlight effect on phones.\n*; removed outdated fixes for tiny screens.\n\nother improvements:\n*; cobalt web & api start faster than before, additional preparation functions aren't unexpectedly run anymore.\n*; cobalt is now available as a docker package. check it out on github.\n\nthank you for being here. i hope you have a great day :D" - }, - "history": [{ + }, { "version": "7.1", "date": "August 20, 2023", "title": "instagram, streamable, video metadata, and more!", diff --git a/src/modules/emoji.js b/src/modules/emoji.js index c019037a..068e6bdb 100644 --- a/src/modules/emoji.js +++ b/src/modules/emoji.js @@ -33,7 +33,8 @@ const names = { "🔗": "link", "⌨": "keyboard", "📑": "boring_document", - "🧮": "abacus" + "🧮": "abacus", + "😸": "cat_grin" } let sizing = { 18: 0.8, diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index a1940d5c..824814e6 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -238,3 +238,10 @@ export function keyboardShortcuts(arr) { return base; } +export function webLoc(t, arr) { + let base = ``; + for (let i = 0; i < arr.length; i++) { + base += `${arr[i]}:` + "`" + t(arr[i]) + "`" + `,` + } + return `{${base}};` +} diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index d73a8e08..006a8849 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -1,4 +1,4 @@ -import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, urgentNotice, keyboardShortcuts } from "./elements.js"; +import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, urgentNotice, keyboardShortcuts, webLoc } from "./elements.js"; import { services as s, authorInfo, version, repo, donations, supportedAudio } from "../config.js"; import { getCommitInfo } from "../sub/currentCommit.js"; import loc from "../../localization/manager.js"; @@ -75,7 +75,7 @@ export default function(obj) { - + ${multiPagePopup({ name: "about", @@ -452,7 +452,7 @@ export default function(obj) { padding: "no-margin" }]) }) - }], + }] })} ${popupWithBottomButtons({ name: "picker", @@ -492,21 +492,35 @@ export default function(obj) { buttonOnly: true, classes: ["small"], header: { - closeAria: t('AccessibilityGoBack'), title: t('TitlePopupError'), emoji: emoji("😿", 78, 1, 1), }, - body: `
`, + body: `
`, buttonText: t('ErrorPopupCloseButton') })} +