3.0
probably the biggest update in history of cobalt
This commit is contained in:
@@ -7,7 +7,7 @@ import { errorUnsupported } from "./sub/errors.js";
|
||||
import loc from "../localization/manager.js";
|
||||
import match from "./match.js";
|
||||
|
||||
export async function getJSON(originalURL, ip, lang, format, quality) {
|
||||
export async function getJSON(originalURL, ip, lang, format, quality, audioFormat, isAudioOnly) {
|
||||
try {
|
||||
let url = decodeURI(originalURL);
|
||||
if (!url.includes('http://')) {
|
||||
@@ -28,7 +28,7 @@ export async function getJSON(originalURL, ip, lang, format, quality) {
|
||||
if (patternMatch) break;
|
||||
}
|
||||
if (patternMatch) {
|
||||
return await match(host, patternMatch, url, ip, lang, format, quality);
|
||||
return await match(host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly);
|
||||
} return apiJSON(0, { t: errorUnsupported(lang) } )
|
||||
} return apiJSON(0, { t: errorUnsupported(lang) } )
|
||||
} else {
|
||||
|
||||
@@ -15,4 +15,5 @@ supportedLanguages = config.supportedLanguages,
|
||||
quality = config.quality,
|
||||
internetExplorerRedirect = config.internetExplorerRedirect,
|
||||
donations = config.donations,
|
||||
ffmpegArgs = config.ffmpegArgs
|
||||
ffmpegArgs = config.ffmpegArgs,
|
||||
supportedAudio = config.supportedAudio
|
||||
|
||||
24
src/modules/emoji.js
Normal file
24
src/modules/emoji.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const names = {
|
||||
"🎶": "musical_notes",
|
||||
"🎬": "clapper_board",
|
||||
"💰": "money_bag",
|
||||
"🎉": "party_popper",
|
||||
"❓": "red_question_mark",
|
||||
"✨": "sparkles",
|
||||
"🪅": "pinata",
|
||||
"🪄": "magic_wand",
|
||||
"🐲": "dragon_face",
|
||||
"💸": "money_with_wings",
|
||||
"⚙️": "gear"
|
||||
}
|
||||
let sizing = {
|
||||
22: 0.4,
|
||||
30: 0.7
|
||||
}
|
||||
export default function(emoji, size, disablePadding) {
|
||||
if (!size) size = 22;
|
||||
let padding = size != 22 ? `margin-right:${sizing[size] ? sizing[size] : "0.4"}rem;`: ``;
|
||||
if (disablePadding) padding = 'margin-right:0!important;';
|
||||
if (!names[emoji]) emoji = "❓";
|
||||
return `<img class="emoji" height="${size}" width="${size}" style="${padding}" alt="${emoji}" src="emoji/${names[emoji]}.svg">`
|
||||
}
|
||||
@@ -11,8 +11,9 @@ import vk from "./services/vk.js";
|
||||
import tiktok from "./services/tiktok.js";
|
||||
import douyin from "./services/douyin.js";
|
||||
import tumblr from "./services/tumblr.js";
|
||||
import matchActionDecider from "./sub/matchActionDecider.js";
|
||||
|
||||
export default async function (host, patternMatch, url, ip, lang, format, quality) {
|
||||
export default async function (host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly) {
|
||||
try {
|
||||
if (!testers[host]) return apiJSON(0, { t: errorUnsupported(lang) });
|
||||
if (!(testers[host](patternMatch))) throw Error();
|
||||
@@ -24,99 +25,69 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit
|
||||
id: patternMatch["id"],
|
||||
lang: lang
|
||||
});
|
||||
return (!r.error) ? apiJSON(1, { u: r }) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "vk":
|
||||
r = await vk({
|
||||
userId: patternMatch["userId"],
|
||||
videoId: patternMatch["videoId"],
|
||||
lang: lang, quality: quality
|
||||
});
|
||||
return (!r.error) ? apiJSON(2, { type: "bridge", lang: lang, u: r.url, filename: r.filename,
|
||||
service: host, ip: ip, salt: process.env.streamSalt }) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "bilibili":
|
||||
r = await bilibili({
|
||||
id: patternMatch["id"].slice(0, 12),
|
||||
lang: lang
|
||||
});
|
||||
return (!r.error) ? apiJSON(2, {
|
||||
type: "render", u: r.urls, lang: lang,
|
||||
service: host, ip: ip,
|
||||
filename: r.filename,
|
||||
salt: process.env.streamSalt, time: r.time
|
||||
}) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "youtube":
|
||||
let fetchInfo = {
|
||||
id: patternMatch["id"].slice(0,11),
|
||||
lang: lang, quality: quality,
|
||||
format: "mp4"
|
||||
format: "webm"
|
||||
};
|
||||
if (url.match('music.youtube.com')) {
|
||||
format = "audio"
|
||||
}
|
||||
if (url.match('music.youtube.com') || isAudioOnly == true) format = "audio";
|
||||
switch (format) {
|
||||
case "webm":
|
||||
fetchInfo["format"] = "webm";
|
||||
case "mp4":
|
||||
fetchInfo["format"] = "mp4";
|
||||
break;
|
||||
case "audio":
|
||||
fetchInfo["format"] = "webm";
|
||||
fetchInfo["isAudioOnly"] = true;
|
||||
fetchInfo["quality"] = "max";
|
||||
isAudioOnly = true;
|
||||
break;
|
||||
}
|
||||
r = await youtube(fetchInfo);
|
||||
return (!r.error) ? apiJSON(2, {
|
||||
type: r.type, u: r.urls, lang: lang, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt,
|
||||
isAudioOnly: fetchInfo["isAudioOnly"] ? fetchInfo["isAudioOnly"] : false,
|
||||
time: r.time,
|
||||
}) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "reddit":
|
||||
r = await reddit({
|
||||
sub: patternMatch["sub"],
|
||||
id: patternMatch["id"],
|
||||
title: patternMatch["title"], lang: lang,
|
||||
});
|
||||
return (!r.error) ? apiJSON(r.typeId, {
|
||||
type: r.type, u: r.urls, lang: lang,
|
||||
service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
}) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "tiktok":
|
||||
r = await tiktok({
|
||||
postId: patternMatch["postId"],
|
||||
id: patternMatch["id"], lang: lang,
|
||||
});
|
||||
return (!r.error) ? apiJSON(2, {
|
||||
type: "bridge", u: r.urls, lang: lang,
|
||||
service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
}) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "douyin":
|
||||
r = await douyin({
|
||||
postId: patternMatch["postId"],
|
||||
id: patternMatch["id"], lang: lang,
|
||||
});
|
||||
return (!r.error) ? apiJSON(2, {
|
||||
type: "bridge", u: r.urls, lang: lang,
|
||||
service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
}) : apiJSON(0, { t: r.error });
|
||||
|
||||
break;
|
||||
case "tumblr":
|
||||
r = await tumblr({
|
||||
id: patternMatch["id"], url: url, user: patternMatch["user"] ? patternMatch["user"] : false,
|
||||
lang: lang
|
||||
});
|
||||
return (!r.error) ? apiJSON(1, { u: r.split('?')[0] }) : apiJSON(0, { t: r.error });
|
||||
break;
|
||||
default:
|
||||
return apiJSON(0, { t: errorUnsupported(lang) });
|
||||
}
|
||||
return matchActionDecider(r, host, ip, audioFormat, isAudioOnly)
|
||||
} catch (e) {
|
||||
return apiJSON(0, { t: genericError(lang, host) })
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import { services, appName, authorInfo, version, quality, repo, donations } from "./config.js";
|
||||
import { getCommitInfo } from "./sub/currentCommit.js";
|
||||
import loc from "../localization/manager.js";
|
||||
|
||||
let s = services;
|
||||
let com = getCommitInfo();
|
||||
|
||||
let enabledServices = Object.keys(s).filter((p) => {
|
||||
if (s[p].enabled) {
|
||||
return true
|
||||
}
|
||||
}).sort().map((p) => {
|
||||
if (s[p].alias) {
|
||||
return s[p].alias
|
||||
} else {
|
||||
return p
|
||||
}
|
||||
}).join(', ')
|
||||
|
||||
let donate = ``
|
||||
for (let i in donations) {
|
||||
donate += `<div class="subtitle">${i} (REPLACEME)</div><div id="don-${i}" class="text-to-copy" onClick="copy('don-${i}')">${donations[i]}</div>`
|
||||
}
|
||||
export default function(obj) {
|
||||
let isIOS = obj.useragent.toLowerCase().match("iphone os")
|
||||
try {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
|
||||
|
||||
<title>${appName}</title>
|
||||
|
||||
<meta property="og:url" content="${process.env.selfURL}" />
|
||||
<meta property="og:title" content="${appName}" />
|
||||
<meta property="og:description" content="${loc(obj.lang, 'EmbedBriefDescription')}" />
|
||||
<meta property="og:image" content="${process.env.selfURL}icons/generic.png" />
|
||||
<meta name="title" content="${appName}" />
|
||||
<meta name="description" content="${loc(obj.lang, 'AboutSummary')}" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="icons/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png" />
|
||||
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<link rel="stylesheet" href="cobalt.css" />
|
||||
<link rel="stylesheet" href="fonts/notosansmono.css" />
|
||||
|
||||
<noscript><div style="margin: 2rem;">${loc(obj.lang, 'NoScriptMessage')}</div></noscript>
|
||||
</head>
|
||||
<body id="cobalt-body" data-nosnippet>
|
||||
<div id="popup-about" class="popup center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('about', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="title" class="popup-title">${loc(obj.lang, 'TitlePopupAbout')}</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content with-footer">
|
||||
<div id="desc" class="popup-desc about-padding">${loc(obj.lang, 'AboutSummary')}</div>
|
||||
<div id="desc" class="popup-desc about-padding">${loc(obj.lang, 'AboutSupportedServices')} ${enabledServices}.</div>
|
||||
<div id="desc" class="popup-desc bottom-link"><a class="text-backdrop" href="${repo}">${loc(obj.lang, 'LinkGitHubIssues')}</a></div>
|
||||
</div>
|
||||
<div id="popup-footer" class="popup-footer">
|
||||
<a id="popup-bottom" class="popup-footer-content" href="${authorInfo.link}">${loc(obj.lang, 'MadeWithLove')}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-changelog" class="popup center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('changelog', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="title" class="popup-title">${loc(obj.lang, 'TitlePopupChangelog')}</div>
|
||||
<div id="desc" class="popup-subtitle">${com[0]} (${obj.hash})</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content">
|
||||
<div id="desc" class="popup-desc about-padding">${com[1]}</div>
|
||||
<div id="desc" class="popup-desc bottom-link"><a class="text-backdrop" href="${repo}/commits">${loc(obj.lang, 'LinkGitHubChanges')}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-donate" class="popup scrollable center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('donate', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="title" class="popup-title">${loc(obj.lang, 'TitlePopupDonate')}</div>
|
||||
<div id="desc" class="little-subtitle">${loc(obj.lang, 'DonateSubtitle')}</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content">
|
||||
${donate.replace(/REPLACEME/g, loc(obj.lang, 'ClickToCopy').trim())}
|
||||
<div id="desc" class="explanation about-padding">${loc(obj.lang, 'DonateDescription')}</div>
|
||||
<div id="desc" class="popup-desc bottom-link"><a class="text-backdrop" href="${authorInfo.contact}">${loc(obj.lang, 'LinkDonateContact')}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-settings" class="popup scrollable center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('settings', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="version" class="popup-above-title">v.${version} ~ ${obj.hash}</div>
|
||||
<div id="title" class="popup-title">${loc(obj.lang, 'TitlePopupSettings')}</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content">
|
||||
<div id="settings-appearance" class="settings-category">
|
||||
<div class="title">${loc(obj.lang, 'SettingsAppearanceSubtitle')}</div>
|
||||
<div class="settings-category-content">
|
||||
<div id="theme-switcher" class="switch-container">
|
||||
<div class="subtitle">${loc(obj.lang, 'SettingsThemeSubtitle')}</div>
|
||||
<div class="switches">
|
||||
<div id="theme-auto" class="switch full" onclick="changeSwitcher('theme', 'auto', 1)">${loc(obj.lang, 'SettingsThemeAuto')}</div>
|
||||
<div id="theme-dark" class="switch" onclick="changeSwitcher('theme', 'dark', 1)">${loc(obj.lang, 'SettingsThemeDark')}</div>
|
||||
<div id="theme-light" class="switch full" onclick="changeSwitcher('theme', 'light', 1)">${loc(obj.lang, 'SettingsThemeLight')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-downloads" class="settings-category">
|
||||
<div class="title">${loc(obj.lang, 'SettingsDownloadsSubtitle')}</div>
|
||||
<div class="settings-category-content">
|
||||
<div id="quality-switcher" class="switch-container">
|
||||
<div class="subtitle">${loc(obj.lang, 'SettingsQualitySubtitle')}</div>
|
||||
<div class="switches">
|
||||
<div id="quality-max" class="switch full" onclick="changeSwitcher('quality', 'max', 1)">${loc(obj.lang, 'SettingsQualitySwitchMax')}</div>
|
||||
<div id="quality-hig" class="switch" onclick="changeSwitcher('quality', 'hig', 1)">${loc(obj.lang, 'SettingsQualitySwitchHigh')}(${quality.hig}p)</div>
|
||||
<div id="quality-mid" class="switch full" onclick="changeSwitcher('quality', 'mid', 1)">${loc(obj.lang, 'SettingsQualitySwitchMedium')}(${quality.mid}p)</div>
|
||||
<div id="quality-low" class="switch right" onclick="changeSwitcher('quality', 'low', 1)">${loc(obj.lang, 'SettingsQualitySwitchLow')}(${quality.low}p)</div>
|
||||
</div>
|
||||
<div class="explanation">${loc(obj.lang, 'SettingsQualityDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-youtube" class="settings-category">
|
||||
<div class="title">youtube</div>
|
||||
<div class="settings-category-content">
|
||||
<div id="youtube-switcher" class="switch-container">
|
||||
<div class="subtitle">${loc(obj.lang, 'SettingsFormatSubtitle')}</div>
|
||||
<div class="switches">
|
||||
<div id="youtubeFormat-mp4" class="switch full" onclick="changeSwitcher('youtubeFormat', 'mp4', 1)">mp4</div>
|
||||
<div id="youtubeFormat-webm" class="switch" onclick="changeSwitcher('youtubeFormat', 'webm', 1)">webm</div>
|
||||
<div id="youtubeFormat-audio" class="switch full" onclick="changeSwitcher('youtubeFormat', 'audio', 1)">${loc(obj.lang, 'SettingsFormatSwitchAudio')}</div>
|
||||
</div>
|
||||
<div class="explanation">${loc(obj.lang, 'SettingsFormatDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings-misc" class="settings-category">
|
||||
<div class="title bottom-margin">${loc(obj.lang, 'SettingsMiscSubtitle')}</div>
|
||||
<div class="settings-category-content">
|
||||
<label class="checkbox">
|
||||
<input id="alwaysVisibleButton" type="checkbox" aria-label="${loc(obj.lang, 'AccessibilityKeepDownloadButton')}" onclick="checkbox('alwaysVisibleButton', 'always-visible-button')">
|
||||
<span>${loc(obj.lang, 'SettingsKeepDownloadButton')}</span>
|
||||
</label>${!isIOS ? `
|
||||
<label class="checkbox">
|
||||
<input id="downloadPopup" type="checkbox" aria-label="${loc(obj.lang, 'AccessibilityEnableDownloadPopup')}" onclick="checkbox('downloadPopup', 'always-visible-button')">
|
||||
<span>${loc(obj.lang, 'SettingsEnableDownloadPopup')}</span>
|
||||
</label>` : ``}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-download" class="popup center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('download', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="title" class="popup-subtitle">${loc(obj.lang, 'TitlePopupDownload')}</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content">
|
||||
<div id="theme-switcher" class="switch-container small-padding">
|
||||
<div class="subtitle">${loc(obj.lang, 'DownloadPopupDescription')}</div>
|
||||
<div class="switches">
|
||||
<a id="pd-download" class="switch full space-right" target="_blank" href="/">${loc(obj.lang, 'Download')}</a>
|
||||
<div id="pd-copy" class="switch full">${loc(obj.lang, 'CopyURL')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="desc" class="explanation about-padding">${isIOS ? loc(obj.lang, 'DownloadPopupDescriptionIOS') : loc(obj.lang, 'DownloadPopupDescription')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-error" class="popup center box" style="visibility: hidden;">
|
||||
<div id="popup-header" class="popup-header">
|
||||
<button id="close" class="button mono" onclick="popup('error', 0)" aria-label="${loc(obj.lang, 'AccessibilityClosePopup')}">x</button>
|
||||
<div id="title" class="popup-title">${loc(obj.lang, 'TitlePopupError')}</div>
|
||||
</div>
|
||||
<div id="content" class="popup-content">
|
||||
<div id="desc-error" class="popup-desc"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="popup-backdrop" style="visibility: hidden;"></div>
|
||||
<div id="cobalt-main-box" class="center box" style="visibility: hidden;">
|
||||
<div id="logo-area">${appName}</div>
|
||||
<div id="download-area" class="mobile-center">
|
||||
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="110" autocapitalize="off" placeholder="${loc(obj.lang, 'LinkInput')}" aria-label="${loc(obj.lang, 'AccessibilityInputArea')}" oninput="button()">
|
||||
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${loc(obj.lang, 'AccessibilityDownloadButton')}">
|
||||
</div>
|
||||
</div>
|
||||
<footer id="footer" style="visibility: hidden;">
|
||||
<div id="footer-buttons">
|
||||
<button id="about-open" class="button footer-button" onclick="popup('about', 1)" aria-label="${loc(obj.lang, 'AccessibilityOpenAbout')}">?</button>
|
||||
<button id="changelog-open" class="button footer-button" onclick="popup('changelog', 1)" aria-label="${loc(obj.lang, 'AccessibilityOpenChangelog')}">!</button>
|
||||
<button id="donate-open" class="button footer-button" onclick="popup('donate', 1)" aria-label="${loc(obj.lang, 'AccessibilityOpenDonate')}">$</button>
|
||||
<button id="settings-open" class="button footer-button" onclick="popup('settings', 1)" aria-label="${loc(obj.lang, 'AccessibilityOpenSettings')}">+</button>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
<script type="text/javascript">const loc = {noInternet:"${loc(obj.lang, 'ErrorNoInternet')}", noURLReturned: "${loc(obj.lang, 'ErrorBadFetch')}"};</script>
|
||||
<script type="text/javascript" src="cobalt.js"></script>
|
||||
</html>`;
|
||||
} catch (err) {
|
||||
return `${loc(obj.lang, 'ErrorPageRenderFail', obj.hash)}`;
|
||||
}
|
||||
}
|
||||
95
src/modules/pageRender/elements.js
Normal file
95
src/modules/pageRender/elements.js
Normal file
@@ -0,0 +1,95 @@
|
||||
export function switcher(obj) {
|
||||
let items = ``;
|
||||
switch(obj.name) {
|
||||
case "download":
|
||||
items = obj.items;
|
||||
break;
|
||||
default:
|
||||
for (let i = 0; i < obj.items.length; i++) {
|
||||
let classes = obj.items[i]["classes"] ? obj.items[i]["classes"] : []
|
||||
items += `<button id="${obj.name}-${obj.items[i]["action"]}" class="switch${classes.length > 0 ? ' ' + classes.join(' ') : ''}" onclick="changeSwitcher('${obj.name}', '${obj.items[i]["action"]}')">${obj.items[i]["text"] ? obj.items[i]["text"] : obj.items[i]["action"]}</button>`
|
||||
}
|
||||
break;
|
||||
}
|
||||
return `
|
||||
<div id="${obj.name}-switcher" class="switch-container">
|
||||
${obj.subtitle ? `<div class="subtitle">${obj.subtitle}</div>` : ``}
|
||||
<div class="switches">${items}</div>
|
||||
${obj.explanation ? `<div class="explanation">${obj.explanation}</div>` : ``}
|
||||
</div>`
|
||||
}
|
||||
|
||||
export function checkbox(action, text, aria) {
|
||||
return `<label class="checkbox">
|
||||
<input id="${action}" type="checkbox" ${aria ? `aria-label="${aria}"` : ''} onclick="checkbox('${action}')">
|
||||
<span>${text}</span>
|
||||
</label>`
|
||||
}
|
||||
|
||||
export function popup(obj) {
|
||||
let classes = obj.classes ? obj.classes : []
|
||||
let body = obj.body;
|
||||
if (Array.isArray(obj.body)) {
|
||||
body = ``
|
||||
for (let i = 0; i < obj.body.length; i++) {
|
||||
let classes = obj.body[i]["classes"] ? obj.body[i]["classes"] : []
|
||||
if (i != obj.body.length - 1 && !obj.body[i]["nopadding"]) {
|
||||
classes.push("desc-padding")
|
||||
}
|
||||
body += obj.body[i]["raw"] ? obj.body[i]["text"] : `<div id="popup-desc" class="${classes.length > 0 ? classes.join(' ') : ''}">${obj.body[i]["text"]}</div>`
|
||||
}
|
||||
}
|
||||
return `
|
||||
${!obj.embed ? `<div id="popup-${obj.name}" class="popup center box${classes.length > 0 ? ' ' + classes.join(' ') : ''}" style="visibility: hidden;">`: ''}
|
||||
<div id="popup-header" class="popup-header">
|
||||
${!obj.embed ? `<button id="popup-close" class="button mono" onclick="popup('${obj.name}', 0)" ${obj.header.closeAria ? `aria-label="${obj.header.closeAria}"` : ''}>x</button>`: ''}
|
||||
${obj.header.aboveTitle ? `<a id="popup-above-title" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
|
||||
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
|
||||
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
|
||||
</div>
|
||||
<div id="popup-content"${obj.footer ? ' class="with-footer"' : ''}>${body}</div>
|
||||
${obj.footer ? `<div id="popup-footer" class="popup-footer">
|
||||
<a id="popup-bottom" class="popup-footer-content" href="${obj.footer.url}">${obj.footer.text}</a>
|
||||
</div>` : ''}
|
||||
${!obj.embed ? `</div>`: ''}`
|
||||
}
|
||||
|
||||
export function multiPagePopup(obj) {
|
||||
let tabs = ``
|
||||
let tabContent = ``
|
||||
for (let i = 0; i < obj.tabs.length; i++) {
|
||||
tabs += `<button id="tab-button-${obj.name}-${obj.tabs[i]["name"]}" class="switch tab tab-${obj.name}" onclick="changeTab(event, 'tab-${obj.name}-${obj.tabs[i]["name"]}', '${obj.name}')">${obj.tabs[i]["title"]}</button>`
|
||||
tabContent += `<div id="tab-${obj.name}-${obj.tabs[i]["name"]}" class="popup-tab-content tab-content-${obj.name}">${obj.tabs[i]["content"]}</div>`
|
||||
}
|
||||
tabs += `<button id="close-bottom" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>x</button>`
|
||||
return `
|
||||
<div id="popup-${obj.name}" class="popup center box scrollable" style="visibility: hidden;">
|
||||
|
||||
<div id="popup-content">${obj.header ? `<div id="popup-header" class="popup-header">
|
||||
${obj.header.aboveTitle ? `<a id="popup-above-title" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
|
||||
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
|
||||
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}</div>`: ''}${tabContent}</div>
|
||||
<div id="popup-tabs" class="switches popup-tabs">${tabs}</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
export function backdropLink(link, text) {
|
||||
return `<a class="text-backdrop" href="${link}" target="_blank">${text}</a>`
|
||||
}
|
||||
|
||||
export function settingsCategory(obj) {
|
||||
return `<div id="settings-${obj.name}" class="settings-category">
|
||||
<div class="category-title">${obj.title ? obj.title : obj.name}</div>
|
||||
<div class="settings-category-content">${obj.body}</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
export function footerButtons(obj) {
|
||||
let items = ``
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
let func = `${obj[i]["type"] == "toggle" ? `toggle('${obj[i]["name"]}')` : `popup('${obj[i]["name"]}', 1)`}`
|
||||
items += `<button id="${obj[i]["name"]}" class="button footer-button" onclick="${func}" aria-label="${obj[i]["aria"]}">${obj[i]["icon"]}</button> `
|
||||
}
|
||||
return `
|
||||
<div id="footer-buttons">${items}</div>`
|
||||
}
|
||||
291
src/modules/pageRender/page.js
Normal file
291
src/modules/pageRender/page.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import { services, appName, authorInfo, version, quality, repo, donations, supportedAudio } from "../config.js";
|
||||
import { getCommitInfo } from "../sub/currentCommit.js";
|
||||
import loc from "../../localization/manager.js";
|
||||
import { backdropLink, checkbox, footerButtons, multiPagePopup, popup, settingsCategory, switcher } from "./elements.js";
|
||||
import emoji from "../emoji.js";
|
||||
|
||||
let s = services;
|
||||
let com = getCommitInfo();
|
||||
|
||||
let enabledServices = Object.keys(s).filter((p) => {
|
||||
if (s[p].enabled) return true;
|
||||
}).sort().map((p) => {
|
||||
return s[p].alias ? s[p].alias : p
|
||||
}).join(', ')
|
||||
|
||||
let donate = ``
|
||||
let donateLinks = ``
|
||||
let audioFormats = supportedAudio.map((p) => {
|
||||
return {"action": p}
|
||||
})
|
||||
audioFormats.unshift({ "action": "best" })
|
||||
for (let i in donations["other"]) {
|
||||
donateLinks += `<a id="don-${i}" class="switch autowidth" href="${donations["other"][i]}" target="_blank">${i}</a>`
|
||||
}
|
||||
let extr = ''
|
||||
for (let i in donations["crypto"]) {
|
||||
donate += `<div class="subtitle${extr}">${i} (REPLACEME)</div><div id="don-${i}" class="text-to-copy" onClick="copy('don-${i}')">${donations["crypto"][i]}</div>`
|
||||
extr = ' extra'
|
||||
}
|
||||
export default function(obj) {
|
||||
audioFormats[0]["text"] = loc(obj.lang, 'SettingsAudioFormatBest')
|
||||
let isIOS = obj.useragent.toLowerCase().match("iphone os")
|
||||
try {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
|
||||
|
||||
<title>${appName}</title>
|
||||
|
||||
<meta property="og:url" content="${process.env.selfURL}" />
|
||||
<meta property="og:title" content="${appName}" />
|
||||
<meta property="og:description" content="${loc(obj.lang, 'EmbedBriefDescription')}" />
|
||||
<meta property="og:image" content="${process.env.selfURL}icons/generic.png" />
|
||||
<meta name="title" content="${appName}" />
|
||||
<meta name="description" content="${loc(obj.lang, 'AboutSummary')}" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="icons/favicon.ico" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png" />
|
||||
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<link rel="stylesheet" href="cobalt.css" />
|
||||
<link rel="stylesheet" href="fonts/notosansmono.css" />
|
||||
|
||||
<noscript><div style="margin: 2rem;">${loc(obj.lang, 'NoScriptMessage')}</div></noscript>
|
||||
</head>
|
||||
<body id="cobalt-body" data-nosnippet>
|
||||
${multiPagePopup({
|
||||
name: "about",
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
tabs: [{
|
||||
name: "about",
|
||||
title: `${emoji("🐲")} ${loc(obj.lang, 'AboutTab')}`,
|
||||
content: popup({
|
||||
embed: true,
|
||||
name: "about",
|
||||
header: {
|
||||
aboveTitle: {
|
||||
text: loc(obj.lang, 'MadeWithLove'),
|
||||
url: authorInfo.link
|
||||
},
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
title: loc(obj.lang, 'TitlePopupAbout')
|
||||
},
|
||||
body: [{
|
||||
text: loc(obj.lang, 'AboutSummary')
|
||||
}, {
|
||||
text: `${loc(obj.lang, 'AboutSupportedServices')} ${enabledServices}.`
|
||||
}, {
|
||||
text: backdropLink(repo, loc(obj.lang, 'LinkGitHubIssues')),
|
||||
classes: ["bottom-link"]
|
||||
}]
|
||||
})
|
||||
}, {
|
||||
name: "changelog",
|
||||
title: `${emoji("🎉")} ${loc(obj.lang, 'ChangelogTab')}`,
|
||||
content: popup({
|
||||
embed: true,
|
||||
name: "changelog",
|
||||
header: {
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
title: `${emoji("🪄", 30)} ${loc(obj.lang, 'TitlePopupChangelog')}`
|
||||
},
|
||||
body: [{
|
||||
text: `<div class="category-title">${loc(obj.lang, 'ChangelogLastMajor')}</div>`,
|
||||
raw: true
|
||||
}, {
|
||||
text: loc('en', 'ChangelogContentTitle'),
|
||||
classes: ["changelog-subtitle"],
|
||||
nopadding: true
|
||||
}, {
|
||||
text: loc('en', 'ChangelogContent')
|
||||
}, {
|
||||
text: `<div class="category-title">${loc(obj.lang, 'ChangelogLastCommit')}</div>`,
|
||||
raw: true
|
||||
}, {
|
||||
text: `${com[0]} (${obj.hash})`,
|
||||
classes: ["changelog-subtitle"],
|
||||
nopadding: true
|
||||
}, {
|
||||
text: com[1]
|
||||
}, {
|
||||
text: backdropLink(`${repo}/commits`, loc(obj.lang, 'LinkGitHubChanges')),
|
||||
classes: ["bottom-link"]
|
||||
}]
|
||||
})
|
||||
}, {
|
||||
name: "donate",
|
||||
title: `${emoji("💰")} ${loc(obj.lang, 'DonationsTab')}`,
|
||||
content: popup({
|
||||
embed: true,
|
||||
name: "donate",
|
||||
header: {
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
title: emoji("💸", 30) + loc(obj.lang, 'TitlePopupDonate'),
|
||||
subtitle: loc(obj.lang, 'DonateSubtitle')
|
||||
},
|
||||
body: [{
|
||||
text: donateLinks,
|
||||
raw: true
|
||||
}, {
|
||||
text: loc(obj.lang, 'DonateLinksDescription'),
|
||||
classes: ["explanation"]
|
||||
}, {
|
||||
text: donate.replace(/REPLACEME/g, loc(obj.lang, 'ClickToCopy'))
|
||||
}, {
|
||||
text: loc(obj.lang, 'DonateDescription'),
|
||||
classes: ["explanation", "no-top-padding"]
|
||||
}, {
|
||||
text: backdropLink(authorInfo.contact, loc(obj.lang, 'LinkDonateContact')),
|
||||
classes: ["bottom-link"]
|
||||
}]
|
||||
})
|
||||
}],
|
||||
})}
|
||||
${multiPagePopup({
|
||||
name: "settings",
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
header: {
|
||||
aboveTitle: {
|
||||
text: `v.${version} ~ ${obj.hash}`,
|
||||
url: repo
|
||||
},
|
||||
title: `${emoji("⚙️", 30)} ${loc(obj.lang, 'TitlePopupSettings')}`
|
||||
},
|
||||
tabs: [{
|
||||
name: "video",
|
||||
title: `${emoji("🎬")} ${loc(obj.lang, 'SettingsVideoTab')}`,
|
||||
content: settingsCategory({
|
||||
name: "downloads",
|
||||
title: loc(obj.lang, 'SettingsDownloadsSubtitle'),
|
||||
body: switcher({
|
||||
name: "quality",
|
||||
subtitle: loc(obj.lang, 'SettingsQualitySubtitle'),
|
||||
explanation: loc(obj.lang, 'SettingsQualityDescription'),
|
||||
items: [{
|
||||
"action": "max",
|
||||
"text": loc(obj.lang, 'SettingsQualitySwitchMax')
|
||||
}, {
|
||||
"action": "hig",
|
||||
"text": `${loc(obj.lang, 'SettingsQualitySwitchHigh')}(${quality.hig}p)`
|
||||
}, {
|
||||
"action": "mid",
|
||||
"text": `${loc(obj.lang, 'SettingsQualitySwitchMedium')}(${quality.mid}p)`
|
||||
}, {
|
||||
"action": "low",
|
||||
"text": `${loc(obj.lang, 'SettingsQualitySwitchLow')}(${quality.low}p)`
|
||||
}]
|
||||
})
|
||||
}) + `${!isIOS ? checkbox("downloadPopup", loc(obj.lang, 'SettingsEnableDownloadPopup'), loc(obj.lang, 'AccessibilityEnableDownloadPopup')) : ''}`
|
||||
+ settingsCategory({
|
||||
name: "youtube",
|
||||
body: switcher({
|
||||
name: "ytFormat",
|
||||
subtitle: loc(obj.lang, 'SettingsFormatSubtitle'),
|
||||
explanation: loc(obj.lang, 'SettingsFormatDescription'),
|
||||
items: [{
|
||||
"action": "mp4"
|
||||
}, {
|
||||
"action": "webm"
|
||||
}]
|
||||
})
|
||||
})
|
||||
}, {
|
||||
name: "audio",
|
||||
title: `${emoji("🎶")} ${loc(obj.lang, 'SettingsAudioTab')}`,
|
||||
content: settingsCategory({
|
||||
name: "general",
|
||||
title: loc(obj.lang, 'SettingsAudioTab'),
|
||||
body: switcher({
|
||||
name: "audioFormat",
|
||||
subtitle: loc(obj.lang, 'SettingsFormatSubtitle'),
|
||||
explanation: loc(obj.lang, 'SettingsAudioFormatDescription'),
|
||||
items: audioFormats
|
||||
})
|
||||
})
|
||||
}, {
|
||||
name: "other",
|
||||
title: `${emoji("🪅")} ${loc(obj.lang, 'SettingsOtherTab')}`,
|
||||
content: settingsCategory({
|
||||
name: "appearance",
|
||||
title: loc(obj.lang, 'SettingsAppearanceSubtitle'),
|
||||
body: switcher({
|
||||
name: "theme",
|
||||
subtitle: loc(obj.lang, 'SettingsThemeSubtitle'),
|
||||
items: [{
|
||||
"action": "auto",
|
||||
"text": loc(obj.lang, 'SettingsThemeAuto')
|
||||
},{
|
||||
"action": "dark",
|
||||
"text": loc(obj.lang, 'SettingsThemeDark')
|
||||
},{
|
||||
"action": "light",
|
||||
"text": loc(obj.lang, 'SettingsThemeLight')
|
||||
}]
|
||||
})
|
||||
}) + checkbox("alwaysVisibleButton", loc(obj.lang, 'SettingsKeepDownloadButton'), loc(obj.lang, 'AccessibilityKeepDownloadButton')) + checkbox("disableChangelog", loc(obj.lang, 'SettingsDisableChangelogOnUpdate'), loc(obj.lang, 'SettingsDisableChangelogOnUpdate'))
|
||||
}],
|
||||
})}
|
||||
${popup({
|
||||
name: "download",
|
||||
header: {
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
subtitle: loc(obj.lang, 'TitlePopupDownload')
|
||||
},
|
||||
body: switcher({
|
||||
name: "download",
|
||||
subtitle: loc(obj.lang, 'DownloadPopupWayToSave'),
|
||||
explanation: `${!isIOS ? loc(obj.lang, 'DownloadPopupDescription') : loc(obj.lang, 'DownloadPopupDescriptionIOS')}`,
|
||||
items: `<a id="pd-download" class="switch full space-right" target="_blank" href="/">${loc(obj.lang, 'Download')}</a>
|
||||
<div id="pd-copy" class="switch full">${loc(obj.lang, 'CopyURL')}</div>`
|
||||
})
|
||||
})}
|
||||
${popup({
|
||||
name: "error",
|
||||
header: {
|
||||
closeAria: loc(obj.lang, 'AccessibilityClosePopup'),
|
||||
title: loc(obj.lang, 'TitlePopupError')
|
||||
},
|
||||
body: `<div id="desc-error"></div>`
|
||||
})}
|
||||
<div id="popup-backdrop" style="visibility: hidden;" onclick="hideAllPopups()"></div>
|
||||
<div id="cobalt-main-box" class="center box" style="visibility: hidden;">
|
||||
<div id="logo-area">${appName}</div>
|
||||
<div id="download-area" class="mobile-center">
|
||||
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="110" autocapitalize="off" placeholder="${loc(obj.lang, 'LinkInput')}" aria-label="${loc(obj.lang, 'AccessibilityInputArea')}" oninput="button()">
|
||||
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${loc(obj.lang, 'AccessibilityDownloadButton')}">
|
||||
</div>
|
||||
</div>
|
||||
<footer id="footer" style="visibility: hidden;">
|
||||
${footerButtons([{
|
||||
name: "about",
|
||||
type: "popup",
|
||||
icon: "?",
|
||||
aria: loc(obj.lang, 'AccessibilityOpenAbout')
|
||||
}, {
|
||||
name: "settings",
|
||||
type: "popup",
|
||||
icon: "+",
|
||||
aria: loc(obj.lang, 'AccessibilityOpenSettings')
|
||||
}, {
|
||||
name: "audioMode",
|
||||
type: "toggle",
|
||||
icon: emoji("✨", 22, 1),
|
||||
aria: loc(obj.lang, 'AccessibilityModeToggle')
|
||||
}]
|
||||
)}
|
||||
</footer>
|
||||
</body>
|
||||
<script type="text/javascript">const loc = {noInternet:"${loc(obj.lang, 'ErrorNoInternet')}", noURLReturned: "${loc(obj.lang, 'ErrorBadFetch')}", toggleDefault:'${emoji("✨")} ${loc(obj.lang, "ModeToggleDefault")}', toggleAudio:'${emoji("🎶")} ${loc(obj.lang, "SettingsFormatSwitchAudio")}'};</script>
|
||||
<script type="text/javascript" src="cobalt.js"></script>
|
||||
</html>`;
|
||||
} catch (err) {
|
||||
return `${loc(obj.lang, 'ErrorPageRenderFail', obj.hash)}`;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export default async function(obj) {
|
||||
let audio = streamData["data"]["dash"]["audio"].filter((a) => {
|
||||
if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
|
||||
}).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
|
||||
return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], time: streamData.data.timelength, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` };
|
||||
return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], time: streamData.data.timelength, audioFilename: `bilibili_${obj.id}_audio`, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` };
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorLengthLimit', maxVideoDuration / 60000) };
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function(obj) {
|
||||
});
|
||||
iteminfo = JSON.parse(iteminfo.body);
|
||||
if (iteminfo['item_list'][0]['video']['play_addr']['url_list'][0]) {
|
||||
return { urls: iteminfo['item_list'][0]['video']['play_addr']['url_list'][0].replace("playwm", "play"), filename: `douyin_${obj.postId}.mp4` };
|
||||
return { urls: iteminfo['item_list'][0]['video']['play_addr']['url_list'][0].replace("playwm", "play"), audioFilename: `douyin_${obj.postId}_audio`, filename: `douyin_${obj.postId}.mp4` };
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorEmptyDownload') };
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ export default async function(obj) {
|
||||
} catch (err) {
|
||||
audio = ''
|
||||
}
|
||||
let id = data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3]
|
||||
if (audio.length > 0) {
|
||||
return { typeId: 2, type: "render", urls: [video, audio], filename: `reddit_${data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3]}.mp4` };
|
||||
return { typeId: 2, type: "render", urls: [video, audio], audioFilename: `reddit_${id}_audio`, filename: `reddit_${id}.mp4` };
|
||||
} else {
|
||||
return { typeId: 1, urls: video};
|
||||
return { typeId: 1, urls: video, audioFilename: loc(obj.lang, 'ErrorEmptyDownload')};
|
||||
}
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorEmptyDownload') };
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function(obj) {
|
||||
});
|
||||
html = html.body;
|
||||
if (html.includes(',"preloadList":[{"url":"')) {
|
||||
return { urls: unicodeDecode(html.split(',"preloadList":[{"url":"')[1].split('","id":"')[0].trim()), filename: `tiktok_${obj.postId}.mp4` };
|
||||
return { urls: unicodeDecode(html.split(',"preloadList":[{"url":"')[1].split('","id":"')[0].trim()), audioFilename: `tiktok_${obj.postId}_audio`, filename: `tiktok_${obj.postId}.mp4` };
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorEmptyDownload') };
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function(obj) {
|
||||
if (html.includes('<!-- GOOGLE CAROUSEL --><script type="application/ld+json">')) {
|
||||
let json = JSON.parse(html.split('<!-- GOOGLE CAROUSEL --><script type="application/ld+json">')[1].split('</script>')[0])
|
||||
if (json["video"] && json["video"]["contentUrl"]) {
|
||||
return json["video"]["contentUrl"]
|
||||
return { urls: json["video"]["contentUrl"], audioFilename: `tumblr_${obj.id}_audio` }
|
||||
} else return { error: loc(obj.lang, 'ErrorEmptyDownload') }
|
||||
} else return { error: loc(obj.lang, 'ErrorBrokenLink', 'tumblr') }
|
||||
} else return { error: loc(obj.lang, 'ErrorBrokenLink', 'tumblr') }
|
||||
|
||||
@@ -39,7 +39,7 @@ export default async function (obj) {
|
||||
if (parsbod.hasOwnProperty("extended_entities") && parsbod["extended_entities"].hasOwnProperty("media")) {
|
||||
if (parsbod["extended_entities"]["media"][0]["type"] === "video" || parsbod["extended_entities"]["media"][0]["type"] === "animated_gif") {
|
||||
let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"]
|
||||
return variants.filter((v) => { if (v["content_type"] == "video/mp4") return true; }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"].split('?')[0];
|
||||
return { urls: variants.filter((v) => { if (v["content_type"] == "video/mp4") return true; }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"].split('?')[0], audioFilename: `twitter_${obj.id}_audio` }
|
||||
} else {
|
||||
return nothing
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ export default async function(obj) {
|
||||
|
||||
let maxQuality = js["player"]["params"][0][selectedQuality].split('type=')[1].slice(0, 1)
|
||||
let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r;})[maxQuality])
|
||||
|
||||
let id = js["player"]["params"][0][selectedQuality].split("id=")[1]
|
||||
if (selectedQuality in js["player"]["params"][0]) {
|
||||
return { url: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${js["player"]["params"][0][selectedQuality].split("id=")[1]}_${attr['width']}x${attr['height']}.mp4` };
|
||||
return { urls: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${js["player"]["params"][0][selectedQuality].split("id=")[1]}_${attr['width']}x${attr['height']}.mp4`, audioFilename: loc(obj.lang, 'ErrorEmptyDownload') };
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorEmptyDownload') };
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default async function (obj) {
|
||||
return { type: "render", urls: [video[0]["url"], audio[0]["url"]], time: video[0]["approxDurationMs"],
|
||||
filename: `youtube_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.${video[0]["container"]}` };
|
||||
} else if (audio.length > 0) {
|
||||
return { type: "bridge", isAudioOnly: true, urls: audio[0]["url"], filename: `youtube_${obj.id}_${audio[0]["audioBitrate"]}kbps.${audio[0]["container"] == "webm" ? "opus" : "m4a"}` };
|
||||
return { type: "bridge", isAudioOnly: true, urls: audio[0]["url"], audioFilename: `youtube_${obj.id}_audio` };
|
||||
} else {
|
||||
return { error: loc(obj.lang, 'ErrorBadFetch') };
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ export function createStream(obj) {
|
||||
ip: iphmac,
|
||||
exp: exp,
|
||||
isAudioOnly: obj.isAudioOnly ? true : false,
|
||||
audioFormat: obj.audioFormat,
|
||||
time: obj.time,
|
||||
lang: obj.lang
|
||||
copy: obj.copy
|
||||
});
|
||||
return `${process.env.selfURL}api/stream?t=${streamUUID}&e=${exp}&h=${ghmac}`;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ import { apiJSON } from "../sub/utils.js";
|
||||
import { verifyStream } from "./manage.js";
|
||||
import { streamAudioOnly, streamDefault, streamLiveRender } from "./types.js";
|
||||
|
||||
export default function(res, ip, id, hmac, exp) {
|
||||
export default function(res, ip, id, hmac, exp, lang) {
|
||||
try {
|
||||
let streamInfo = verifyStream(ip, id, hmac, exp, process.env.streamSalt);
|
||||
if (!streamInfo.error) {
|
||||
if (streamInfo.isAudioOnly && streamInfo.type == "render") {
|
||||
if (streamInfo.isAudioOnly && streamInfo.type != "bridge") {
|
||||
streamAudioOnly(streamInfo, res);
|
||||
} else {
|
||||
switch (streamInfo.type) {
|
||||
case "render":
|
||||
streamLiveRender(streamInfo, res);
|
||||
streamLiveRender(streamInfo, res, lang);
|
||||
break;
|
||||
default:
|
||||
streamDefault(streamInfo, res);
|
||||
streamDefault(streamInfo, res, lang);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import loc from "../../localization/manager.js";
|
||||
|
||||
export async function streamDefault(streamInfo, res) {
|
||||
try {
|
||||
res.setHeader('Content-disposition', `attachment; filename="${streamInfo.filename}"`);
|
||||
res.setHeader('Content-disposition', `attachment; filename="${streamInfo.isAudioOnly ? `${streamInfo.filename}.${streamInfo.audioFormat}` : streamInfo.filename}"`);
|
||||
const stream = got.get(streamInfo.urls, {
|
||||
headers: {
|
||||
"user-agent": genericUserAgent
|
||||
@@ -25,7 +25,7 @@ export async function streamDefault(streamInfo, res) {
|
||||
internalError(res);
|
||||
}
|
||||
}
|
||||
export async function streamLiveRender(streamInfo, res) {
|
||||
export async function streamLiveRender(streamInfo, res, lang) {
|
||||
try {
|
||||
if (streamInfo.urls.length == 2) {
|
||||
let headers = {};
|
||||
@@ -69,7 +69,7 @@ export async function streamLiveRender(streamInfo, res) {
|
||||
ffmpegProcess.kill();
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({ status: "error", text: loc(streamInfo.lang, 'ErrorCorruptedStream') });
|
||||
res.status(400).json({ status: "error", text: loc(lang, 'ErrorCorruptedStream') });
|
||||
}
|
||||
} catch (e) {
|
||||
internalError(res);
|
||||
@@ -82,15 +82,15 @@ export async function streamAudioOnly(streamInfo, res) {
|
||||
headers = { "user-agent": genericUserAgent };
|
||||
}
|
||||
const audio = got.get(streamInfo.urls, { isStream: true, headers: headers });
|
||||
let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
|
||||
let args = [
|
||||
'-loglevel', '-8',
|
||||
'-i', 'pipe:3',
|
||||
'-vn',
|
||||
];
|
||||
args = args.concat(ffmpegArgs[format])
|
||||
if (streamInfo.time) args.push('-t', msToTime(streamInfo.time));
|
||||
args.push('-f', format, 'pipe:4');
|
||||
|
||||
'-vn'
|
||||
]
|
||||
let arg = streamInfo.copy ? ffmpegArgs["copy"] : ffmpegArgs["audio"]
|
||||
args = args.concat(arg)
|
||||
if (ffmpegArgs[streamInfo.audioFormat]) args = args.concat(ffmpegArgs[streamInfo.audioFormat]);
|
||||
args.push('-f', streamInfo.audioFormat == "m4a" ? "ipod" : streamInfo.audioFormat, 'pipe:4');
|
||||
const ffmpegProcess = spawn(ffmpeg, args, {
|
||||
windowsHide: true,
|
||||
stdio: [
|
||||
@@ -104,7 +104,7 @@ export async function streamAudioOnly(streamInfo, res) {
|
||||
audio.on('error', (err) => {
|
||||
ffmpegProcess.kill();
|
||||
});
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}.${streamInfo.audioFormat}"`);
|
||||
ffmpegProcess.stdio[4].pipe(res);
|
||||
audio.pipe(ffmpegProcess.stdio[3]).on('error', (err) => {
|
||||
ffmpegProcess.kill();
|
||||
|
||||
68
src/modules/sub/matchActionDecider.js
Normal file
68
src/modules/sub/matchActionDecider.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { supportedAudio } from "../config.js"
|
||||
import { apiJSON } from "./utils.js"
|
||||
|
||||
export default function(r, host, ip, audioFormat, isAudioOnly) {
|
||||
if (!r.error) {
|
||||
if (!isAudioOnly) {
|
||||
switch (host) {
|
||||
case "twitter":
|
||||
return apiJSON(1, { u: r.urls })
|
||||
case "vk":
|
||||
return apiJSON(2, {
|
||||
type: "bridge", u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
})
|
||||
case "bilibili":
|
||||
return apiJSON(2, {
|
||||
type: "render", u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt,
|
||||
time: r.time
|
||||
})
|
||||
case "youtube":
|
||||
return apiJSON(2, {
|
||||
type: r.type, u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt,
|
||||
time: r.time,
|
||||
})
|
||||
case "reddit":
|
||||
return apiJSON(r.typeId, {
|
||||
type: r.type, u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
})
|
||||
case "tiktok":
|
||||
return apiJSON(2, {
|
||||
type: "bridge", u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
})
|
||||
case "douyin":
|
||||
return apiJSON(2, {
|
||||
type: "bridge", u: r.urls, service: host, ip: ip,
|
||||
filename: r.filename, salt: process.env.streamSalt
|
||||
})
|
||||
case "tumblr":
|
||||
return apiJSON(1, { u: r.urls })
|
||||
}
|
||||
} else {
|
||||
let type = "render"
|
||||
let copy = false
|
||||
if (!supportedAudio.includes(audioFormat)) audioFormat = "best";
|
||||
if (audioFormat == "best") {
|
||||
if (host != "youtube") {
|
||||
audioFormat = "m4a"
|
||||
copy = true
|
||||
} else {
|
||||
audioFormat = "opus"
|
||||
type = "bridge"
|
||||
}
|
||||
}
|
||||
if (host == "reddit" && r.typeId == 1 || host == "vk") return apiJSON(0, { t: r.audioFilename });
|
||||
return apiJSON(2, {
|
||||
type: type,
|
||||
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,
|
||||
filename: r.audioFilename, salt: process.env.streamSalt, isAudioOnly: true, audioFormat: audioFormat, copy: copy
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return apiJSON(0, { t: r.error });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user