diff --git a/src/config.json b/src/config.json
index f1aa4a2a..0a32d220 100644
--- a/src/config.json
+++ b/src/config.json
@@ -3,9 +3,6 @@
"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",
- "link": "https://wukko.me/",
- "contact": "https://wukko.me/contacts",
"support": {
"default": {
"email": {
diff --git a/src/front/cobalt.js b/src/front/cobalt.js
index ad2e9e59..61b29f8e 100644
--- a/src/front/cobalt.js
+++ b/src/front/cobalt.js
@@ -1,5 +1,3 @@
-const version = 42;
-
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
const isMobile = ua.match("android") || ua.match("iphone os");
@@ -7,19 +5,14 @@ const isSafari = ua.match("safari/");
const isFirefox = ua.match("firefox/");
const isOldFirefox = ua.match("firefox/") && ua.split("firefox/")[1].split('.')[0] < 103;
-const regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/);
-const notification = ``;
-
const switchers = {
"theme": ["auto", "light", "dark"],
"vCodec": ["h264", "av1", "vp9"],
- "vQuality": ["1080", "max", "2160", "1440", "720", "480", "360"],
+ "vQuality": ["720", "max", "2160", "1440", "1080", "480", "360"],
"aFormat": ["mp3", "best", "ogg", "wav", "opus"],
- "dubLang": ["original", "auto"],
- "vimeoDash": ["false", "true"],
"audioMode": ["false", "true"],
"filenamePattern": ["classic", "pretty", "basic", "nerdy"]
-};
+}
const checkboxes = [
"alwaysVisibleButton",
"downloadPopup",
@@ -29,101 +22,129 @@ const checkboxes = [
"disableAnimations",
"disableMetadata",
"twitterGif",
- "plausible_ignore"
-];
-const exceptions = { // used for mobile devices
- "vQuality": "720"
-};
-const bottomPopups = ["error", "download"];
-
-const pageQuery = new URLSearchParams(window.location.search);
+ "plausible_ignore",
+ "ytDub",
+ "tiktokH265"
+]
+const bottomPopups = ["error", "download"]
let store = {};
-function fixApiUrl(url) {
+const validLink = (link) => {
+ try {
+ return /^https?:/i.test(new URL(link).protocol);
+ } catch {
+ return false
+ }
+}
+
+const fixApiUrl = (url) => {
return url.endsWith('/') ? url.slice(0, -1) : url
}
let apiURL = fixApiUrl(defaultApiUrl);
-function changeApi(url) {
+const changeApi = (url) => {
apiURL = fixApiUrl(url);
return true
}
-function eid(id) {
+
+const eid = (id) => {
return document.getElementById(id)
}
-function sGet(id) {
+const sGet = (id) =>{
return localStorage.getItem(id)
}
-function sSet(id, value) {
+const sSet = (id, value) => {
localStorage.setItem(id, value)
}
-function enable(id) {
+const enable = (id) => {
eid(id).dataset.enabled = "true";
}
-function disable(id) {
+const disable = (id) => {
eid(id).dataset.enabled = "false";
}
-function vis(state) {
- return (state === 1) ? "visible" : "hidden";
-}
-function opposite(state) {
+const opposite = (state) => {
return state === "true" ? "false" : "true";
}
-function changeDownloadButton(action, text) {
+
+const lazyGet = (key) => {
+ const value = sGet(key);
+ if (key in switchers) {
+ if (switchers[key][0] !== value)
+ return value;
+ } else if (checkboxes.includes(key)) {
+ if (value === 'true')
+ return true;
+ }
+}
+
+const changeDownloadButton = (action, text) => {
switch (action) {
- case 0:
+ case "hidden": // hidden, but only visible when alwaysVisibleButton is true
eid("download-button").disabled = true
if (sGet("alwaysVisibleButton") === "true") {
- eid("download-button").value = text
+ eid("download-button").value = '>>'
eid("download-button").style.padding = '0 1rem'
} else {
eid("download-button").value = ''
eid("download-button").style.padding = '0'
}
break;
- case 1:
- eid("download-button").disabled = false
- eid("download-button").value = text
- eid("download-button").style.padding = '0 1rem'
- break;
- case 2:
+ case "disabled":
eid("download-button").disabled = true
eid("download-button").value = text
eid("download-button").style.padding = '0 1rem'
break;
+ default:
+ eid("download-button").disabled = false
+ eid("download-button").value = '>>'
+ eid("download-button").style.padding = '0 1rem'
+ break;
}
}
-document.addEventListener("keydown", (event) => {
- if (event.key === "Tab") {
- eid("download-button").value = '>>'
- eid("download-button").style.padding = '0 1rem'
- }
-})
-function button() {
- let regexTest = regex.test(eid("url-input-area").value);
+
+const button = () => {
+ let regexTest = validLink(eid("url-input-area").value);
+
+ eid("url-clear").style.display = "none";
+
if ((eid("url-input-area").value).length > 0) {
eid("url-clear").style.display = "block";
- } else {
- eid("url-clear").style.display = "none";
}
- regexTest ? changeDownloadButton(1, '>>') : changeDownloadButton(0, '>>');
+
+ if (regexTest) {
+ changeDownloadButton()
+ } else {
+ changeDownloadButton("hidden")
+ }
}
-function clearInput() {
+
+const clearInput = () => {
eid("url-input-area").value = '';
button();
}
-function copy(id, data) {
- let e = document.getElementById(id);
- e.classList.add("text-backdrop");
- setTimeout(() => { e.classList.remove("text-backdrop") }, 600);
- data ? navigator.clipboard.writeText(data) : navigator.clipboard.writeText(e.innerText);
+
+const copy = (id, data) => {
+ let target = document.getElementById(id);
+ target.classList.add("text-backdrop");
+
+ setTimeout(() => {
+ target.classList.remove("text-backdrop")
+ }, 600);
+
+ if (data) {
+ navigator.clipboard.writeText(data)
+ } else {
+ navigator.clipboard.writeText(e.innerText)
+ }
}
-async function share(url) {
- try { await navigator.share({url: url}) } catch (e) {}
+
+const share = async(url) => {
+ try { await navigator.share({url: url}) } catch {}
}
-function detectColorScheme() {
+
+const detectColorScheme = () => {
let theme = "auto";
let localTheme = sGet("theme");
if (localTheme) {
@@ -133,7 +154,8 @@ function detectColorScheme() {
}
document.documentElement.setAttribute("data-theme", theme);
}
-function changeTab(evnt, tabId, tabClass) {
+
+const changeTab = (evnt, tabId, tabClass) => {
if (tabId === "tab-settings-other") updateFilenamePreview();
let tabcontent = document.getElementsByClassName(`tab-content-${tabClass}`);
@@ -149,46 +171,15 @@ function changeTab(evnt, tabId, tabClass) {
evnt.currentTarget.dataset.enabled = "true";
eid(tabId).dataset.enabled = "true";
eid(tabId).parentElement.scrollTop = 0;
-
- if (tabId === "tab-about-changelog" && sGet("changelogStatus") !== `${version}`) notificationCheck("changelog");
- if (tabId === "tab-about-about" && !sGet("seenAbout")) notificationCheck("about");
}
-function expandCollapsible(evnt) {
+
+const expandCollapsible = (evnt) => {
let classlist = evnt.currentTarget.parentNode.classList;
let c = "expanded";
!classlist.contains(c) ? classlist.add(c) : classlist.remove(c);
}
-function notificationCheck(type) {
- let changed = true;
- switch (type) {
- case "about":
- sSet("seenAbout", "true");
- break;
- case "changelog":
- sSet("changelogStatus", version)
- break;
- default:
- changed = false;
- }
- if (changed && sGet("changelogStatus") === `${version}`) {
- setTimeout(() => {
- eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace(notification, '');
- eid("tab-button-about-changelog").innerHTML = eid("tab-button-about-changelog").innerHTML.replace(notification, '')
- }, 900)
- }
- if (!sGet("seenAbout") && !eid("about-footer").innerHTML.includes(notification)) {
- eid("about-footer").innerHTML = `${notification}${eid("about-footer").innerHTML}`;
- }
- if (sGet("changelogStatus") !== `${version}`) {
- if (!eid("about-footer").innerHTML.includes(notification)) {
- eid("about-footer").innerHTML = `${notification}${eid("about-footer").innerHTML}`;
- }
- if (!eid("tab-button-about-changelog").innerHTML.includes(notification)) {
- eid("tab-button-about-changelog").innerHTML = `${notification}${eid("tab-button-about-changelog").innerHTML}`;
- }
- }
-}
-function hideAllPopups() {
+
+const hideAllPopups = () => {
let filter = document.getElementsByClassName('popup');
for (let i = 0; i < filter.length; i++) {
filter[i].classList.remove("visible");
@@ -201,13 +192,14 @@ function hideAllPopups() {
eid("picker-download").href = '/';
eid("picker-download").classList.remove("visible");
}
-function popup(type, action, text) {
+
+const popup = (type, action, text) => {
if (action === 1) {
hideAllPopups(); // hide the previous popup before showing a new one
store.isPopupOpen = true;
switch (type) {
case "about":
- let tabId = sGet("changelogStatus") !== `${version}` ? "changelog" : "about";
+ let tabId = "about";
if (text) tabId = text;
eid(`tab-button-${type}-${tabId}`).click();
break;
@@ -276,7 +268,8 @@ function popup(type, action, text) {
eid(`popup-${type}`).classList.toggle("visible");
eid(`popup-${type}`).focus();
}
-function changeSwitcher(li, b) {
+
+const changeSwitcher = (li, b) => {
if (b) {
if (!switchers[li].includes(b)) b = switchers[li][0];
sSet(li, b);
@@ -287,14 +280,14 @@ function changeSwitcher(li, b) {
if (li === "filenamePattern") updateFilenamePreview();
} else {
let pref = switchers[li][0];
- if (isMobile && exceptions[li]) pref = exceptions[li];
sSet(li, pref);
for (let i in switchers[li]) {
(switchers[li][i] === pref) ? enable(`${li}-${pref}`) : disable(`${li}-${switchers[li][i]}`)
}
}
}
-function checkbox(action) {
+
+const checkbox = (action) => {
sSet(action, !!eid(action).checked);
switch(action) {
case "alwaysVisibleButton": button(); break;
@@ -302,45 +295,51 @@ function checkbox(action) {
case "disableAnimations": eid("cobalt-body").classList.toggle('no-animation'); break;
}
}
-function changeButton(type, text) {
+
+const changeButton = (type, text) => {
switch (type) {
- case 0: //error
+ case "error": //error
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
- changeDownloadButton(2, '!!');
+ changeDownloadButton("disabled", '!!');
popup("error", 1, text);
- setTimeout(() => { changeButton(1); }, 2500);
+ setTimeout(() => { changeButton("default") }, 2500);
break;
- case 1: //enable back
- changeDownloadButton(1, '>>');
+ case "default": //enable back
+ changeDownloadButton();
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
break;
- case 2: //enable back + information popup
+ case "error-default": //enable back + information popup
popup("error", 1, text);
- changeDownloadButton(1, '>>');
+ changeDownloadButton();
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
break;
}
}
-function internetError() {
+
+const internetError = () => {
eid("url-input-area").disabled = false
- changeDownloadButton(2, '!!');
- setTimeout(() => { changeButton(1); }, 2500);
+ changeDownloadButton("disabled", '!!');
+ setTimeout(() => { changeButton("default") }, 2500);
popup("error", 1, loc.ErrorNoInternet);
}
-function resetSettings() {
+
+const resetSettings = () => {
localStorage.clear();
window.location.reload();
}
-async function pasteClipboard() {
+
+const pasteClipboard = async() => {
try {
- let t = await navigator.clipboard.readText();
- if (regex.test(t)) {
- eid("url-input-area").value = t;
- download(eid("url-input-area").value);
- }
+ navigator.clipboard.readText().then(text => {
+ let matchLink = text.match(/https?:\/\/[^\s]+/g);
+ if (matchLink) {
+ eid("url-input-area").value = text;
+ download(eid("url-input-area").value);
+ }
+ })
} catch (e) {
let errorMessage = loc.FeatureErrorGeneric;
let doError = true;
@@ -353,160 +352,168 @@ async function pasteClipboard() {
if (doError) popup("error", 1, errorMessage);
}
}
-async function download(url) {
- changeDownloadButton(2, '...');
+
+const download = async(url) => {
+ changeDownloadButton("disabled", '...');
+
eid("url-clear").style.display = "none";
eid("url-input-area").disabled = true;
+
let req = {
url,
- aFormat: sGet("aFormat").slice(0, 4),
- filenamePattern: sGet("filenamePattern"),
- dubLang: false
+ vCodec: lazyGet("vCodec"),
+ vQuality: lazyGet("vQuality"),
+ aFormat: lazyGet("aFormat"),
+ filenamePattern: lazyGet("filenamePattern"),
+ isAudioOnly: lazyGet("audioMode"),
+ isTTFullAudio: lazyGet("fullTikTokAudio"),
+ isAudioMuted: lazyGet("muteAudio"),
+ disableMetadata: lazyGet("disableMetadata"),
+ dubLang: lazyGet("dubLang"),
+ twitterGif: lazyGet("twitterGif"),
+ tiktokH265: lazyGet("tiktokH265"),
}
- if (sGet("dubLang") === "auto") {
- req.dubLang = true
- } else if (sGet("dubLang") === "custom") {
- req.dubLang = true
- }
- if (sGet("vimeoDash") === "true") req.vimeoDash = true;
- if (sGet("audioMode") === "true") {
- req.isAudioOnly = true;
- if (sGet("fullTikTokAudio") === "true") req.isTTFullAudio = true; // audio tiktok full
- } else {
- req.vQuality = sGet("vQuality").slice(0, 4);
- if (sGet("muteAudio") === "true") req.isAudioMuted = true;
- if (url.includes("youtube.com/") || url.includes("/youtu.be/")) req.vCodec = sGet("vCodec").slice(0, 4);
- }
-
- if (sGet("disableMetadata") === "true") req.disableMetadata = true;
- if (sGet("twitterGif") === "true") req.twitterGif = true;
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 });
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ }).then(r => r.json()).catch(() => {});
+
if (!j) {
internetError();
- return
+ 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.ErrorNoUrlReturned);
+ if ((j.status === "error" || j.status === "rate-limit") && j && j.text) {
+ changeButton("error", j.text);
+ return;
+ }
+
+ if (j.text && (!j.url || !j.picker)) {
+ if (j.status === "success") {
+ changeButton("error-default", j.text)
+ } else {
+ changeButton("error", loc.ErrorNoUrlReturned);
}
- 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, '>>>');
- popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
- setTimeout(() => { changeButton(1) }, 2500);
- } else if (j.picker) {
- changeDownloadButton(2, '>>>');
- popup('picker', 1, { arr: j.picker, type: j.pickerType });
- setTimeout(() => { changeButton(1) }, 2500);
+ }
+ switch (j.status) {
+ case "redirect":
+ changeDownloadButton("disabled", '>>>');
+ setTimeout(() => { changeButton("default") }, 1500);
+
+ if (sGet("downloadPopup") === "true") {
+ popup('download', 1, j.url)
+ } else {
+ window.open(j.url, '_blank')
+ }
+ break;
+ case "stream":
+ changeDownloadButton("disabled", '?..');
+
+ let probeStream = await fetch(`${j.url}&p=1`).then(r => r.json()).catch(() => {});
+ if (!probeStream) return internetError();
+
+ if (probeStream.status !== "continue") {
+ changeButton("error", probeStream.text);
+ return;
+ }
+
+ changeDownloadButton("disabled", '>>>');
+ if (sGet("downloadPopup") === "true") {
+ popup('download', 1, j.url)
+ } else {
+ if (isMobile || isSafari) {
+ window.location.href = j.url;
} else {
- changeButton(0, loc.ErrorNoUrlReturned);
+ window.open(j.url, '_blank');
}
- break;
- case "stream":
- changeDownloadButton(2, '?..')
- fetch(`${j.url}&p=1`).then(async (res) => {
- let jp = await res.json();
- if (jp.status === "continue") {
- changeDownloadButton(2, '>>>');
- if (sGet("downloadPopup") === "true") {
- popup('download', 1, j.url)
- } else {
- if (isMobile || isSafari) {
- window.location.href = j.url;
- } else window.open(j.url, '_blank');
- }
- setTimeout(() => { changeButton(1) }, 2500);
- } else {
- changeButton(0, jp.text);
- }
- }).catch((error) => internetError());
- break;
- case "success":
- changeButton(2, j.text);
- break;
- default:
- changeButton(0, loc.ErrorUnknownStatus);
- break;
- }
- } else if (j && j.text) {
- changeButton(0, j.text);
+ }
+ setTimeout(() => { changeButton("default") }, 2500);
+ break;
+ case "picker":
+ if (j.audio && j.picker) {
+ changeDownloadButton("disabled", '>>>');
+ popup('picker', 1, {
+ audio: j.audio,
+ arr: j.picker,
+ type: j.pickerType
+ });
+ setTimeout(() => { changeButton("default") }, 2500);
+ } else if (j.picker) {
+ changeDownloadButton("disabled", '>>>');
+ popup('picker', 1, {
+ arr: j.picker,
+ type: j.pickerType
+ });
+ setTimeout(() => { changeButton("default") }, 2500);
+ } else {
+ changeButton("error", loc.ErrorNoUrlReturned);
+ }
+ break;
+ case "success":
+ changeButton("error-default", j.text);
+ break;
+ default:
+ changeButton("error", loc.ErrorUnknownStatus);
+ break;
}
}
-async function loadCelebrationsEmoji() {
- let bac = eid("about-footer").innerHTML;
+
+const loadCelebrationsEmoji = async() => {
+ let aboutButtonBackup = eid("about-footer").innerHTML;
try {
- let j = await fetch(`/onDemand?blockId=1`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
+ let j = await fetch(`/onDemand?blockId=1`).then(r => r.json()).catch(() => {});
+
if (j && j.status === "success" && j.text) {
- eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('', j.text);
+ eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace(
+ `
`,
+ j.text
+ )
}
- } catch (e) {
- eid("about-footer").innerHTML = bac;
+ } catch {
+ eid("about-footer").innerHTML = aboutButtonBackup;
}
}
-async function loadOnDemand(elementId, blockId) {
- let j = {};
+
+const loadOnDemand = async(elementId, blockId) => {
store.historyButton = eid(elementId).innerHTML;
eid(elementId).innerHTML = `