diff --git a/package.json b/package.json index e94f1b11..f0a2e512 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/cobalt.js b/src/cobalt.js index 118ddfb1..990f5d31 100644 --- a/src/cobalt.js +++ b/src/cobalt.js @@ -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: diff --git a/src/front/cobalt.css b/src/front/cobalt.css index a0fb336b..f7034a66 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -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; } diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 8b8d9732..adc84a31 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -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 = `
` +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 = `
`; -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('🐲', 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 = `${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 = `${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)) { diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index a0c3febb..20ed2e5e 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -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 completely free to use. 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 completely free to use. 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. donations are more appreciated than ever.\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 hire me :)", "SettingsVideoMute": "mute audio", diff --git a/src/localization/manager.js b/src/localization/manager.js index 65c048c8..8809d1a5 100644 --- a/src/localization/manager.js +++ b/src/localization/manager.js @@ -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 = []; diff --git a/src/modules/pageRender/elements.js b/src/modules/pageRender/elements.js index dc016124..32ccd1db 100644 --- a/src/modules/pageRender/elements.js +++ b/src/modules/pageRender/elements.js @@ -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 += ``; 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 += ` `; i++; break; @@ -169,7 +178,12 @@ export function explanation(text) { return `
${text}
` } 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 + } } diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index e2df0af7..83232fb0 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -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", diff --git a/src/test/tests.json b/src/test/tests.json index fe241685..17ed2c41 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -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/",