mirror of
https://github.com/wukko/cobalt.git
synced 2024-11-17 22:00:00 +00:00
5.6: tiny quality of life improvements
- remember celebratory emoji changes? they've been fixed, and are now dynamically loaded! - changelog history now lets you try to load it again if first attempt failed for whatever reason. - added glow to the donation button to make it more visible. - cleaned up frontend js a little bit. - updated some links in tests.
This commit is contained in:
parent
ece4899415
commit
d85205649e
9 changed files with 164 additions and 96 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "cobalt",
|
||||
"description": "save what you love",
|
||||
"version": "5.5.1",
|
||||
"version": "5.6",
|
||||
"author": "wukko",
|
||||
"exports": "./src/cobalt.js",
|
||||
"type": "module",
|
||||
|
@ -34,6 +34,6 @@
|
|||
"node-cache": "^5.1.2",
|
||||
"url-pattern": "1.0.3",
|
||||
"xml-js": "^1.6.11",
|
||||
"youtubei.js": "^5.0.0"
|
||||
"youtubei.js": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import { buildFront } from "./modules/build.js";
|
|||
import { changelogHistory } from "./modules/pageRender/onDemand.js";
|
||||
import { sha256 } from "./modules/sub/crypto.js";
|
||||
import findRendered from "./modules/pageRender/findRendered.js";
|
||||
import { celebrationsEmoji } from "./modules/pageRender/elements.js";
|
||||
|
||||
if (process.env.selfURL && process.env.port) {
|
||||
const commitHash = shortCommit();
|
||||
|
@ -138,21 +139,29 @@ if (process.env.selfURL && process.env.port) {
|
|||
break;
|
||||
case 'onDemand':
|
||||
if (req.query.blockId) {
|
||||
let blockId = req.query.blockId.slice(0, 3)
|
||||
let blockId = req.query.blockId.slice(0, 3);
|
||||
let r, j;
|
||||
switch(blockId) {
|
||||
case "0":
|
||||
case "0": // changelog history
|
||||
r = changelogHistory();
|
||||
j = r ? apiJSON(3, { t: r }) : apiJSON(0, { t: "couldn't render this block" })
|
||||
break;
|
||||
case "1": // celebrations emoji
|
||||
r = celebrationsEmoji();
|
||||
j = r ? apiJSON(3, { t: r }) : false
|
||||
break;
|
||||
default:
|
||||
j = apiJSON(0, { t: "couldn't find a block with this id" })
|
||||
break;
|
||||
}
|
||||
res.status(j.status).json(j.body);
|
||||
if (j.body) {
|
||||
res.status(j.status).json(j.body)
|
||||
} else {
|
||||
res.status(204).end()
|
||||
}
|
||||
} else {
|
||||
let j = apiJSON(0, { t: "no block id" })
|
||||
res.status(j.status).json(j.body);
|
||||
let j = apiJSON(0, { t: "no block id" });
|
||||
res.status(j.status).json(j.body)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
--line-height: 1.65rem;
|
||||
--red: rgb(255, 0, 61);
|
||||
--gap: 0.6rem;
|
||||
--rainbow-gradient: linear-gradient(161deg,#ffe454,#ff6964,#fe85e5,#bd26fe,#587ae9,#8ded95);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
|
@ -655,6 +656,19 @@ button:active,
|
|||
display: block;
|
||||
text-align: right;
|
||||
}
|
||||
#about-donate-footer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: 110%;
|
||||
width: 32%;
|
||||
background: var(--rainbow-gradient);
|
||||
z-index: -2;
|
||||
filter: blur(5px);
|
||||
opacity: 0.65;
|
||||
}
|
||||
#about-donate-footer:active::before {
|
||||
opacity: 0;
|
||||
}
|
||||
/* adapt the page according to screen size */
|
||||
@media screen and (min-width: 2300px) {
|
||||
html {
|
||||
|
@ -797,6 +811,10 @@ button:active,
|
|||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
#about-donate-footer::before {
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
.footer-pair .footer-button {
|
||||
width: 100%!important;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
let ua = navigator.userAgent.toLowerCase();
|
||||
let isIOS = ua.match("iphone os");
|
||||
let isMobile = ua.match("android") || ua.match("iphone os");
|
||||
let version = 26;
|
||||
let regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/);
|
||||
let notification = `<div class="notification-dot"></div>`
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
const isIOS = ua.match("iphone os");
|
||||
const isMobile = ua.match("android") || ua.match("iphone os");
|
||||
const version = 26;
|
||||
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 = `<div class="notification-dot"></div>`;
|
||||
|
||||
let store = {}
|
||||
|
||||
let switchers = {
|
||||
const switchers = {
|
||||
"theme": ["auto", "light", "dark"],
|
||||
"vCodec": ["h264", "av1", "vp9"],
|
||||
"vQuality": ["1080", "max", "2160", "1440", "720", "480", "360"],
|
||||
|
@ -15,11 +13,15 @@ let switchers = {
|
|||
"dubLang": ["original", "auto"],
|
||||
"vimeoDash": ["false", "true"],
|
||||
"audioMode": ["false", "true"]
|
||||
}
|
||||
let checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio"];
|
||||
let exceptions = { // used for mobile devices
|
||||
};
|
||||
const checkboxes = ["disableTikTokWatermark", "fullTikTokAudio", "muteAudio"];
|
||||
const exceptions = { // used for mobile devices
|
||||
"vQuality": "720"
|
||||
}
|
||||
};
|
||||
|
||||
const apiURL = '';
|
||||
|
||||
let store = {};
|
||||
|
||||
function eid(id) {
|
||||
return document.getElementById(id)
|
||||
|
@ -333,65 +335,83 @@ async function download(url) {
|
|||
if (url.includes("youtube.com/") || url.includes("/youtu.be/")) req.vCodec = sGet("vCodec").slice(0, 4);
|
||||
if ((url.includes("tiktok.com/") || url.includes("douyin.com/")) && sGet("disableTikTokWatermark") === "true") req.isNoTTWatermark = true;
|
||||
}
|
||||
await fetch('/api/json', { method: "POST", body: JSON.stringify(req), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }).then(async (r) => {
|
||||
let j = await r.json();
|
||||
if (j.status !== "error" && j.status !== "rate-limit") {
|
||||
if (j.url || j.picker) {
|
||||
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, '?..')
|
||||
fetch(`${j.audio}&p=1`).then(async (res) => {
|
||||
let jp = await res.json();
|
||||
if (jp.status === "continue") {
|
||||
changeDownloadButton(2, '>>>');
|
||||
popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
|
||||
setTimeout(() => { changeButton(1) }, 2500);
|
||||
} else {
|
||||
changeButton(0, jp.text);
|
||||
}
|
||||
}).catch((error) => internetError());
|
||||
} else if (j.picker) {
|
||||
|
||||
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.noURLReturned);
|
||||
}
|
||||
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, '?..')
|
||||
fetch(`${j.audio}&p=1`).then(async (res) => {
|
||||
let jp = await res.json();
|
||||
if (jp.status === "continue") {
|
||||
changeDownloadButton(2, '>>>');
|
||||
popup('picker', 1, { arr: j.picker, type: j.pickerType });
|
||||
popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
|
||||
setTimeout(() => { changeButton(1) }, 2500);
|
||||
} else {
|
||||
changeButton(0, loc.noURLReturned);
|
||||
changeButton(0, jp.text);
|
||||
}
|
||||
break;
|
||||
case "stream":
|
||||
changeDownloadButton(2, '?..')
|
||||
fetch(`${j.url}&p=1`).then(async (res) => {
|
||||
let jp = await res.json();
|
||||
if (jp.status === "continue") {
|
||||
changeDownloadButton(2, '>>>'); window.location.href = j.url;
|
||||
setTimeout(() => { changeButton(1) }, 2500);
|
||||
} else {
|
||||
changeButton(0, jp.text);
|
||||
}
|
||||
}).catch((error) => internetError());
|
||||
break;
|
||||
case "success":
|
||||
changeButton(2, j.text);
|
||||
break;
|
||||
default:
|
||||
changeButton(0, loc.unknownStatus);
|
||||
break;
|
||||
}).catch((error) => internetError());
|
||||
} else if (j.picker) {
|
||||
changeDownloadButton(2, '>>>');
|
||||
popup('picker', 1, { arr: j.picker, type: j.pickerType });
|
||||
setTimeout(() => { changeButton(1) }, 2500);
|
||||
} else {
|
||||
changeButton(0, loc.noURLReturned);
|
||||
}
|
||||
} else {
|
||||
if (j.status === "success") {
|
||||
changeButton(2, j.text)
|
||||
} else changeButton(0, loc.noURLReturned);
|
||||
}
|
||||
} else {
|
||||
changeButton(0, j.text);
|
||||
break;
|
||||
case "stream":
|
||||
changeDownloadButton(2, '?..')
|
||||
fetch(`${j.url}&p=1`).then(async (res) => {
|
||||
let jp = await res.json();
|
||||
if (jp.status === "continue") {
|
||||
changeDownloadButton(2, '>>>'); window.location.href = j.url;
|
||||
setTimeout(() => { changeButton(1) }, 2500);
|
||||
} else {
|
||||
changeButton(0, jp.text);
|
||||
}
|
||||
}).catch((error) => internetError());
|
||||
break;
|
||||
case "success":
|
||||
changeButton(2, j.text);
|
||||
break;
|
||||
default:
|
||||
changeButton(0, loc.unknownStatus);
|
||||
break;
|
||||
}
|
||||
}).catch((error) => internetError());
|
||||
} else if (j && j.text) {
|
||||
changeButton(0, j.text);
|
||||
}
|
||||
}
|
||||
async function loadCelebrationsEmoji() {
|
||||
let bac = eid("about-footer").innerHTML;
|
||||
try {
|
||||
let j = await fetch(`${apiURL}/api/onDemand?blockId=1`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
|
||||
if (j && j.status === "success" && j.text) {
|
||||
eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('<img class="emoji" draggable="false" height="22" width="22" alt="🐲" src="emoji/dragon_face.svg">', j.text);
|
||||
}
|
||||
} catch (e) {
|
||||
eid("about-footer").innerHTML = bac;
|
||||
}
|
||||
}
|
||||
async function loadOnDemand(elementId, blockId) {
|
||||
store.historyButton = eid(elementId).innerHTML;
|
||||
|
@ -401,17 +421,15 @@ async function loadOnDemand(elementId, blockId) {
|
|||
if (store.historyContent) {
|
||||
j = store.historyContent;
|
||||
} else {
|
||||
await fetch(`/api/onDemand?blockId=${blockId}`).then(async (r) => {
|
||||
j = await r.json();
|
||||
if (j.status === "success") store.historyContent = j;
|
||||
})
|
||||
}
|
||||
if (j.status === "success" && j.status !== "rate-limit") {
|
||||
if (j.text) {
|
||||
eid(elementId).innerHTML = `<button class="switch bottom-margin" onclick="restoreUpdateHistory()">${loc.collapseHistory}</button>${j.text}`;
|
||||
j = await fetch(`${apiURL}/api/onDemand?blockId=${blockId}`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
|
||||
if (j && j.status === "success") {
|
||||
store.historyContent = j;
|
||||
} else {
|
||||
throw new Error()
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
if (j.text) {
|
||||
eid(elementId).innerHTML = `<button class="switch bottom-margin" onclick="restoreUpdateHistory()">${loc.collapseHistory}</button>${j.text}`;
|
||||
} else {
|
||||
throw new Error()
|
||||
}
|
||||
|
@ -431,6 +449,7 @@ window.onload = () => {
|
|||
eid("footer").style.visibility = 'visible';
|
||||
eid("url-input-area").value = "";
|
||||
notificationCheck();
|
||||
loadCelebrationsEmoji();
|
||||
if (isIOS) sSet("downloadPopup", "true");
|
||||
let urlQuery = new URLSearchParams(window.location.search).get("u");
|
||||
if (urlQuery !== null && regex.test(urlQuery)) {
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"ChangelogPressToHide": "collapse",
|
||||
"Donate": "donate",
|
||||
"DonateSub": "help me keep it up",
|
||||
"DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's <span class=\"text-backdrop\">completely free to use</span>. but turns out developing and keeping up a web service used by over 80 thousand people is not that easy.\n\nif you ever found {appName} useful and want to keep it online, or simply want to thank the developer, consider chipping in! every cent helps and is VERY appreciated :D",
|
||||
"DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's <span class=\"text-backdrop\">completely free to use</span>. but turns out developing and keeping up a web service used by over 150,000 people is not that easy.\n\nif you ever found {appName} useful and want to help continue its development and support, or simply want to thank the developer, consider chipping in! every cent helps and is VERY appreciated :D\n\ncurrently, i have big (scaling) plans, and i need your help. {appName}'s usage is growing daily, so i need to make up for it. <span class=\"text-backdrop\">donations are more appreciated than ever.</span>\n\ni am yet to earn anything from {appName}, everything goes back to users, so you're essentially helping everyone.",
|
||||
"DonateVia": "donate via",
|
||||
"DonateHireMe": "...or you can <a class=\"text-backdrop italic\" href=\"{s}\" target=\"_blank\">hire me</a> :)",
|
||||
"SettingsVideoMute": "mute audio",
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as fs from "fs";
|
|||
import { appName, repo } from "../modules/config.js";
|
||||
import loadJson from "../modules/sub/loadJSON.js";
|
||||
|
||||
const locPath = './src/localization/languages'
|
||||
const locPath = './src/localization/languages';
|
||||
|
||||
let loc = {}
|
||||
let languages = [];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { celebrations } from "../config.js";
|
||||
import emoji from "../emoji.js";
|
||||
|
||||
export function switcher(obj) {
|
||||
let items = ``;
|
||||
|
@ -151,12 +152,20 @@ export function footerButtons(obj) {
|
|||
items += `<button id="${obj[i]["name"]}-footer" class="switch footer-button" onclick="${obj[i]["action"]}()" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>`;
|
||||
break;
|
||||
case "popup":
|
||||
let context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : ''
|
||||
let context2 = obj[i+1] && obj[i+1]["context"] ? `, '${obj[i+1]["context"]}'` : ''
|
||||
let buttonName = obj[i]["context"] ? `${obj[i]["name"]}-${obj[i]["context"]}` : obj[i]["name"],
|
||||
context = obj[i]["context"] ? `, '${obj[i]["context"]}'` : '',
|
||||
buttonName2,
|
||||
context2;
|
||||
|
||||
if (obj[i+1]) {
|
||||
buttonName2 = obj[i+1]["context"] ? `${obj[i+1]["name"]}-${obj[i+1]["context"]}` : obj[i+1]["name"];
|
||||
context2 = obj[i+1]["context"] ? `, '${obj[i+1]["context"]}'` : '';
|
||||
}
|
||||
|
||||
items += `
|
||||
<div class="footer-pair">
|
||||
<button id="${obj[i]["name"]}-footer" class="switch footer-button" onclick="popup('${obj[i]["name"]}', 1${context})" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>
|
||||
${obj[i+1] ? `<button id="${obj[i+1]["name"]}-footer" class="switch footer-button" onclick="popup('${obj[i+1]["name"]}', 1${context2})" aria-label="${obj[i+1]["aria"]}">${obj[i+1]["text"]}</button>`: ''}
|
||||
<button id="${buttonName}-footer" class="switch footer-button" onclick="popup('${obj[i]["name"]}', 1${context})" aria-label="${obj[i]["aria"]}">${obj[i]["text"]}</button>
|
||||
${obj[i+1] ? `<button id="${buttonName2}-footer" class="switch footer-button" onclick="popup('${obj[i+1]["name"]}', 1${context2})" aria-label="${obj[i+1]["aria"]}">${obj[i+1]["text"]}</button>`: ''}
|
||||
</div>`;
|
||||
i++;
|
||||
break;
|
||||
|
@ -169,7 +178,12 @@ export function explanation(text) {
|
|||
return `<div class="explanation">${text}</div>`
|
||||
}
|
||||
export function celebrationsEmoji() {
|
||||
let n = new Date().toISOString().split('T')[0].split('-');
|
||||
let dm = `${n[1]}-${n[2]}`;
|
||||
return Object.keys(celebrations).includes(dm) ? celebrations[dm] : "🐲";
|
||||
try {
|
||||
let n = new Date().toISOString().split('T')[0].split('-');
|
||||
let dm = `${n[1]}-${n[2]}`;
|
||||
let f = Object.keys(celebrations).includes(dm) ? celebrations[dm] : "🐲";
|
||||
return f != "🐲" ? emoji(f, 22) : false;
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { backdropLink, celebrationsEmoji, checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink } from "./elements.js";
|
||||
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 { getCommitInfo } from "../sub/currentCommit.js";
|
||||
import loc from "../../localization/manager.js";
|
||||
|
@ -392,7 +392,7 @@ export default function(obj) {
|
|||
footerButtons([{
|
||||
name: "about",
|
||||
type: "popup",
|
||||
text: `${emoji(celebrationsEmoji() , 22)} ${t('AboutTab')}`,
|
||||
text: `${emoji("🐲" , 22)} ${t('AboutTab')}`,
|
||||
aria: t('AccessibilityOpenAbout')
|
||||
}, {
|
||||
name: "about",
|
||||
|
|
|
@ -36,8 +36,8 @@
|
|||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "picker: mixed media (3 gifs + image)",
|
||||
"url": "https://twitter.com/emerald_pedrod/status/1582418163521581063?s=20",
|
||||
"name": "picker: mixed media (2 videos)",
|
||||
"url": "https://twitter.com/taehyungsflow/status/1583411488433516544",
|
||||
"params": {
|
||||
"aFormat": "mp3",
|
||||
"isAudioOnly": false,
|
||||
|
@ -633,7 +633,7 @@
|
|||
}
|
||||
}, {
|
||||
"name": "images",
|
||||
"url": "https://vt.tiktok.com/ZS8JP89eB/",
|
||||
"url": "https://www.tiktok.com/@matryoshk4/video/7231234675476532526",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
|
@ -820,6 +820,14 @@
|
|||
"code": 200,
|
||||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "regular video",
|
||||
"url": "https://www.instagram.com/p/CmCVWoIr9OH/",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "redirect"
|
||||
}
|
||||
}, {
|
||||
"name": "reel (isAudioOnly)",
|
||||
"url": "https://www.instagram.com/reel/CoEBV3eM4QR/",
|
||||
|
|
Loading…
Reference in a new issue