diff --git a/docs/api.md b/docs/api.md
index e63ee7cd..57509669 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -32,7 +32,7 @@ Content-Type: application/json
| `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 |
-| `vimeoDash` | `boolean` | `true / false` | `false` | changes whether streamed file type is preferred for vimeo videos. |
+| `tiktokH265` | `boolean` | `true / false` | `false` | changes whether 1080p h265 videos are preferred or not. |
### response body variables
| key | type | variables |
diff --git a/package.json b/package.json
index 3b0b3443..a2c270fa 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
- "version": "7.12.6",
+ "version": "7.13",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
@@ -40,6 +40,6 @@
"set-cookie-parser": "2.6.0",
"undici": "^6.7.0",
"url-pattern": "1.0.3",
- "youtubei.js": "^9.2.0"
+ "youtubei.js": "^9.3.0"
}
}
diff --git a/src/cobalt.js b/src/cobalt.js
index 050aec46..473c9b5b 100644
--- a/src/cobalt.js
+++ b/src/cobalt.js
@@ -6,6 +6,7 @@ import express from "express";
import { Bright, Green, Red } from "./modules/sub/consoleText.js";
import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js";
import { loadLoc } from "./localization/manager.js";
+import { mode } from "./modules/config.js"
import path from 'path';
import { fileURLToPath } from 'url';
@@ -22,13 +23,10 @@ app.disable('x-powered-by');
await loadLoc();
-const apiMode = process.env.API_URL && !process.env.WEB_URL;
-const webMode = process.env.WEB_URL && process.env.API_URL;
-
-if (apiMode) {
+if (mode === 'API') {
const { runAPI } = await import('./core/api.js');
runAPI(express, app, gitCommit, gitBranch, __dirname)
-} else if (webMode) {
+} else if (mode === 'WEB') {
const { runWeb } = await import('./core/web.js');
await runWeb(express, app, gitCommit, gitBranch, __dirname)
} else {
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/core/api.js b/src/core/api.js
index eda3c014..8ff0b5c1 100644
--- a/src/core/api.js
+++ b/src/core/api.js
@@ -4,18 +4,18 @@ import { randomBytes } from "crypto";
const ipSalt = randomBytes(64).toString('hex');
-import { version } from "../modules/config.js";
+import { env, version } from "../modules/config.js";
import { getJSON } from "../modules/api.js";
import { apiJSON, checkJSONPost, getIP, languageCode } from "../modules/sub/utils.js";
import { Bright, Cyan } from "../modules/sub/consoleText.js";
import stream from "../modules/stream/stream.js";
import loc from "../localization/manager.js";
import { generateHmac } from "../modules/sub/crypto.js";
-import { verifyStream } from "../modules/stream/manage.js";
+import { verifyStream, getInternalStream } from "../modules/stream/manage.js";
export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
- const corsConfig = process.env.CORS_WILDCARD === '0' ? {
- origin: process.env.CORS_URL,
+ const corsConfig = !env.corsWildcard ? {
+ origin: env.corsURL,
optionsSuccessStatus: 200
} : {};
@@ -123,42 +123,53 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
app.get('/api/:type', (req, res) => {
try {
+ let j;
switch (req.params.type) {
case 'stream':
const q = req.query;
const checkQueries = q.t && q.e && q.h && q.s && q.i;
const checkBaseLength = q.t.length === 21 && q.e.length === 13;
const checkSafeLength = q.h.length === 43 && q.s.length === 43 && q.i.length === 22;
-
if (checkQueries && checkBaseLength && checkSafeLength) {
+ if (q.p) {
+ return res.status(200).json({
+ status: "continue"
+ })
+ }
let streamInfo = verifyStream(q.t, q.h, q.e, q.s, q.i);
if (streamInfo.error) {
return res.status(streamInfo.status).json(apiJSON(0, { t: streamInfo.error }).body);
}
- if (q.p) {
- return res.status(200).json({
- status: "continue"
- });
- }
return stream(res, streamInfo);
- } else {
- let j = apiJSON(0, {
- t: "bad request. stream link may be incomplete or corrupted."
- })
- return res.status(j.status).json(j.body);
- }
+ }
+
+ j = apiJSON(0, {
+ t: "bad request. stream link may be incomplete or corrupted."
+ })
+ return res.status(j.status).json(j.body);
+ case 'istream':
+ if (!req.ip.endsWith('127.0.0.1'))
+ return res.sendStatus(403);
+ if (('' + req.query.t).length !== 21)
+ return res.sendStatus(400);
+
+ let streamInfo = getInternalStream(req.query.t);
+ if (!streamInfo) return res.sendStatus(404);
+ streamInfo.headers = req.headers;
+
+ return stream(res, { type: 'internal', ...streamInfo });
case 'serverInfo':
return res.status(200).json({
version: version,
commit: gitCommit,
branch: gitBranch,
- name: process.env.API_NAME || "unknown",
- url: process.env.API_URL,
- cors: process.env?.CORS_WILDCARD === "0" ? 0 : 1,
+ name: env.apiName,
+ url: env.apiURL,
+ cors: Number(env.corsWildcard),
startTime: `${startTimestamp}`
});
default:
- let j = apiJSON(0, {
+ j = apiJSON(0, {
t: "unknown response type"
})
return res.status(j.status).json(j.body);
@@ -183,12 +194,12 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
res.redirect('/api/json')
});
- app.listen(process.env.API_PORT || 9000, () => {
+ app.listen(env.apiPort, () => {
console.log(`\n` +
`${Cyan("cobalt")} API ${Bright(`v.${version}-${gitCommit} (${gitBranch})`)}\n` +
`Start time: ${Bright(`${startTime.toUTCString()} (${startTimestamp})`)}\n\n` +
- `URL: ${Cyan(`${process.env.API_URL}`)}\n` +
- `Port: ${process.env.API_PORT || 9000}\n`
+ `URL: ${Cyan(`${env.apiURL}`)}\n` +
+ `Port: ${env.apiPort}\n`
)
});
}
diff --git a/src/core/web.js b/src/core/web.js
index 7c0cbf33..626574a3 100644
--- a/src/core/web.js
+++ b/src/core/web.js
@@ -1,4 +1,4 @@
-import { genericUserAgent, version } from "../modules/config.js";
+import { genericUserAgent, version, env } from "../modules/config.js";
import { apiJSON, languageCode } from "../modules/sub/utils.js";
import { Bright, Cyan } from "../modules/sub/consoleText.js";
@@ -76,12 +76,12 @@ export async function runWeb(express, app, gitCommit, gitBranch, __dirname) {
return res.redirect('/')
});
- app.listen(process.env.WEB_PORT || 9001, () => {
+ app.listen(env.webPort, () => {
console.log(`\n` +
`${Cyan("cobalt")} WEB ${Bright(`v.${version}-${gitCommit} (${gitBranch})`)}\n` +
`Start time: ${Bright(`${startTime.toUTCString()} (${startTimestamp})`)}\n\n` +
- `URL: ${Cyan(`${process.env.WEB_URL}`)}\n` +
- `Port: ${process.env.WEB_PORT || 9001}\n`
+ `URL: ${Cyan(`${env.webURL}`)}\n` +
+ `Port: ${env.webPort}\n`
)
})
}
diff --git a/src/front/cobalt.js b/src/front/cobalt.js
index ad2e9e59..0fd638f2 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,127 @@ 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) {}
-}
-function detectColorScheme() {
+
+const share = url => navigator?.share({ url }).catch(() => {});
+
+const detectColorScheme = () => {
let theme = "auto";
let localTheme = sGet("theme");
if (localTheme) {
@@ -133,7 +152,59 @@ function detectColorScheme() {
}
document.documentElement.setAttribute("data-theme", theme);
}
-function changeTab(evnt, tabId, tabClass) {
+
+const updateFilenamePreview = () => {
+ let videoFilePreview = ``;
+ let audioFilePreview = ``;
+ let resMatch = {
+ "max": "3840x2160",
+ "2160": "3840x2160",
+ "1440": "2560x1440",
+ "1080": "1920x1080",
+ "720": "1280x720",
+ "480": "854x480",
+ "360": "640x360",
+ }
+
+ switch(sGet("filenamePattern")) {
+ case "classic":
+ videoFilePreview = `youtube_dQw4w9WgXcQ_${resMatch[sGet('vQuality')]}_${sGet('vCodec')}`
+ + `${sGet("muteAudio") === "true" ? "_mute" : ""}`
+ + `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
+ audioFilePreview = `youtube_dQw4w9WgXcQ_audio`
+ + `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
+ break;
+ case "basic":
+ videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ + `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, `
+ + `${sGet('vCodec')}${sGet("muteAudio") === "true" ? ", mute" : ""})`
+ + `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
+ audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor}`
+ + `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
+ break;
+ case "pretty":
+ videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ + `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ + `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube)`
+ + `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
+ audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} (soundcloud)`
+ + `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
+ break;
+ case "nerdy":
+ videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ + `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ + `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube, dQw4w9WgXcQ)`
+ + `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
+ audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} `
+ + `(soundcloud, 1242868615)`
+ + `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
+ break;
+ }
+ eid("video-filename-text").innerHTML = videoFilePreview
+ eid("audio-filename-text").innerHTML = audioFilePreview
+}
+
+const changeTab = (evnt, tabId, tabClass) => {
if (tabId === "tab-settings-other") updateFilenamePreview();
let tabcontent = document.getElementsByClassName(`tab-content-${tabClass}`);
@@ -149,46 +220,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 +241,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 +317,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 +329,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,43 +344,158 @@ 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 download = async(url) => {
+ changeDownloadButton("disabled", '...');
+
+ eid("url-clear").style.display = "none";
+ eid("url-input-area").disabled = true;
+
+ let req = {
+ url,
+ 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("ytDub"),
+ twitterGif: lazyGet("twitterGif"),
+ tiktokH265: lazyGet("tiktokH265"),
+ }
+
+ let j = await fetch(`${apiURL}/api/json`, {
+ method: "POST",
+ body: JSON.stringify(req),
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ }).then(r => r.json()).catch(() => {});
+
+ if (!j) {
+ internetError();
+ return;
+ }
+
+ 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("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 {
+ window.open(j.url, '_blank');
+ }
+ }
+ 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;
+ }
+}
+
+const pasteClipboard = async() => {
try {
- let t = await navigator.clipboard.readText();
- if (regex.test(t)) {
- eid("url-input-area").value = t;
+ let clipboard = await navigator.clipboard.readText();
+ let onlyURL = clipboard.match(/https:\/\/[^\s]+/g)
+ if (onlyURL) {
+ eid("url-input-area").value = onlyURL;
download(eid("url-input-area").value);
}
} catch (e) {
@@ -353,204 +510,58 @@ async function pasteClipboard() {
if (doError) popup("error", 1, errorMessage);
}
}
-async function download(url) {
- changeDownloadButton(2, '...');
- 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
- }
- 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 });
- if (!j) {
- internetError();
- return
- }
-
- if (j && j.status !== "error" && j.status !== "rate-limit") {
- if (j.text && (!j.url || !j.picker)) {
- if (j.status === "success") {
- changeButton(2, j.text)
- } else changeButton(0, loc.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);
- } else {
- changeButton(0, loc.ErrorNoUrlReturned);
- }
- 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);
- }
-}
-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 = `