7.0: ui refresh and more

This commit is contained in:
wukko
2023-08-05 00:43:12 +06:00
parent 38ceb1be77
commit 43a3ebf475
23 changed files with 838 additions and 526 deletions

View File

@@ -1,14 +1,10 @@
import * as esbuild from "esbuild";
import * as fs from "fs";
import { loadLoc, languageList } from "../localization/manager.js";
import { cleanHTML } from "./sub/utils.js";
import page from "./pageRender/page.js";
function cleanHTML(html) {
let clean = html.replace(/ {4}/g, '');
clean = clean.replace(/\n/g, '');
return clean
}
export async function buildFront(commitHash, branch) {
try {
// preload localization files

View File

@@ -1,11 +1,18 @@
{
"current": {
"version": "7.0",
"date": "August 4, 2023",
"title": "wip: ui refresh and more!",
"banner": "cattired.webp",
"content": "hey beta testers, this changelog isn't final but i do want to highlight some changes here just to keep track of them. make sure to report all issues in the testing discord channel!\n\n(this changelog is not sorted as it usually is)\n\nservice improvements:\n*; fixed unexpected stream drop when downloading a silent reddit video with mute mode on.\n*; added support for new reddit audio link type.\n\nweb improvements:\n*; removed 6.0 api fallback.\n*; moved on demand blocks to web server, now changelog can be updated independently from preferred api server.\n*; all-new matte glass aesthetic, applied to revamped popup headers, tab selectors, and also small popups.\n*; optimized installed web app to look and act like a native app, especially on ios. !!!!please try this!!!!\n*; added ability to attach a date to changelog.\n*; refreshed the look of entire changelog tab: separated title and version/commit, made title bigger, evened out all paddings.\n*; popups now work without any weird workarounds, especially on mobile. they're clean and nice.\n*; homescreen now also works without any weird workarounds. it is also clean and nice.\n*; replaced close button with back button, moved it to left. it makes more sense.\n*; (kinda old but not in older changelog) absolutely reimagined error and download popups, consistent with the rest of refreshed design.\n*; reduced spacing, optimized css of almost all ui elements. should be even more consistent across platforms now.\n*; added interaction animations.\n*; added more accessibility options, put them all into one category. you can disable animations and transparency if you want to.\n*; added a link to self-troubleshooting guide to support expand list in about popup.\n*; renamed 2160p and 4320p to 4k and 8k respectfully for better clarity.\n*; cobalt now lets you know if your browser doesn't support clipboard api and helps you fix it.\n*; added ability to translate \"cobalt\" for twitter-like localization. in russian cobalt is now кобальт, that's the style i will be going with from now on.\n*; updated some localization strings.\n*; removed ability to change the app name dynamically in all locations. cobalt is a sustained product name.\n*; \n*; added more keyboard shorcuts:\nshift+d: paste and download,\nshift+k: auto mode,\nshift+L: audio mode,\nshift+b: about popup,\nshift+n: donate popup,\nshift+m: settings popup.\n\non top of existing ones:\nctrl+v (without focusing anything): paste the link;\nescape/delete/clear: clear url input area\nescape: close current popup;\n\nyour keyboard slightly represents cobalt's ui. let me know if you like these.\n\ninternal web improvements:\n*; cleaned up all related frontend modules, especially page.js. will add more in final changelog, i'm very tired.\n\napi improvements:\n*; now catching all json api related errors.\n*; moved on demand blocks to web server.\n*; now sending standard rate limiting headers.\n*; better readability in source.\n\nother improvements:\n*; renamed docker-compose.yml.example to docker-compose.example.yml for linting in code editors.\n*; added a wiki with wip troubleshooting guide on github.\n\nwhat doesn't work or works poorly:\n*; tiktok/twitter media pickers look like shit, they haven't been worked on yet. they also might not work at all on ios.\n*; unknown if scrolling within popups works properly on ios 16 (when installed as web app).\n*; \"ask how to save\" toggle is pressable on ios devices even though it shouldn't be.\n\nwhat will surely be added in coming days:\n*; list of all keyboard shortcuts, probably a popup opened with a little button in left or right corner of the screen.\n*; proper dropdown arrow for about tab dropdowns.\n*; dates for all older changelogs.\n*; ...more?"
},
"history": [{
"version": "6.2",
"date": "June 27 2023",
"title": "all network issues have been fixed!",
"banner": "meowthhammer.webp",
"content": "hey! there have been some hiccups in cobalt's stability lately, i was going through finals while trying to scale up the infrastructure, and that didn't really work out, lol.\nBUT i'm happy to announce that i've optimized all nodes! <span class='text*;backdrop'>there should no longer be any networking issues</span>.\n\nenjoy stable experience while i work in background to make cobalt even better :)\n\nhere's what's new in this update:\n*; better button contrast in both themes. \n*; button highlight in light theme now actually looks like a highlight.\n*; removed ip gate for streamables and updated privacy policy to reflect this change.\n*; streamable links now last for 20 seconds instead of 2 minutes.\n*; cleaned up stream verification algorithm. now the same function doesn't run 4 times in a row.\n*; removed deprecated way of hosting a cobalt instance.\n\nthank you for sticking with cobalt, and i hope you have a great day :D"
},
"history": [{
}, {
"version": "6.0",
"title": "better reliability, new infrastructure, pinterest support, and way more!",
"banner": "catswitchboxes.webp",

View File

@@ -6,8 +6,12 @@ let changelog = loadJSON('./src/modules/changelog/changelog.json')
export default function(string) {
try {
switch (string) {
case "version":
return `<span class="text-backdrop changelog-tag-version">v.${changelog["current"]["version"]}</span>${
changelog["current"]["date"] ? `<span class="changelog-tag-date">· ${changelog["current"]["date"]}</span>` : ''
}`
case "title":
return `<span class="text-backdrop">${changelog["current"]["version"]}:</span> ${replaceBase(changelog["current"]["title"])}`;
return replaceBase(changelog["current"]["title"]);
case "banner":
return changelog["current"]["banner"] ? `updateBanners/${changelog["current"]["banner"]}` : false;
case "content":
@@ -15,9 +19,11 @@ export default function(string) {
case "history":
return changelog["history"].map((i) => {
return {
title: `<span class="text-backdrop">${i["version"]}:</span> ${replaceBase(i["title"])}`,
title: replaceBase(i["title"]),
version: `<span class="text-backdrop changelog-tag-version">v.${i["version"]}</span>${
i["date"] ? `<span class="changelog-tag-date">· ${i["date"]}</span>` : ''
}`,
content: replaceBase(i["content"]),
version: i["version"],
banner: i["banner"] ? `updateBanners/${i["banner"]}` : false,
}
});

View File

@@ -6,7 +6,6 @@ const servicesConfigJson = loadJson("./src/modules/processing/servicesConfig.jso
export const
services = servicesConfigJson.config,
audioIgnore = servicesConfigJson.audioIgnore,
appName = packageJson.name,
version = packageJson.version,
streamLifespan = config.streamLifespan,
maxVideoDuration = config.maxVideoDuration,

View File

@@ -1,6 +1,10 @@
import { celebrations } from "../config.js";
import emoji from "../emoji.js";
export const backButtonSVG = `<svg width="22" height="22" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.7551 27.5102L3 15.7551L14.7551 4L16.7755 5.99417L8.45773 14.312H30V17.1982H8.45773L16.7755 25.4898L14.7551 27.5102Z" fill="#FFFFFF"/>
</svg>`
export function switcher(obj) {
let items = ``;
if (obj.name === "download") {
@@ -19,26 +23,18 @@ export function switcher(obj) {
${obj.explanation ? `<div class="explanation">${obj.explanation}</div>` : ``}
</div>`
}
export function checkbox(obj) {
let paddings = ["bottom-margin", "top-margin", "no-margin", "top-margin-only"];
let checkboxes = ``;
for (let i = 0; i < obj.length; i++) {
let paddingClass = obj[i].padding && paddings.includes(obj[i].padding) ? ` ${obj[i].padding}` : '';
export function checkbox(action, text, paddingType, aria) {
let paddingClass = ` `
switch (paddingType) {
case 1:
paddingClass += "bottom-margin"
break;
case 2:
paddingClass += "top-margin"
break;
case 3:
paddingClass += "no-margin"
break;
case 4:
paddingClass += "top-margin-only"
checkboxes += `<label id="${obj[i].action}-chkbx" class="checkbox${paddingClass}">
<input id="${obj[i].action}" type="checkbox" aria-label="${obj[i].aria ? obj[i].aria : obj[i].name}" onclick="checkbox('${obj[i].action}')">
<span>${obj[i].name}</span>
</label>`
}
return `<label id="${action}-chkbx" class="checkbox${paddingClass}">
<input id="${action}" type="checkbox" ${aria ? `aria-label="${aria}"` : `aria-label="${text}"`} onclick="checkbox('${action}')">
<span>${text}</span>
</label>`
return checkboxes
}
export function sep(paddingType) {
let paddingClass = ``
@@ -50,7 +46,7 @@ export function sep(paddingType) {
return `<div class="separator${paddingClass}"></div>`
}
export function popup(obj) {
let classes = obj.classes ? obj.classes : []
let classes = obj.classes ? obj.classes : [];
let body = obj.body;
if (Array.isArray(obj.body)) {
body = ``
@@ -65,37 +61,44 @@ export function popup(obj) {
}
}
return `
${obj.standalone ? `<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.standalone && !obj.buttonOnly ? `<button id="close-button" class="switch up" onclick="popup('${obj.name}', 0)" ${obj.header.closeAria ? `aria-label="${obj.header.closeAria}"` : ''}>x</button>` : ''}
${obj.buttonOnly ? obj.header.emoji : ``}
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" 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>` : ''}
${obj.standalone ? `<div id="popup-${obj.name}" class="popup center${!obj.buttonOnly ? " box": ''}${classes.length > 0 ? ' ' + classes.join(' ') : ''}">` : ''}
<div id="popup-header" class="popup-header${!obj.buttonOnly ? " glass-bkg": ''}">
<div id="popup-header-contents">
${obj.buttonOnly ? obj.header.emoji : ``}
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" 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>
<div id="popup-content"${obj.footer ? ' class="with-footer"' : ''}>
<div id="popup-content" class="popup-content-inner">
${body}${obj.buttonOnly ? `<button id="close-error" class="switch" onclick="popup('${obj.name}', 0)">${obj.buttonText}</button>` : ''}
</div>
${obj.footer ? `<div id="popup-footer" class="popup-footer">
<a id="popup-bottom" class="popup-footer-content" target="_blank" href="${obj.footer.url}">${obj.footer.text}</a>
</div>` : ''}
${obj.standalone ? `</div>` : ''}`
}
export function multiPagePopup(obj) {
let tabs = ``
let tabContent = ``
let tabs = `
<button id="back-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>
${backButtonSVG}
</button>`;
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>`
}
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" target="_blank" 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"><div class="switches popup-tabs-child">${tabs}</div><button id="close-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>x</button></div>
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${obj.header ? `<div id="popup-header" class="popup-header glass-bkg">
<div id="popup-header-contents">
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" 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>` : ''}${tabContent}</div>
<div id="popup-tabs" class="switches popup-tabs glass-bkg"><div class="switches popup-tabs-child">${tabs}</div></div>
</div>`
}
export function collapsibleList(arr) {
@@ -112,24 +115,28 @@ export function collapsibleList(arr) {
return items;
}
export function popupWithBottomButtons(obj) {
let tabs = ``
let tabs = `
<button id="back-button" class="switch tab-${obj.name}" onclick="popup('${obj.name}', 0)" ${obj.closeAria ? `aria-label="${obj.closeAria}"` : ''}>
${backButtonSVG}
</button>`
for (let i = 0; i < obj.buttons.length; i++) {
tabs += obj.buttons[i]
}
tabs += `<button id="close-button" 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" target="_blank" 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>` : ''}
${obj.header.explanation ? `<div class="explanation">${obj.header.explanation}</div>` : ''}</div>` : ''}${obj.content}</div>
<div id="popup-buttons" class="switches popup-tabs">${tabs}</div>
<div id="popup-${obj.name}" class="popup center box scrollable">
<div id="popup-content">
${obj.header ? `<div id="popup-header" class="popup-header glass-bkg">
<div id="popup-header-contents">
${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" 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>` : ''}
${obj.header.explanation ? `<div class="explanation">${obj.header.explanation}</div>` : ''}
</div>
</div>` : ''}${obj.content}</div>
<div id="popup-tabs" class="switches popup-tabs glass-bkg"><div id="picker-buttons" class="switches popup-tabs-child">${tabs}</div></div>
</div>`
}
export function backdropLink(link, text) {
return `<a class="text-backdrop italic" href="${link}" target="_blank">${text}</a>`
}
export function socialLink(emji, name, handle, url) {
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop italic" href="${url}" target="_blank">${handle}</a></div>`
}

View File

@@ -1,4 +1,5 @@
import changelogManager from "../changelog/changelogManager.js"
import { cleanHTML } from "../sub/utils.js";
let cache = {}
@@ -10,8 +11,16 @@ export function changelogHistory() { // blockId 0
let historyLen = history.length;
for (let i in history) {
let separator = (i !== 0 && i !== historyLen) ? '<div class="separator"></div>' : '';
render += `${separator}${history[i]["banner"] ? `<div class="changelog-banner"><img class="changelog-img" src="${history[i]["banner"]}" onerror="this.style.display='none'" loading="lazy"></img></div>` : ''}<div id="popup-desc" class="changelog-subtitle">${history[i]["title"]}</div><div id="popup-desc" class="desc-padding">${history[i]["content"]}</div>`
render += `
${separator}${history[i]["banner"] ? `<div class="changelog-banner">
<img class="changelog-img" src="${history[i]["banner"]}" onerror="this.style.display='none'" loading="lazy"></img>
</div>` : ''}
<div id="popup-desc" class="changelog-tags">${history[i]["version"]}</div>
<div id="popup-desc" class="changelog-subtitle">${history[i]["title"]}</div>
<div id="popup-desc" class="desc-padding">${history[i]["content"]}</div>`
}
render = cleanHTML(render);
cache['0'] = render;
return render;
}

View File

@@ -1,5 +1,5 @@
import { backdropLink, checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink } from "./elements.js";
import { services as s, appName, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { getCommitInfo } from "../sub/currentCommit.js";
import loc from "../../localization/manager.js";
import emoji from "../emoji.js";
@@ -30,6 +30,7 @@ for (let i in donations["crypto"]) {
export default function(obj) {
const t = (str, replace) => { return loc(obj.lang, str, replace) };
let ua = obj.useragent.toLowerCase();
let isIOS = ua.match("iphone os");
let isMobile = ua.match("android") || ua.match("iphone os");
@@ -40,26 +41,32 @@ export default function(obj) {
audioFormats[0]["text"] = t('SettingsAudioFormatBest');
try {
return `<!DOCTYPE html>
return `
<!DOCTYPE html>
<html lang="${obj.lang}">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="viewport-fit=cover width=device-width, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1, maximum-scale=${isIOS ? `1` : `5`}" />
<title>${appName}</title>
<title>${t("AppTitleCobalt")}</title>
<meta property="og:url" content="${process.env.webURL || process.env.selfURL}" />
<meta property="og:title" content="${appName}" />
<meta property="og:title" content="${t("AppTitleCobalt")}" />
<meta property="og:description" content="${t('EmbedBriefDescription')}" />
<meta property="og:image" content="${process.env.webURL || process.env.selfURL}icons/generic.png" />
<meta name="title" content="${appName}" />
<meta name="title" content="${t("AppTitleCobalt")}" />
<meta name="description" content="${t('AboutSummary')}" />
<meta name="theme-color" content="#000000" />
<meta name="twitter:card" content="summary" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="${t("AppTitleCobalt")}">
<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" />
@@ -69,9 +76,10 @@ export default function(obj) {
<noscript><div style="margin: 2rem;">${t('NoScriptMessage')}</div></noscript>
</head>
<body id="cobalt-body" ${platform === "p" ? 'class="desktop"' : ''} data-nosnippet ontouchstart>
<body id="notification-area"></div>
${multiPagePopup({
name: "about",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
tabs: [{
name: "about",
title: `${emoji("🐲")} ${t('AboutTab')}`,
@@ -82,30 +90,40 @@ export default function(obj) {
text: t('MadeWithLove'),
url: authorInfo.link
},
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: `${emoji("🔮", 30)} ${t('TitlePopupAbout')}`
},
body: [{
text: t('AboutSummary')
}, {
text: collapsibleList([{
"name": "services",
"title": t("CollapseServices"),
"body": `${enabledServices}<br/><br/>${t("ServicesNote")}`
name: "services",
title: t("CollapseServices"),
body: `${enabledServices}<br/><br/>${t("ServicesNote")}`
}, {
"name": "support",
"title": t("CollapseSupport"),
"body": `${t("FollowSupport")}<br/>
${socialLink(emoji("🐦"), "twitter", authorInfo.support.twitter.handle, authorInfo.support.twitter.url)}
${socialLink(emoji("👾"), "discord", authorInfo.support.discord.handle, authorInfo.support.discord.url)}
${socialLink(emoji("🐘"), "mastodon", authorInfo.support.mastodon.handle, authorInfo.support.mastodon.url)}<br/>
name: "support",
title: t("CollapseSupport"),
body: `
${t("SupportSelfTroubleshooting")}<br/>
${t("FollowSupport")}<br/>
${socialLink(
emoji("🐦"), "twitter", authorInfo.support.twitter.handle, authorInfo.support.twitter.url
)}
${socialLink(
emoji("👾"), "discord", authorInfo.support.discord.handle, authorInfo.support.discord.url
)}
${socialLink(
emoji("🐘"), "mastodon", authorInfo.support.mastodon.handle, authorInfo.support.mastodon.url
)}<br/>
${t("SourceCode")}<br/>
${socialLink(emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo)}<br/>
${socialLink(
emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo
)}<br/>
${t("SupportNote")}`
}, {
"name": "privacy",
"title": t("CollapsePrivacy"),
"body": t("PrivacyPolicy")
name: "privacy",
title: t("CollapsePrivacy"),
body: t("PrivacyPolicy")
}])
}]
})
@@ -115,7 +133,7 @@ export default function(obj) {
content: popup({
name: "changelog",
header: {
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: `${emoji("🪄", 30)} ${t('TitlePopupChangelog')}`
},
body: [{
@@ -123,8 +141,14 @@ export default function(obj) {
raw: true
}, {
text: changelogManager("banner") ?
`<div class="changelog-banner"><img class="changelog-img" src="${changelogManager("banner")}" onerror="this.style.display='none'" loading="lazy"></img></div>`: '',
`<div class="changelog-banner">
<img class="changelog-img" src="${changelogManager("banner")}" onerror="this.style.display='none'" loading="lazy"></img>
</div>`: '',
raw: true
}, {
text: changelogManager("version"),
classes: ["changelog-tags"],
nopadding: true
}, {
text: changelogManager("title"),
classes: ["changelog-subtitle"],
@@ -132,19 +156,26 @@ export default function(obj) {
}, {
text: changelogManager("content")
}, {
text: `${sep()}<span class="text-backdrop">${obj.hash}:</span> ${com[0]}`,
text: sep(),
raw: true
},{
text: `<a class="text-backdrop changelog-tag-version" href="${repo}/commit/${obj.hash}">#${obj.hash}</a>`,
classes: ["changelog-tags"],
nopadding: true
}, {
text: com[0],
classes: ["changelog-subtitle"],
nopadding: true
}, {
text: com[1]
}, {
text: backdropLink(`${repo}/commits`, t('LinkGitHubChanges')),
classes: ["bottom-link"]
}, {
text: `<div class="category-title">${t('ChangelogOlder')}</div>`,
raw: true
}, {
text: `<div id="changelog-history"><button class="switch bottom-margin" onclick="loadOnDemand('changelog-history', '0')">${t("ChangelogPressToExpand")}</button></div>`,
text: `
<div id="changelog-history">
<button class="switch bottom-margin" onclick="loadOnDemand('changelog-history', '0')">${t("ChangelogPressToExpand")}</button>
</div>`,
raw: true
}]
})
@@ -154,14 +185,17 @@ export default function(obj) {
content: popup({
name: "donate",
header: {
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
title: emoji("💸", 30) + t('TitlePopupDonate')
},
body: [{
text: `<div class="category-title">${t('DonateSub')}</div>`,
raw: true
}, {
text: `<div class="changelog-banner"><img class="changelog-img" src="updateBanners/catsleep.webp" onerror="this.style.display='none'" loading="lazy"></img></div>`,
text: `
<div class="changelog-banner">
<img class="changelog-img" src="updateBanners/catsleep.webp" onerror="this.style.display='none'" loading="lazy"></img>
</div>`,
raw: true
}, {
text: t('DonateExplanation')
@@ -189,7 +223,7 @@ export default function(obj) {
})}
${multiPagePopup({
name: "settings",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
header: {
aboveTitle: {
text: `v.${version}-${obj.hash}${platform} (${obj.branch})`,
@@ -207,33 +241,37 @@ export default function(obj) {
name: "vQuality",
explanation: t('SettingsQualityDescription'),
items: [{
"action": "max",
"text": "4320p+"
action: "max",
text: "8k+"
}, {
"action": "2160",
"text": "2160p"
action: "2160",
text: "4k"
}, {
"action": "1440",
"text": "1440p"
action: "1440",
text: "1440p"
}, {
"action": "1080",
"text": "1080p"
action: "1080",
text: "1080p"
}, {
"action": "720",
"text": "720p"
action: "720",
text: "720p"
}, {
"action": "480",
"text": "480p"
action: "480",
text: "480p"
}, {
"action": "360",
"text": "360p"
action: "360",
text: "360p"
}]
})
})
+ settingsCategory({
name: "tiktok",
title: "tiktok",
body: checkbox("disableTikTokWatermark", t('SettingsRemoveWatermark'), 3)
body: checkbox([{
action: "disableTikTokWatermark",
name: t("SettingsRemoveWatermark"),
padding: "no-margin"
}])
})
+ settingsCategory({
name: t('SettingsCodecSubtitle'),
@@ -241,14 +279,14 @@ export default function(obj) {
name: "vCodec",
explanation: t('SettingsCodecDescription'),
items: [{
"action": "h264",
"text": "h264 (mp4)"
action: "h264",
text: "h264 (mp4)"
}, {
"action": "av1",
"text": "av1 (mp4)"
action: "av1",
text: "av1 (mp4)"
}, {
"action": "vp9",
"text": "vp9 (webm)"
action: "vp9",
text: "vp9 (webm)"
}]
})
})
@@ -258,11 +296,11 @@ export default function(obj) {
name: "vimeoDash",
explanation: t('SettingsVimeoPreferDescription'),
items: [{
"action": "false",
"text": "progressive"
action: "false",
text: "progressive"
}, {
"action": "true",
"text": "dash"
action: "true",
text: "dash"
}]
})
})
@@ -272,31 +310,44 @@ export default function(obj) {
content: settingsCategory({
name: "general",
title: t('SettingsFormatSubtitle'),
body:
switcher({
name: "aFormat",
explanation: t('SettingsAudioFormatDescription'),
items: audioFormats
}) + sep(0) + checkbox("muteAudio", t('SettingsVideoMute'), 3) + explanation(t('SettingsVideoMuteExplanation'))
}) + settingsCategory({
name: "dub",
title: t("SettingsAudioDub"),
body: switcher({
name: "dubLang",
explanation: t('SettingsAudioDubDescription'),
items: [{
"action": "original",
"text": t('SettingsDubDefault')
}, {
"action": "auto",
"text": t('SettingsDubAuto')
}]
})
}) + settingsCategory({
name: "tiktok",
title: "tiktok",
body: checkbox("fullTikTokAudio", t('SettingsAudioFullTikTok'), 3) + explanation(t('SettingsAudioFullTikTokDescription'))
})
body: switcher({
name: "aFormat",
explanation: t('SettingsAudioFormatDescription'),
items: audioFormats
})
+ sep(0)
+ checkbox([{
action: "muteAudio",
name: t("SettingsVideoMute"),
padding: "no-margin"
}])
+ explanation(t('SettingsVideoMuteExplanation'))
})
+ settingsCategory({
name: "dub",
title: t("SettingsAudioDub"),
body: switcher({
name: "dubLang",
explanation: t('SettingsAudioDubDescription'),
items: [{
action: "original",
text: t('SettingsDubDefault')
}, {
action: "auto",
text: t('SettingsDubAuto')
}]
})
})
+ settingsCategory({
name: "tiktok",
title: "tiktok",
body: checkbox([{
action: "fullTikTokAudio",
name: t("SettingsAudioFullTikTok"),
padding: "no-margin"
}])
+ explanation(t('SettingsAudioFullTikTokDescription'))
})
}, {
name: "other",
title: `${emoji("🪅")} ${t('SettingsOtherTab')}`,
@@ -307,26 +358,51 @@ export default function(obj) {
name: "theme",
subtitle: t('SettingsThemeSubtitle'),
items: [{
"action": "auto",
"text": t('SettingsThemeAuto')
action: "auto",
text: t('SettingsThemeAuto')
}, {
"action": "dark",
"text": t('SettingsThemeDark')
action: "dark",
text: t('SettingsThemeDark')
}, {
"action": "light",
"text": t('SettingsThemeLight')
action: "light",
text: t('SettingsThemeLight')
}]
}) + checkbox("alwaysVisibleButton", t('SettingsKeepDownloadButton'), 4, t('AccessibilityKeepDownloadButton'))
}) + settingsCategory({
})
})
+ settingsCategory({
name: "accessibility",
title: t('Accessibility'),
body: checkbox([{
action: "alwaysVisibleButton",
name: t("SettingsKeepDownloadButton"),
aria: t("AccessibilityKeepDownloadButton")
}, {
action: "reduceTransparency",
name: t("SettingsReduceTransparency")
}, {
action: "disableAnimations",
name: t("SettingsDisableAnimations"),
padding: "no-margin"
}])
})
+ settingsCategory({
name: "miscellaneous",
title: t('Miscellaneous'),
body: checkbox("disableChangelog", t('SettingsDisableNotifications')) + `${!isIOS ? checkbox("downloadPopup", t('SettingsEnableDownloadPopup'), 1, t('AccessibilityEnableDownloadPopup')) : ''}`
body: checkbox([{
action: "disableChangelog",
name: t("SettingsDisableNotifications")
}, {
action: "downloadPopup",
name: t("SettingsEnableDownloadPopup"),
padding: "no-margin",
aria: t("AccessibilityEnableDownloadPopup")
}])
})
}],
})}
${popupWithBottomButtons({
name: "picker",
closeAria: t('AccessibilityClosePopup'),
closeAria: t('AccessibilityGoBack'),
header: {
title: `<div id="picker-title"></div>`,
explanation: `<div id="picker-subtitle"></div>`,
@@ -334,100 +410,109 @@ export default function(obj) {
buttons: [`<a id="picker-download" class="switch" target="_blank" href="/">${t('ImagePickerDownloadAudio')}</a>`],
content: '<div id="picker-holder"></div>'
})}
${popup({
name: "download",
standalone: true,
buttonOnly: true,
classes: ["small"],
header: {
closeAria: t('AccessibilityClosePopup'),
emoji: emoji("🐱", 78, 1, 1),
title: t('TitlePopupDownload')
},
body: switcher({
<div id="popup-download-container" class="popup-from-bottom">
${popup({
name: "download",
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
items: `<a id="pd-download" class="switch full" target="_blank" href="/">${t('Download')}</a>
<div id="pd-share" class="switch full">${t('ShareURL')}</div>
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
}),
buttonText: t('PopupCloseDone')
})}
${popup({
name: "error",
standalone: true,
buttonOnly: true,
classes: ["small"],
header: {
closeAria: t('AccessibilityClosePopup'),
title: t('TitlePopupError'),
emoji: emoji("😿", 78, 1, 1),
},
body: `<div id="desc-error" class="desc-padding subtext"></div>`,
buttonText: t('ErrorPopupCloseButton')
})}
<div id="popup-backdrop" style="visibility: hidden;" onclick="hideAllPopups()"></div>
<div id="urgent-notice" class="urgent-notice explanation center" onclick="popup('about', 1, 'donate')" style="visibility: hidden;">${emoji("💖", 18)} ${t("UrgentDonate")}</div>
<div id="cobalt-main-box" class="center" style="visibility: hidden;">
<div id="logo">${appName}</div>
<div id="download-area">
<div id="top">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<button id="url-clear" onclick="clearInput()" style="display:none;">x</button>
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${t('AccessibilityDownloadButton')}">
</div>
<div id="bottom">
<button id="paste" class="switch" onclick="pasteClipboard()" aria-label="${t('PasteFromClipboard')}">${emoji("📋", 22)} ${t('PasteFromClipboard')}</button>
${switcher({
name: "audioMode",
noParent: true,
items: [{
"action": "false",
"text": `${emoji("✨")} ${t("ModeToggleAuto")}`
}, {
"action": "true",
"text": `${emoji("🎶")} ${t("ModeToggleAudio")}`
}]
})}
standalone: true,
buttonOnly: true,
classes: ["small", "glass-bkg"],
header: {
closeAria: t('AccessibilityGoBack'),
emoji: emoji("🐱", 78, 1, 1),
title: t('TitlePopupDownload')
},
body: switcher({
name: "download",
explanation: `${!isIOS ? t('DownloadPopupDescription') : t('DownloadPopupDescriptionIOS')}`,
items: `<a id="pd-download" class="switch full" target="_blank" href="/"><span>${t('Download')}</span></a>
<div id="pd-share" class="switch full">${t('ShareURL')}</div>
<div id="pd-copy" class="switch full">${t('CopyURL')}</div>`
}),
buttonText: t('PopupCloseDone')
})}
</div>
<div id="popup-error-container" class="popup-from-bottom">
${popup({
name: "error",
standalone: true,
buttonOnly: true,
classes: ["small", "glass-bkg"],
header: {
closeAria: t('AccessibilityGoBack'),
title: t('TitlePopupError'),
emoji: emoji("😿", 78, 1, 1),
},
body: `<div id="desc-error" class="desc-padding subtext"></div>`,
buttonText: t('ErrorPopupCloseButton')
})}
</div>
<div id="popup-backdrop" onclick="hideAllPopups()"></div>
<div id="home">
<div id="urgent-notice" class="urgent-notice explanation" onclick="popup('about', 1, 'donate')" style="visibility: hidden;">${emoji("💖", 18)} ${t("UrgentDonate")}</div>
<div id="cobalt-main-box" class="center" style="visibility: hidden;">
<div id="logo">${t("AppTitleCobalt")}</div>
<div id="download-area">
<div id="top">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${t('LinkInput')}" aria-label="${t('AccessibilityInputArea')}" oninput="button()"></input>
<button id="url-clear" onclick="clearInput()" style="display:none;">x</button>
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${t('AccessibilityDownloadButton')}">
</div>
<div id="bottom">
<button id="paste" class="switch" onclick="pasteClipboard()" aria-label="${t('PasteFromClipboard')}">${emoji("📋", 22)} ${t('PasteFromClipboard')}</button>
${switcher({
name: "audioMode",
noParent: true,
items: [{
action: "false",
text: `${emoji("✨")} ${t("ModeToggleAuto")}`
}, {
action: "true",
text: `${emoji("🎶")} ${t("ModeToggleAudio")}`
}]
})}
</div>
</div>
</div>
<footer id="footer" style="visibility: hidden;">
${footerButtons([{
name: "about",
type: "popup",
text: `${emoji("🐲" , 22)} ${t('AboutTab')}`,
aria: t('AccessibilityOpenAbout')
}, {
name: "about",
type: "popup",
context: "donate",
text: `${emoji("💖", 22)} ${t('Donate')}`,
aria: t('AccessibilityOpenDonate')
}, {
name: "settings",
type: "popup",
text: `${emoji("⚙️", 22)} ${t('TitlePopupSettings')}`,
aria: t('AccessibilityOpenSettings')
}])}
</footer>
</div>
<footer id="footer" style="visibility: hidden;">
${/* big action buttons are ALWAYS either first or last, because usual buttons are bundled in pairs and are sandwiched between bigger buttons for mobile view */
footerButtons([{
name: "about",
type: "popup",
text: `${emoji("🐲" , 22)} ${t('AboutTab')}`,
aria: t('AccessibilityOpenAbout')
}, {
name: "about",
type: "popup",
context: "donate",
text: `${emoji("💖", 22)} ${t('Donate')}`,
aria: t('AccessibilityOpenDonate')
}, {
name: "settings",
type: "popup",
text: `${emoji("⚙️", 22)} ${t('TitlePopupSettings')}`,
aria: t('AccessibilityOpenSettings')
}])}
</footer>
</body>
<script type="text/javascript">
const loc = {
noInternet: ` + "`" + t('ErrorNoInternet') + "`" + `,
noURLReturned: ` + "`" + t('ErrorNoUrlReturned') + "`" + `,
unknownStatus: ` + "`" + t('ErrorUnknownStatus') + "`" + `,
collapseHistory: ` + "`" + t('ChangelogPressToHide') + "`" + `,
pickerDefault: ` + "`" + t('MediaPickerTitle') + "`" + `,
pickerImages: ` + "`" + t('ImagePickerTitle') + "`" + `,
pickerImagesExpl: ` + "`" + t(`ImagePickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
pickerDefaultExpl: ` + "`" + t(`MediaPickerExplanation${isMobile ? `Phone${isIOS ? "IOS" : ""}` : "PC"}`) + "`" + `,
};
let apiURL = '${process.env.apiURL ? process.env.apiURL.slice(0, -1) : ''}';
const loc = {
noInternet: ` + "`" + t('ErrorNoInternet') + "`" + `,
noURLReturned: ` + "`" + t('ErrorNoUrlReturned') + "`" + `,
unknownStatus: ` + "`" + t('ErrorUnknownStatus') + "`" + `,
collapseHistory: ` + "`" + t('ChangelogPressToHide') + "`" + `,
pickerDefault: ` + "`" + t('MediaPickerTitle') + "`" + `,
pickerImages: ` + "`" + t('ImagePickerTitle') + "`" + `,
pickerImagesExpl: ` + "`" + t(`ImagePickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
pickerDefaultExpl: ` + "`" + t(`MediaPickerExplanation${isMobile ? "Phone" : "PC"}`) + "`" + `,
featureErrorGeneric: ` + "`" + t('FeatureErrorGeneric') + "`" + `,
clipboardErrorNoPermission: ` + "`" + t('ClipboardErrorNoPermission') + "`" + `,
clipboardErrorFirefox: ` + "`" + t('ClipboardErrorFirefox') + "`" + `,
};
let apiURL = '${process.env.apiURL ? process.env.apiURL.slice(0, -1) : ''}';
</script>
<script type="text/javascript" src="cobalt.js"></script>
</html>`;
</html>
`
} catch (err) {
return `${t('ErrorPageRenderFail', obj.hash)}`;
}

View File

@@ -69,6 +69,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
u: Array.isArray(r.urls) ? r.urls[0] : r.urls,
mute: true
}
if (host === "reddit" && r.typeId === 1) responseType = 1;
break;
case "picker":

View File

@@ -11,17 +11,25 @@ export default async function(obj) {
if (!("reddit_video" in data["secure_media"])) return { error: 'ErrorEmptyDownload' };
if (data["secure_media"]["reddit_video"]["duration"] * 1000 > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
audio = video.match('.mp4') ? `${video.split('_')[0]}_audio.mp4` : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
await fetch(audio, { method: "HEAD" }).then((r) => {if (Number(r.status) !== 200) audio = ''}).catch(() => {audio = ''});
let audio = false,
video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
audioFileLink = video.match('.mp4') ? `${video.split('_')[0]}_audio.mp4` : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
let id = data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3];
if (!audio.length > 0) return { typeId: 1, urls: video };
await fetch(audioFileLink, { method: "HEAD" }).then((r) => { if (Number(r.status) === 200) audio = true }).catch(() => { audio = false });
// fallback for videos with differentiating audio quality
if (!audio) {
audioFileLink = `${video.split('_')[0]}_AUDIO_128.mp4`
await fetch(audioFileLink, { method: "HEAD" }).then((r) => { if (Number(r.status) === 200) audio = true }).catch(() => { audio = false });
}
let id = video.split('/')[3];
if (!audio) return { typeId: 1, urls: video };
return {
typeId: 2,
type: "render",
urls: [video, audio],
urls: [video, audioFileLink],
audioFilename: `reddit_${id}_audio`,
filename: `reddit_${id}.mp4`
};

View File

@@ -12,7 +12,7 @@
"enabled": true
},
"twitter": {
"alias": "twitter posts & voice",
"alias": "twitter videos & voice",
"patterns": [":user/status/:id", ":user/status/:id/video/:v", "i/spaces/:spaceId"],
"enabled": true
},

View File

@@ -3,6 +3,8 @@ import { createInterface } from "readline";
import { Cyan, Bright } from "./sub/consoleText.js";
import { execSync } from "child_process";
import { version } from "../modules/config.js";
let envPath = './.env';
let q = `${Cyan('?')} \x1b[1m`;
let ob = {};
@@ -24,7 +26,7 @@ let final = () => {
}
console.log(
`${Cyan("Hey, this is cobalt.")}\n${Bright("Let's start by creating a new ")}${Cyan(".env")}${Bright(" file. You can always change it later.")}`
`${Cyan(`Hey, this is cobalt v.${version}!`)}\n${Bright("Let's start by creating a new ")}${Cyan(".env")}${Bright(" file. You can always change it later.")}`
)
console.log(

View File

@@ -153,3 +153,8 @@ export function getThreads() {
return '0'
}
}
export function cleanHTML(html) {
let clean = html.replace(/ {4}/g, '');
clean = clean.replace(/\n/g, '');
return clean
}