Compare commits

...

15 commits

Author SHA1 Message Date
Solyn ad1e8b4f7f
Merge ca34525fac into 709d14ee9e 2024-04-30 00:56:20 +01:00
hyperdefined 709d14ee9e
feat: ddinstagram.com support (#402)
Co-authored-by: dumbmoron <log@riseup.net>
2024-04-30 01:11:25 +06:00
wukko 6392f912ad
added an option for tiktok h265 videos, majorly cleaned up frontend (#472)
- cleaned up cobalt.js (by a lot)
- removed notification dot
- removed settings migration
- removed vimeoDash
- turned youtube track language switcher into a toggle
- added clarification as to what youtube dub does
- updated defaults to match with backend
- now matching a url from any string at any place
2024-04-30 00:45:10 +06:00
wukko c1079c544d
localization: update russian description for tiktok h265 2024-04-30 00:38:00 +06:00
wukko 511ad07d2f
front/cobalt.js: actually extract the url from clipboard 2024-04-30 00:34:29 +06:00
wukko 9b0d968cca
cobalt: use test instead of match in pasteClipboard 2024-04-30 00:28:40 +06:00
wukko 0ca393e8ec
docs/api: add tiktokH265 and remove vimeoDash 2024-04-30 00:27:17 +06:00
wukko 9fae8f03ff
front/cobalt.js: fixes based on review 2024-04-30 00:25:43 +06:00
wukko 8f5eec0b5d
added an option for tiktok h265 videos, majorly cleaned up frontend
- cleaned up cobalt.js (by a lot)
- removed notification dot
- removed settings migration
- removed vimeoDash
- turned youtube track language switcher into a toggle
- added clarification as to what youtube dub does
- updated defaults to match with backend
- now matching a url from any string at any place
2024-04-30 00:04:19 +06:00
Solyn ca34525fac
Merge branch 'wukko:current' into current 2024-04-29 00:17:58 +01:00
Solyn bb6690bde4
Merge branch 'wukko:current' into current 2024-04-27 19:26:17 +01:00
Solyn 032be4103f
Merge branch 'wukko:current' into current 2024-04-20 22:01:34 +01:00
Solyn 8f1cbf44dd 📪 * Grammar fixes :/ 2024-04-20 12:36:59 +01:00
Solyn 52fa96b6b3 😳 * Fix a little oopsies 2024-04-20 12:07:35 +01:00
Solyn e0ae124ce9 🇧🇷 * Added portuguese translations 2024-04-20 12:03:58 +01:00
14 changed files with 592 additions and 385 deletions

View file

@ -32,7 +32,7 @@ Content-Type: application/json
| `dubLang` | `boolean` | `true / false` | `false` | backend uses Accept-Language header for youtube video audio tracks when `true`. |
| `disableMetadata` | `boolean` | `true / false` | `false` | disables file metadata when set to `true`. |
| `twitterGif` | `boolean` | `true / false` | `false` | changes whether twitter gifs are converted to .gif |
| `vimeoDash` | `boolean` | `true / false` | `false` | changes whether streamed file type is preferred for vimeo videos. |
| `tiktokH265` | `boolean` | `true / false` | `false` | changes whether 1080p h265 videos are preferred or not. |
### response body variables
| key | type | variables |

View file

@ -3,9 +3,6 @@
"maxVideoDuration": 10800000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"authorInfo": {
"name": "wukko",
"link": "https://wukko.me/",
"contact": "https://wukko.me/contacts",
"support": {
"default": {
"email": {

View file

@ -1,5 +1,3 @@
const version = 42;
const ua = navigator.userAgent.toLowerCase();
const isIOS = ua.match("iphone os");
const isMobile = ua.match("android") || ua.match("iphone os");
@ -7,19 +5,14 @@ const isSafari = ua.match("safari/");
const isFirefox = ua.match("firefox/");
const isOldFirefox = ua.match("firefox/") && ua.split("firefox/")[1].split('.')[0] < 103;
const regex = new RegExp(/https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)/);
const notification = `<span class="notification-dot"></span>`;
const switchers = {
"theme": ["auto", "light", "dark"],
"vCodec": ["h264", "av1", "vp9"],
"vQuality": ["1080", "max", "2160", "1440", "720", "480", "360"],
"vQuality": ["720", "max", "2160", "1440", "1080", "480", "360"],
"aFormat": ["mp3", "best", "ogg", "wav", "opus"],
"dubLang": ["original", "auto"],
"vimeoDash": ["false", "true"],
"audioMode": ["false", "true"],
"filenamePattern": ["classic", "pretty", "basic", "nerdy"]
};
}
const checkboxes = [
"alwaysVisibleButton",
"downloadPopup",
@ -29,101 +22,127 @@ const checkboxes = [
"disableAnimations",
"disableMetadata",
"twitterGif",
"plausible_ignore"
];
const exceptions = { // used for mobile devices
"vQuality": "720"
};
const bottomPopups = ["error", "download"];
const pageQuery = new URLSearchParams(window.location.search);
"plausible_ignore",
"ytDub",
"tiktokH265"
]
const bottomPopups = ["error", "download"]
let store = {};
function fixApiUrl(url) {
const validLink = (link) => {
try {
return /^https:/i.test(new URL(link).protocol);
} catch {
return false
}
}
const fixApiUrl = (url) => {
return url.endsWith('/') ? url.slice(0, -1) : url
}
let apiURL = fixApiUrl(defaultApiUrl);
function changeApi(url) {
const changeApi = (url) => {
apiURL = fixApiUrl(url);
return true
}
function eid(id) {
const eid = (id) => {
return document.getElementById(id)
}
function sGet(id) {
const sGet = (id) =>{
return localStorage.getItem(id)
}
function sSet(id, value) {
const sSet = (id, value) => {
localStorage.setItem(id, value)
}
function enable(id) {
const enable = (id) => {
eid(id).dataset.enabled = "true";
}
function disable(id) {
const disable = (id) => {
eid(id).dataset.enabled = "false";
}
function vis(state) {
return (state === 1) ? "visible" : "hidden";
}
function opposite(state) {
const opposite = (state) => {
return state === "true" ? "false" : "true";
}
function changeDownloadButton(action, text) {
const lazyGet = (key) => {
const value = sGet(key);
if (key in switchers) {
if (switchers[key][0] !== value)
return value;
} else if (checkboxes.includes(key)) {
if (value === 'true')
return true;
}
}
const changeDownloadButton = (action, text) => {
switch (action) {
case 0:
case "hidden": // hidden, but only visible when alwaysVisibleButton is true
eid("download-button").disabled = true
if (sGet("alwaysVisibleButton") === "true") {
eid("download-button").value = text
eid("download-button").value = '>>'
eid("download-button").style.padding = '0 1rem'
} else {
eid("download-button").value = ''
eid("download-button").style.padding = '0'
}
break;
case 1:
eid("download-button").disabled = false
eid("download-button").value = text
eid("download-button").style.padding = '0 1rem'
break;
case 2:
case "disabled":
eid("download-button").disabled = true
eid("download-button").value = text
eid("download-button").style.padding = '0 1rem'
break;
default:
eid("download-button").disabled = false
eid("download-button").value = '>>'
eid("download-button").style.padding = '0 1rem'
break;
}
}
document.addEventListener("keydown", (event) => {
if (event.key === "Tab") {
eid("download-button").value = '>>'
eid("download-button").style.padding = '0 1rem'
}
})
function button() {
let regexTest = regex.test(eid("url-input-area").value);
const button = () => {
let regexTest = validLink(eid("url-input-area").value);
eid("url-clear").style.display = "none";
if ((eid("url-input-area").value).length > 0) {
eid("url-clear").style.display = "block";
} else {
eid("url-clear").style.display = "none";
}
regexTest ? changeDownloadButton(1, '>>') : changeDownloadButton(0, '>>');
if (regexTest) {
changeDownloadButton()
} else {
changeDownloadButton("hidden")
}
}
function clearInput() {
const clearInput = () => {
eid("url-input-area").value = '';
button();
}
function copy(id, data) {
let e = document.getElementById(id);
e.classList.add("text-backdrop");
setTimeout(() => { e.classList.remove("text-backdrop") }, 600);
data ? navigator.clipboard.writeText(data) : navigator.clipboard.writeText(e.innerText);
const copy = (id, data) => {
let target = document.getElementById(id);
target.classList.add("text-backdrop");
setTimeout(() => {
target.classList.remove("text-backdrop")
}, 600);
if (data) {
navigator.clipboard.writeText(data)
} else {
navigator.clipboard.writeText(e.innerText)
}
}
async function share(url) {
try { await navigator.share({url: url}) } catch (e) {}
}
function detectColorScheme() {
const share = url => navigator?.share({ url }).catch(() => {});
const detectColorScheme = () => {
let theme = "auto";
let localTheme = sGet("theme");
if (localTheme) {
@ -133,7 +152,59 @@ function detectColorScheme() {
}
document.documentElement.setAttribute("data-theme", theme);
}
function changeTab(evnt, tabId, tabClass) {
const updateFilenamePreview = () => {
let videoFilePreview = ``;
let audioFilePreview = ``;
let resMatch = {
"max": "3840x2160",
"2160": "3840x2160",
"1440": "2560x1440",
"1080": "1920x1080",
"720": "1280x720",
"480": "854x480",
"360": "640x360",
}
switch(sGet("filenamePattern")) {
case "classic":
videoFilePreview = `youtube_dQw4w9WgXcQ_${resMatch[sGet('vQuality')]}_${sGet('vCodec')}`
+ `${sGet("muteAudio") === "true" ? "_mute" : ""}`
+ `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `youtube_dQw4w9WgXcQ_audio`
+ `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "basic":
videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, `
+ `${sGet('vCodec')}${sGet("muteAudio") === "true" ? ", mute" : ""})`
+ `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor}`
+ `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "pretty":
videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube)`
+ `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} (soundcloud)`
+ `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "nerdy":
videoFilePreview = `${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube, dQw4w9WgXcQ)`
+ `.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} `
+ `(soundcloud, 1242868615)`
+ `.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
}
eid("video-filename-text").innerHTML = videoFilePreview
eid("audio-filename-text").innerHTML = audioFilePreview
}
const changeTab = (evnt, tabId, tabClass) => {
if (tabId === "tab-settings-other") updateFilenamePreview();
let tabcontent = document.getElementsByClassName(`tab-content-${tabClass}`);
@ -149,46 +220,15 @@ function changeTab(evnt, tabId, tabClass) {
evnt.currentTarget.dataset.enabled = "true";
eid(tabId).dataset.enabled = "true";
eid(tabId).parentElement.scrollTop = 0;
if (tabId === "tab-about-changelog" && sGet("changelogStatus") !== `${version}`) notificationCheck("changelog");
if (tabId === "tab-about-about" && !sGet("seenAbout")) notificationCheck("about");
}
function expandCollapsible(evnt) {
const expandCollapsible = (evnt) => {
let classlist = evnt.currentTarget.parentNode.classList;
let c = "expanded";
!classlist.contains(c) ? classlist.add(c) : classlist.remove(c);
}
function notificationCheck(type) {
let changed = true;
switch (type) {
case "about":
sSet("seenAbout", "true");
break;
case "changelog":
sSet("changelogStatus", version)
break;
default:
changed = false;
}
if (changed && sGet("changelogStatus") === `${version}`) {
setTimeout(() => {
eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace(notification, '');
eid("tab-button-about-changelog").innerHTML = eid("tab-button-about-changelog").innerHTML.replace(notification, '')
}, 900)
}
if (!sGet("seenAbout") && !eid("about-footer").innerHTML.includes(notification)) {
eid("about-footer").innerHTML = `${notification}${eid("about-footer").innerHTML}`;
}
if (sGet("changelogStatus") !== `${version}`) {
if (!eid("about-footer").innerHTML.includes(notification)) {
eid("about-footer").innerHTML = `${notification}${eid("about-footer").innerHTML}`;
}
if (!eid("tab-button-about-changelog").innerHTML.includes(notification)) {
eid("tab-button-about-changelog").innerHTML = `${notification}${eid("tab-button-about-changelog").innerHTML}`;
}
}
}
function hideAllPopups() {
const hideAllPopups = () => {
let filter = document.getElementsByClassName('popup');
for (let i = 0; i < filter.length; i++) {
filter[i].classList.remove("visible");
@ -201,13 +241,14 @@ function hideAllPopups() {
eid("picker-download").href = '/';
eid("picker-download").classList.remove("visible");
}
function popup(type, action, text) {
const popup = (type, action, text) => {
if (action === 1) {
hideAllPopups(); // hide the previous popup before showing a new one
store.isPopupOpen = true;
switch (type) {
case "about":
let tabId = sGet("changelogStatus") !== `${version}` ? "changelog" : "about";
let tabId = "about";
if (text) tabId = text;
eid(`tab-button-${type}-${tabId}`).click();
break;
@ -276,7 +317,8 @@ function popup(type, action, text) {
eid(`popup-${type}`).classList.toggle("visible");
eid(`popup-${type}`).focus();
}
function changeSwitcher(li, b) {
const changeSwitcher = (li, b) => {
if (b) {
if (!switchers[li].includes(b)) b = switchers[li][0];
sSet(li, b);
@ -287,14 +329,14 @@ function changeSwitcher(li, b) {
if (li === "filenamePattern") updateFilenamePreview();
} else {
let pref = switchers[li][0];
if (isMobile && exceptions[li]) pref = exceptions[li];
sSet(li, pref);
for (let i in switchers[li]) {
(switchers[li][i] === pref) ? enable(`${li}-${pref}`) : disable(`${li}-${switchers[li][i]}`)
}
}
}
function checkbox(action) {
const checkbox = (action) => {
sSet(action, !!eid(action).checked);
switch(action) {
case "alwaysVisibleButton": button(); break;
@ -302,43 +344,158 @@ function checkbox(action) {
case "disableAnimations": eid("cobalt-body").classList.toggle('no-animation'); break;
}
}
function changeButton(type, text) {
const changeButton = (type, text) => {
switch (type) {
case 0: //error
case "error": //error
eid("url-input-area").disabled = false
eid("url-clear").style.display = "block";
changeDownloadButton(2, '!!');
changeDownloadButton("disabled", '!!');
popup("error", 1, text);
setTimeout(() => { changeButton(1); }, 2500);
setTimeout(() => { changeButton("default") }, 2500);
break;
case 1: //enable back
changeDownloadButton(1, '>>');
case "default": //enable back
changeDownloadButton();
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
break;
case 2: //enable back + information popup
case "error-default": //enable back + information popup
popup("error", 1, text);
changeDownloadButton(1, '>>');
changeDownloadButton();
eid("url-clear").style.display = "block";
eid("url-input-area").disabled = false
break;
}
}
function internetError() {
const internetError = () => {
eid("url-input-area").disabled = false
changeDownloadButton(2, '!!');
setTimeout(() => { changeButton(1); }, 2500);
changeDownloadButton("disabled", '!!');
setTimeout(() => { changeButton("default") }, 2500);
popup("error", 1, loc.ErrorNoInternet);
}
function resetSettings() {
const resetSettings = () => {
localStorage.clear();
window.location.reload();
}
async function pasteClipboard() {
const download = async(url) => {
changeDownloadButton("disabled", '...');
eid("url-clear").style.display = "none";
eid("url-input-area").disabled = true;
let req = {
url,
vCodec: lazyGet("vCodec"),
vQuality: lazyGet("vQuality"),
aFormat: lazyGet("aFormat"),
filenamePattern: lazyGet("filenamePattern"),
isAudioOnly: lazyGet("audioMode"),
isTTFullAudio: lazyGet("fullTikTokAudio"),
isAudioMuted: lazyGet("muteAudio"),
disableMetadata: lazyGet("disableMetadata"),
dubLang: lazyGet("ytDub"),
twitterGif: lazyGet("twitterGif"),
tiktokH265: lazyGet("tiktokH265"),
}
let j = await fetch(`${apiURL}/api/json`, {
method: "POST",
body: JSON.stringify(req),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(r => r.json()).catch(() => {});
if (!j) {
internetError();
return;
}
if ((j.status === "error" || j.status === "rate-limit") && j && j.text) {
changeButton("error", j.text);
return;
}
if (j.text && (!j.url || !j.picker)) {
if (j.status === "success") {
changeButton("error-default", j.text)
} else {
changeButton("error", loc.ErrorNoUrlReturned);
}
}
switch (j.status) {
case "redirect":
changeDownloadButton("disabled", '>>>');
setTimeout(() => { changeButton("default") }, 1500);
if (sGet("downloadPopup") === "true") {
popup('download', 1, j.url)
} else {
window.open(j.url, '_blank')
}
break;
case "stream":
changeDownloadButton("disabled", '?..');
let probeStream = await fetch(`${j.url}&p=1`).then(r => r.json()).catch(() => {});
if (!probeStream) return internetError();
if (probeStream.status !== "continue") {
changeButton("error", probeStream.text);
return;
}
changeDownloadButton("disabled", '>>>');
if (sGet("downloadPopup") === "true") {
popup('download', 1, j.url)
} else {
if (isMobile || isSafari) {
window.location.href = j.url;
} else {
window.open(j.url, '_blank');
}
}
setTimeout(() => { changeButton("default") }, 2500);
break;
case "picker":
if (j.audio && j.picker) {
changeDownloadButton("disabled", '>>>');
popup('picker', 1, {
audio: j.audio,
arr: j.picker,
type: j.pickerType
});
setTimeout(() => { changeButton("default") }, 2500);
} else if (j.picker) {
changeDownloadButton("disabled", '>>>');
popup('picker', 1, {
arr: j.picker,
type: j.pickerType
});
setTimeout(() => { changeButton("default") }, 2500);
} else {
changeButton("error", loc.ErrorNoUrlReturned);
}
break;
case "success":
changeButton("error-default", j.text);
break;
default:
changeButton("error", loc.ErrorUnknownStatus);
break;
}
}
const pasteClipboard = async() => {
try {
let t = await navigator.clipboard.readText();
if (regex.test(t)) {
eid("url-input-area").value = t;
let clipboard = await navigator.clipboard.readText();
let onlyURL = clipboard.match(/https:\/\/[^\s]+/g)
if (onlyURL) {
eid("url-input-area").value = onlyURL;
download(eid("url-input-area").value);
}
} catch (e) {
@ -353,204 +510,58 @@ async function pasteClipboard() {
if (doError) popup("error", 1, errorMessage);
}
}
async function download(url) {
changeDownloadButton(2, '...');
eid("url-clear").style.display = "none";
eid("url-input-area").disabled = true;
let req = {
url,
aFormat: sGet("aFormat").slice(0, 4),
filenamePattern: sGet("filenamePattern"),
dubLang: false
}
if (sGet("dubLang") === "auto") {
req.dubLang = true
} else if (sGet("dubLang") === "custom") {
req.dubLang = true
}
if (sGet("vimeoDash") === "true") req.vimeoDash = true;
if (sGet("audioMode") === "true") {
req.isAudioOnly = true;
if (sGet("fullTikTokAudio") === "true") req.isTTFullAudio = true; // audio tiktok full
} else {
req.vQuality = sGet("vQuality").slice(0, 4);
if (sGet("muteAudio") === "true") req.isAudioMuted = true;
if (url.includes("youtube.com/") || url.includes("/youtu.be/")) req.vCodec = sGet("vCodec").slice(0, 4);
}
if (sGet("disableMetadata") === "true") req.disableMetadata = true;
if (sGet("twitterGif") === "true") req.twitterGif = true;
let j = await fetch(`${apiURL}/api/json`, {
method: "POST",
body: JSON.stringify(req),
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
}).then((r) => { return r.json() }).catch((e) => { return false });
if (!j) {
internetError();
return
}
if (j && j.status !== "error" && j.status !== "rate-limit") {
if (j.text && (!j.url || !j.picker)) {
if (j.status === "success") {
changeButton(2, j.text)
} else changeButton(0, loc.ErrorNoUrlReturned);
}
switch (j.status) {
case "redirect":
changeDownloadButton(2, '>>>');
setTimeout(() => { changeButton(1); }, 1500);
sGet("downloadPopup") === "true" ? popup('download', 1, j.url) : window.open(j.url, '_blank');
break;
case "picker":
if (j.audio && j.picker) {
changeDownloadButton(2, '>>>');
popup('picker', 1, { audio: j.audio, arr: j.picker, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 2500);
} else if (j.picker) {
changeDownloadButton(2, '>>>');
popup('picker', 1, { arr: j.picker, type: j.pickerType });
setTimeout(() => { changeButton(1) }, 2500);
} else {
changeButton(0, loc.ErrorNoUrlReturned);
}
break;
case "stream":
changeDownloadButton(2, '?..')
fetch(`${j.url}&p=1`).then(async (res) => {
let jp = await res.json();
if (jp.status === "continue") {
changeDownloadButton(2, '>>>');
if (sGet("downloadPopup") === "true") {
popup('download', 1, j.url)
} else {
if (isMobile || isSafari) {
window.location.href = j.url;
} else window.open(j.url, '_blank');
}
setTimeout(() => { changeButton(1) }, 2500);
} else {
changeButton(0, jp.text);
}
}).catch((error) => internetError());
break;
case "success":
changeButton(2, j.text);
break;
default:
changeButton(0, loc.ErrorUnknownStatus);
break;
}
} else if (j && j.text) {
changeButton(0, j.text);
}
}
async function loadCelebrationsEmoji() {
let bac = eid("about-footer").innerHTML;
const loadCelebrationsEmoji = async() => {
let aboutButtonBackup = eid("about-footer").innerHTML;
try {
let j = await fetch(`/onDemand?blockId=1`).then((r) => { if (r.status === 200) { return r.json() } else { return false } }).catch(() => { return false });
let j = await fetch(`/onDemand?blockId=1`).then(r => r.json()).catch(() => {});
if (j && j.status === "success" && j.text) {
eid("about-footer").innerHTML = eid("about-footer").innerHTML.replace('<img class="emoji" draggable="false" height="22" width="22" alt="🐲" src="emoji/dragon_face.svg" loading="lazy">', 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"
loading="lazy">`,
j.text
)
}
} catch (e) {
eid("about-footer").innerHTML = bac;
} catch {
eid("about-footer").innerHTML = aboutButtonBackup;
}
}
async function loadOnDemand(elementId, blockId) {
let j = {};
const loadOnDemand = async(elementId, blockId) => {
store.historyButton = eid(elementId).innerHTML;
eid(elementId).innerHTML = `<div class="loader">...</div>`;
try {
if (store.historyContent) {
j = store.historyContent;
} else {
await fetch(`/onDemand?blockId=${blockId}`).then(async(r) => {
j = await r.json();
if (j && j.status === "success") {
store.historyContent = j;
} else throw new Error();
}).catch(() => { throw new Error() });
if (!store.historyContent) {
let j = await fetch(`/onDemand?blockId=${blockId}`).then(r => r.json()).catch(() => {});
if (!j) throw new Error();
if (j.status === "success") {
store.historyContent = j.text
}
}
if (j.text) {
eid(elementId).innerHTML = `<button class="switch bottom-margin" onclick="restoreUpdateHistory()">${loc.ChangelogPressToHide}</button>${j.text}`;
} else throw new Error()
} catch (e) {
eid(elementId).innerHTML =
`<button class="switch bottom-margin" onclick="restoreUpdateHistory()">
${loc.ChangelogPressToHide}
</button>
${store.historyContent}`;
} catch {
eid(elementId).innerHTML = store.historyButton;
internetError()
}
}
function restoreUpdateHistory() {
const restoreUpdateHistory = () => {
eid("changelog-history").innerHTML = store.historyButton;
}
function unpackSettings(b64) {
let changed = null;
try {
let settingsToImport = JSON.parse(atob(b64));
let currentSettings = JSON.parse(JSON.stringify(localStorage));
for (let s in settingsToImport) {
if (checkboxes.includes(s) && (settingsToImport[s] === "true" || settingsToImport[s] === "false")
&& currentSettings[s] !== settingsToImport[s]) {
sSet(s, settingsToImport[s]);
changed = true
}
if (switchers[s] && switchers[s].includes(settingsToImport[s])
&& currentSettings[s] !== settingsToImport[s]) {
sSet(s, settingsToImport[s]);
changed = true
}
}
} catch (e) {
changed = false;
}
return changed
}
function updateFilenamePreview() {
let videoFilePreview = ``;
let audioFilePreview = ``;
let resMatch = {
"max": "3840x2160",
"2160": "3840x2160",
"1440": "2560x1440",
"1080": "1920x1080",
"720": "1280x720",
"480": "854x480",
"360": "640x360",
}
// "dubLang"
// sGet("muteAudio") === "true"
switch(sGet("filenamePattern")) {
case "classic":
videoFilePreview = `youtube_yPYZpwSpKmA_${resMatch[sGet('vQuality')]}_${sGet('vCodec')}`
+ `${sGet("muteAudio") === "true" ? "_mute" : ""}.${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `youtube_yPYZpwSpKmA_audio.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "pretty":
videoFilePreview =
`${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube).${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} (soundcloud).${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "basic":
videoFilePreview =
`${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}${sGet("muteAudio") === "true" ? " mute" : ""}).${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor}.${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
case "nerdy":
videoFilePreview =
`${loc.FilenamePreviewVideoTitle} `
+ `(${sGet('vQuality') === "max" ? "2160p" : `${sGet('vQuality')}p`}, ${sGet('vCodec')}, `
+ `${sGet("muteAudio") === "true" ? "mute, " : ""}youtube, yPYZpwSpKmA).${sGet('vCodec') === "vp9" ? 'webm' : 'mp4'}`;
audioFilePreview = `${loc.FilenamePreviewAudioTitle} - ${loc.FilenamePreviewAudioAuthor} (soundcloud, 1242868615).${sGet('aFormat') !== "best" ? sGet('aFormat') : 'opus'}`;
break;
}
eid("video-filename-text").innerHTML = videoFilePreview
eid("audio-filename-text").innerHTML = audioFilePreview
}
function loadSettings() {
const loadSettings = () => {
if (sGet("alwaysVisibleButton") === "true") {
eid("alwaysVisibleButton").checked = true;
eid("download-button").value = '>>'
@ -578,13 +589,14 @@ function loadSettings() {
}
updateFilenamePreview()
}
window.onload = () => {
loadCelebrationsEmoji();
loadSettings();
detectColorScheme();
changeDownloadButton(0, '>>');
changeDownloadButton("hidden");
eid("url-input-area").value = "";
if (isIOS) {
@ -595,37 +607,32 @@ window.onload = () => {
eid("home").style.visibility = 'visible';
eid("home").classList.toggle("visible");
if (pageQuery.has("u") && regex.test(pageQuery.get("u"))) {
const pageQuery = new URLSearchParams(window.location.search);
if (pageQuery.has("u") && validLink(pageQuery.get("u"))) {
eid("url-input-area").value = pageQuery.get("u");
button()
}
if (pageQuery.has("migration")) {
if (pageQuery.has("settingsData") && !sGet("migrated")) {
let setUn = unpackSettings(pageQuery.get("settingsData"));
if (setUn !== null) {
if (setUn) {
sSet("migrated", "true")
}
}
}
loadSettings();
detectColorScheme();
}
window.history.replaceState(null, '', window.location.pathname);
notificationCheck();
// fix for animations not working in Safari
if (isIOS) {
document.addEventListener('touchstart', () => {}, true);
}
}
eid("url-input-area").addEventListener("keydown", (e) => {
button();
})
eid("url-input-area").addEventListener("keyup", (e) => {
if (e.key === 'Enter') eid("download-button").click();
})
document.addEventListener("keydown", (event) => {
if (event.key === "Tab") {
eid("download-button").value = '>>'
eid("download-button").style.padding = '0 1rem'
}
})
document.onkeydown = (e) => {
if (!store.isPopupOpen) {
if (e.metaKey || e.ctrlKey || e.key === "/") eid("url-input-area").focus();

View file

@ -8,7 +8,7 @@
"LinkInput": "paste the link here",
"AboutSummary": "cobalt is your go-to place for downloads from social and media platforms. zero ads, trackers, or other creepy bullshit. simply paste a share link and you're ready to rock!",
"EmbedBriefDescription": "save what you love. no ads, trackers, or other creepy bullshit.",
"MadeWithLove": "made with &lt;3 by wukko",
"MadeWithLove": "made with &lt;3 by imput",
"AccessibilityInputArea": "link input area",
"AccessibilityOpenAbout": "open about popup",
"AccessibilityDownloadButton": "download button",
@ -90,7 +90,6 @@
"DonateSub": "help it stay online",
"DonateExplanation": "cobalt doesn't shove ads in your face and doesn't sell your personal data, meaning that it's <span class=\"text-backdrop\">completely free to use</span> for everyone. but development and maintenance of a media-heavy service used by over 750k people is quite costly. both in terms of time and money.\n\nif cobalt helped you in the past and you want to keep it growing and evolving, you can return the favor by making a donation!\n\nyour donation will help all cobalt users: educators, students, content creators, artists, musicians, and many, many more!\n\nin past, donations have let cobalt:\n*; increase stability and uptime to nearly 100%.\n*; speed up ALL downloads, especially heavier ones.\n*; open the api for free public use.\n*; withstand several huge user influxes with 0 downtime.\n*; add resource-intensive features (such as gif conversion).\n*; continue improving our infrastructure.\n*; keep developers happy.\n\n<span class=\"text-backdrop\">every cent matters and is extremely appreciated</span>, you can truly make a difference!\n\nif you can't donate, share cobalt with a friend! we don't get ads anywhere, so cobalt is spread by word of mouth.\nsharing is the easiest way to help achieve the goal of better internet for everyone.",
"DonateVia": "donate via",
"DonateHireMe": "...or you can <a class=\"text-backdrop link\" href=\"{s}\" target=\"_blank\">hire me</a> :)",
"SettingsVideoMute": "mute audio",
"SettingsVideoMuteExplanation": "removes audio from video downloads when possible.",
"ErrorSoundCloudNoClientId": "i couldn't get the temporary token that's required to download songs from soundcloud. try again, but if issue persists, {ContactLink}.",
@ -106,11 +105,6 @@
"SettingsCodecSubtitle": "youtube codec",
"SettingsCodecDescription": "h264: best support across apps/platforms, average detail level. max quality is 1080p.\nav1: best quality, small file size, most detail. supports 8k & HDR.\nvp9: same quality as av1, but file is x2 bigger. supports 4k & HDR.\n\npick h264 if you want best compatibility.\npick av1 if you want best quality and efficiency.",
"SettingsAudioDub": "youtube audio track",
"SettingsAudioDubDescription": "defines which audio track will be used. if dubbed track isn't available, original video language is used instead.\n\noriginal: original video language is used.\nauto: default browser (and cobalt) language is used.",
"SettingsDubDefault": "original",
"SettingsDubAuto": "auto",
"SettingsVimeoPrefer": "vimeo downloads type",
"SettingsVimeoPreferDescription": "progressive: direct file link to vimeo's cdn. max quality is 1080p.\ndash: video and audio are merged by cobalt into one file. max quality is 4k.\n\npick \"progressive\" if you want best editor/player/social media compatibility. if progressive download isn't available, dash is used instead.",
"ShareURL": "share",
"ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!",
"PopupCloseDone": "done",
@ -157,6 +151,10 @@
"PrivateAnalytics": "private analytics",
"SettingsDisableAnalytics": "opt out of private analytics",
"SettingsAnalyticsExplanation": "enable if you don't want to be included in anonymous traffic stats. read more about this in about > privacy policy (tl;dr: nothing about you is ever stored or tracked, no cookies are used).",
"AnalyticsDescription": "cobalt uses a self-hosted plausible instance to get an approximate number of how many people use it.\n\nplausible is fully compliant with GDPR, CCPA and PECR, doesn't use cookies, and never stores any identifiable info, not even your ip address.\n\nall data is aggregated and never personalized. nothing about what you download is ever saved anywhere. it's used just for anonymous traffic stats, nothing more.\n\nplausible is fully open source, just like cobalt, and if you want to learn more about it, you can do so <a class=\"text-backdrop link\" href=\"https://plausible.io\" target=\"_blank\">here</a>. if you wish to opt out of traffic stats, you can do it in settings > other."
"AnalyticsDescription": "cobalt uses a self-hosted plausible instance to get an approximate number of how many people use it.\n\nplausible is fully compliant with GDPR, CCPA and PECR, doesn't use cookies, and never stores any identifiable info, not even your ip address.\n\nall data is aggregated and never personalized. nothing about what you download is ever saved anywhere. it's used just for anonymous traffic stats, nothing more.\n\nplausible is fully open source, just like cobalt, and if you want to learn more about it, you can do so <a class=\"text-backdrop link\" href=\"https://plausible.io\" target=\"_blank\">here</a>. if you wish to opt out of traffic stats, you can do it in settings > other.",
"SettingsTikTokH265": "prefer h265",
"SettingsTikTokH265Description": "download 1080p videos from tiktok in h265/hevc format when available.",
"SettingsYoutubeDub": "use browser language",
"SettingsYoutubeDubDescription": "uses your browser's default language for youtube dubbed audio tracks. works even if cobalt ui isn't translated to your language."
}
}
}

View file

@ -0,0 +1,164 @@
{
"name": "Português",
"substrings": {
"ContactLink": "verifique a <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">página de status</a> ou <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">crie um problema no GitHub</a>"
},
"strings": {
"AppTitleCobalt": "cobalt",
"LinkInput": "cole o link aqui",
"AboutSummary": "cobalt é o seu lugar ideal para downloads de redes sociais e para plataformas de média. sem anúncios, rastreadores, ou outro tipo de besteiras assustadoras. simplesmente cole o link e você está pronto para arrasar!",
"EmbedBriefDescription": "salve o que você ama. sem anúncios, rastreadores, ou outras besteiras assustadoras",
"MadeWithLove": "feito com &lt;3 por wukko",
"AccessibilityInputArea": "área de entrada do link",
"AccessibilityOpenAbout": "abrir pop-up de informações",
"AccessibilityDownloadButton": "botão de download",
"AccessibilityOpenSettings": "abrir pop-up de configurações",
"AccessibilityOpenDonate": "abrir pop-up de doações",
"TitlePopupAbout": "o que é o cobalt?",
"TitlePopupSettings": "configurações",
"TitlePopupChangelog": "o que tem de novo?",
"TitlePopupDonate": "apoie o cobalt",
"TitlePopupDownload": "como salvar?",
"ErrorSomethingWentWrong": "algo de errado ocorreu e eu não consegui encontrar nada para você. tente novamente, mas se o problema continuar ocorrendo, {ContactLink}.",
"ErrorUnsupported": "parece que o serviço ainda não é suportado ou o seu link é invalido. você colou o link correto?",
"ErrorBrokenLink": "{s} é suportado, mas algo está errado com o seu link, talvez você não colou o link completo?",
"ErrorNoLink": "nãp consigo adivinhar o que você quer baixar! por favor me de um link :(",
"ErrorPageRenderFail": "se você está lendo isso, então tem algo de errado com a renderização da pagina, por favor {ContactLink} tenha certeza de providenciar o dominio que esse erro está sendo apresentado e o hash do commit ({s}). obrigado desde já :D",
"ErrorRateLimit": "você está enviando muitos pedidos. tente novamente em alguns minutos!",
"ErrorCouldntFetch": "eu não consegui encontrar nada sobre esse link. verifique se ele está funcionando e tente novamente! alguns conteúdos podem ter bloqueio regional, então tenha isso em mente. ",
"ErrorLengthLimit": "eu não consigo processar vídeos com uma duração maior que {s} minutos, então escolha algo mais curto!",
"ErrorBadFetch": "algo de errado aconteceu quando eu tentei receber informações do seu link. você tem certeza que o seu link está funcionando? verifique se está e tente novamente.",
"ErrorNoInternet": "você não está conectado a nenhuma internet ou a api do cobalt está temporariamente indisponível, verifique a sua conexão e tente novamente.",
"ErrorCantConnectToServiceAPI": "Eu não consegui conectar a api do serviço. talvez esteja fora do ar, ou o cobalt foi bloqueado. tente novamente mais tarde, mas se o problema persistir {ContactLink}.",
"ErrorEmptyDownload": "eu não encontro nada que eu possa baixar do seu link. tente um link diferente!",
"ErrorLiveVideo": "esse vídeo é uma transmissão, ainda estou para aprender como olhar para futuro. espere a transmissão acabar e tente novamente!",
"SettingsAppearanceSubtitle": "aparência",
"SettingsThemeSubtitle": "tema",
"SettingsFormatSubtitle": "formato",
"SettingsQualitySubtitle": "qualidade",
"SettingsThemeAuto": "automático",
"SettingsThemeLight": "claro",
"SettingsThemeDark": "escuro",
"SettingsKeepDownloadButton": "manter &gt;&gt; visível",
"AccessibilityKeepDownloadButton": "manter o botão de download visível",
"SettingsEnableDownloadPopup": "perguntar como salvar",
"AccessibilityEnableDownloadPopup": "perguntar o que fazer com os downloads",
"SettingsQualityDescription": "se a qualidade selecionada não estiver disponível, então a mais próxima será usada.",
"NoScriptMessage": "cobalt usa JavaScript para pedidos de API e interface interativa. você tem de permitir o acesso a JavaScript para usar esse site. não tem nenhum script irritante, promessa de mindinho.",
"DownloadPopupDescriptionIOS": "como salvar fotos:\n1. adicionar um <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">atalho para salvar para a galeria</a>.\n2. pressione o botão \"partilhar\" acima desse texto.\n3. selecione \"salvar para a galeria\" na planilha de compartilhamento i.\n\ncomo salvar o arquivo\n1. adicione um <a class=\"text-backdrop link\" href=\"{saveToFilesShortcut}\" target=\"_blank\">atalho para salvar nos ficheiros</a>.\n2. pressione o botão \"compartilhar\" acima desse texto \n3. selecione \"salvar nos ficheiros\" nessa planilha de compartilhamento.\n4. selecione a pasta que você quer salvar o arquivo e pressione \"abrir\".\n\n ambos os atalhos podem apenas ser usados na aplicação web do cobalt.",
"DownloadPopupDescription": "o botão de download abre uma nova aba com o arquivo pedido. você pode desabilitar esse pop-up nas configurações.",
"ClickToCopy": "pressione para copiar",
"Download": "baixar",
"CopyURL": "copiar",
"AboutTab": "sobre",
"ChangelogTab": "registo de mudanças",
"DonationsTab": "doações",
"SettingsVideoTab": "vídeos",
"SettingsAudioTab": "áudios",
"SettingsOtherTab": "outros",
"ChangelogLastMajor": "versão atual & commit",
"AccessibilityModeToggle": "ativar modo de download",
"DonateLinksDescription": "essa é a melhor maneira para doar se você quer que eu receba a doação diretamente.",
"SettingsAudioFormatBest": "melhor",
"SettingsAudioFormatDescription": "quando o \"melhor\" formato está selecionado, você recebe o áudio na maneira que está no lado do serviço. Ele não é recodificado. ",
"Keyphrase": "salve o que você ama",
"ErrorPopupCloseButton": "entendi",
"ErrorLengthAudioConvert": "eu não consigo converter áudios com duração maior que {s} minutos. selecione o \"melhor\" formato se você quiser evitar limitações!",
"SettingsAudioFullTikTok": "áudio completo",
"SettingsAudioFullTikTokDescription": "baixa o áudio original usado no vídeo sem mudanças adicionais pelo autor do post.",
"ErrorCantGetID": "eu não consegui obter as informações completas do link curto. tenha certeza que ele funciona ou tente um link completo! se o problema persistir, {ContactLink}.",
"ErrorNoVideosInTweet": "eu não consegui achar nenhum arquivo nesse tweet. tente um diferente!",
"ImagePickerTitle": "selecione as imagens para baixar",
"ImagePickerDownloadAudio": "baixar áudio",
"ImagePickerExplanationPC": "dê um clique direito na imagem para salvar.",
"ImagePickerExplanationPhone": "pressione e segure uma imagem para salvá-la.",
"ErrorNoUrlReturned": "eu não recebi um link de download do servidor. isso nunca deve acontecer. tente novamente, mas se ainda assim não funcionar, {ContactLink}.",
"ErrorUnknownStatus": "eu recebi uma resposta que não consigo processar. isso nunca deve acontecer. tente novamente, mas se ainda assim não funcionar, {ContactLink}.",
"PasteFromClipboard": "colar",
"ChangelogOlder": "versão anterior",
"ChangelogPressToExpand": "expandir",
"Miscellaneous": "diversos",
"ModeToggleAuto": "automático",
"ModeToggleAudio": "áudio",
"MediaPickerTitle": "selecione o que salvar",
"MediaPickerExplanationPC": "clique ou dê um clique-direito para baixar o que você quer.",
"MediaPickerExplanationPhone": "pressione ou pressione e segure para baixar o que você quer.",
"TwitterSpaceWasntRecorded": "esse espaço do Twitter não foi gravado, então não tem nada para baixar. tente com outro!",
"ErrorCantProcess": "eu não consegui processar o seu pedido :(\nvocê pode tentar novamente, mas se o problema persistir, por favor {ContactLink}.",
"ChangelogPressToHide": "esconder",
"Donate": "doar",
"DonateSub": "ajude a ficar online",
"DonateExplanation": "o cobalt não joga anúncios na sua cara e não vende as suas informações pessoais, isso significa que é <span class=\"text-backdrop\">completamente grátis</span> para todos. mas o desenvolvimento e manutenção de uma plataforma com muita média usado por aproximadamente 750 mil pessoas pode ser meio caro. tanto em termos de tempo quanto de dinheiro. \n\n se o cobalt já te ajudou no passado e se você quer que ele continue crescendo e evoluindo, você pode retribuir o favor fazendo uma doação!\n\n a sua doação vai ajudar todos os utilizadores do cobalt: educadores, estudantes, criadores de conteúdo, artistas, músicos, e muito, muito mais!\n\nno passado, doações permitiram o cobalt:\n*; aumentar a sua estabilidade e tempo de atividade para perto dos 100% .\n*; acelerar TODOS os downloads, especialmente os mais pesados.\n*; abrir a API para uso publico gratuito\n*; aguentar vários influxos de usuários com 0 tempo de inatividade\n*; adicionar funções intensivas em recursos (como a conversão de gifs).\n*; continuar a melhorar a nossa infraestrutura.\n*; deixar os desenvolvedores felizes.\n\n<span class=\"text-backdrop\">todos os centavos importam e são altamente valorizados</span>, você pode realmente fazer a diferença\n\n se você não pode doar, compartilhe o cobalt com um amigo, nos não temos anúncios em nenhum lugar, então o cobalt é espalhado de boca em boca. \ncompartilhar é a maneira mais fácil de ajudar a alcançar o objetivo de uma Internet melhor para todos.",
"DonateVia": "doar via",
"DonateHireMe": "...ou você pode <a class=\"text-backdrop link\" href=\"{s}\" target=\"_blank\">me contratar</a> :)",
"SettingsVideoMute": "silenciar áudio",
"SettingsVideoMuteExplanation": "remove o áudio do vídeo baixado quando possível.",
"ErrorSoundCloudNoClientId": "eu não consegui obter o token temporário que é necessário para baixar sons do SoundCloud. tente novamente, mas se o problema persistir, {ContactLink}.",
"CollapseServices": "serviços suportados",
"CollapseSupport": "suporte & código fonte",
"CollapsePrivacy": "politica de privacidade",
"ServicesNote": "esta lista não está finalizada e continua expandindo ao longo do tempo, tenha certeza de dar uma olhada ao longo do tempo!",
"FollowSupport": "mantenha contato com o cobalt para receber notícias, suporte e mais: ",
"SourceCode": "explore o código fonte, denuncie problemas, dê uma estrela, ou ramifique o repositório:",
"PrivacyPolicy": "a política de privacidade do cobalt é simples: nenhuma informação sua é coletada ou guardada, zero, zilch, nothing, nada.\no que você baixa é exclusivamente da sua conta, não da minha ou de mais alguém \n\nse o download necessita de renderização, então dados sobre o conteúdo pedido é encriptado e temporariamente guardado na RAM do servidor. isso é necessário para essa função funcionar\n\ndados encriptados são guardados por: <span class=\"text-backdrop\">90 segundos</span> e depois permanentemente apagadas.\n\ndados guardado são apenas possíveis de descriptografar com uma chave única do seu link de download, além disso, a base de código oficial do cobalt não fornece uma maneira para ler dados temporariamente guardados fora das funções de processamento \n\nvocê pode verificar o <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">código fonte</a> do cobalt você mesmo, e verificar tudo o que é dito",
"ErrorYTUnavailable": "este vídeo do Youtube está indisponível, ele pode ter um bloqueio de região ou ter restrição de idade. tente um diferente!",
"ErrorYTTryOtherCodec": "eu não consegui encontrar nada para baixar com as suas configurações. tente outra codec ou qualidade! \n\nas vezes a api do Youtube age inesperadamente. tente novamente ou tente outra configuração.",
"SettingsCodecSubtitle": "codec do Youtube",
"SettingsCodecDescription": "h264: suporte do reprodutor geralmente melhor, mas a qualidade máxima é 1080p.\nav1: suporte de reprodutor fraco, mas suporta 8k & HDR.\nvp9: geralmente bitrates altimissimos, preserva a maioria dos detalhes. suporta 4k & HDR. \n\nselecione h264 se você quiser a melhor compatibilidade entre editor/player/redes sociais.",
"SettingsAudioDub": "faixa de áudio do Youtube",
"SettingsAudioDubDescription": "define que faixa de áudio será usada. se o áudio dublado não estiver disponível, então a linguagem original do vídeo será usada. \n\noriginal: a linguagem original do vídeo é usada. \nautomático: a linguagem padrão do seu navegador (e do cobalt) é usada.",
"SettingsDubDefault": "original",
"SettingsDubAuto": "automático",
"SettingsVimeoPrefer": "tipos de downloads do vimeo",
"SettingsVimeoPreferDescription": "progressivo: link direto da cdn do vimeo. a qualidade máxima é 1080p. \ndash: o vídeo e o áudio são misturados pelo cobalt em um arquivo. a qualidade máxima é 4k.\n\nselecione \"progressivo\" se você quiser a melhor compatibilidade entre editor/player/redes sociais..",
"ShareURL": "partilhar",
"ErrorTweetUnavailable": "não pode achar nada sobre esse tweet. isso pode ser devido a sua visibilidade limitada. tente um diferente!",
"ErrorTwitterRIP": "o Twitter restringiu o acesso a qualquer conteúdo a usuários não autenticados. embora tenha uma maneira de obter tweets regulares, os espaços são, infelizmente, impossíveis de obter neste momento. eu estou procurando possiveis soluções",
"PopupCloseDone": "feito",
"Accessibility": "acessibilidade",
"SettingsReduceTransparency": "reduzir transparência",
"SettingsDisableAnimations": "desabilitar animações",
"FeatureErrorGeneric": "o seu navegador não permite ou não suporta esta função. verifique se tem novas atualizações disponíveis e tente novamente!",
"ClipboardErrorFirefox": "você esta usando o Firefox onde todas as funções de leitura da área de transferências estão desabilitadas.\n\nvocê pode consertar isso seguindo os seguintes passos listados: <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">aqui!</a>\n\n... ou você pode colar o link manualmente aqui!",
"ClipboardErrorNoPermission": "o cobalt não consegue aceder ao item mais recente da sua área de transferência sem a sua permissão. \n\nse você não quiser dar acesso, então cole o link manualmente aqui .\n\nse você quiser dar permissão, vá as configurações do site e habilite as permissões de acesso a área de transferência.",
"SupportSelfTroubleshooting": "tendo problemas? tente um desses passos primeiro:",
"AccessibilityGoBack": "voltar e fechar esse pop-up",
"CollapseKeyboard": "atalhos de teclado",
"KeyboardShortcutsIntro": "uso o cobalt mais rapidamente com os atalhos de teclado: ",
"KeyboardShortcutQuickPaste": "colar o link",
"KeyboardShortcutClear": "limpar a área de entrada do link",
"KeyboardShortcutClosePopup": "fechar todos os pop-ups",
"CollapseLegal": "termos e éticas",
"FairUse": "o cobalt é uma ferramenta web que faz com que seja mais fácil baixar conteúdo da internet e não assume <span class=\"text-backdrop\">nenhuma responsabilidade</span>. servidores de processamento funcionam como <span class=\"text-backdrop\">proxies limitados</span>, então nenhum conteúdo de media é armazenado no cache ou guardado. \n\nvocê (o utilizador final) é responsável pelo que você baixa, como você usa e distribui o conteúdo. por favor, tenha em mente que ao usar conteúdo dos outros sempre dê créditos aos criadores originais. \n\nquando usado em fins educacionais (palestras, deveres de casa, etc) por favor anexe o link da fonte.\n\nuso justo e créditos beneficiam a todos.",
"SettingsDisableMetadata": "não adicionar meta dados",
"NewDomainWelcomeTitle": "olá!",
"NewDomainWelcome": "o cobalt está movendo! mesmas funções, mesmo dono, simplesmente um domínio mais lembrável. e ainda sem anúncios. \n\n<span class=\"text-backdrop\">cobalt.tools</span> é o novo domínio, também conhecido como: onde você está agora. tenha certeza de atualizar o seu marcador e reinstalar a aplicação web!",
"DataTransferSuccess": "alias, as suas funções foram transferidas automaticamente :)",
"DataTransferError": "algo de errado ocorreu ao transferir as suas preferências. você terá de abrir as configurações e configurar o cobalt manualmente.",
"SupportNotAffiliated": "o cobalt <span class=\"text-backdrop\">não é afiliado</span> com quaisquer serviços listados acima.",
"SponsoredBy": "patrocinado por",
"FilenameTitle": "estilo de nome de arquivo",
"FilenamePatternClassic": "clássico",
"FilenamePatternPretty": "bonito",
"FilenamePatternBasic": "básico",
"FilenamePatternNerdy": "nerd",
"FilenameDescription": "clássico: nome de arquivo padrão do cobalt.\nbásico: titulo e informações básicas entre parêntese.\nbonito: titulo e informações entre parêntese\nnerd: titulo e todas as informações entre parênteses\n\nalguns serviços não suportam nomes de arquivo detalhados e sempre usam o estilo clássico.",
"Preview": "previsualizar",
"FilenamePreviewVideoTitle": "Titulo do Vídeo",
"FilenamePreviewAudioTitle": "Titulo do Áudio",
"FilenamePreviewAudioAuthor": "Autor do Áudio",
"StatusPage": "pagina de estado do serviço",
"TroubleshootingGuide": "guia de autodiagnóstico",
"DonateImageDescription": "gato dormindo num teclado de Notebook e digitando letras repetidamente",
"SettingsTwitterGif": "converter gifs para .gif",
"SettingsTwitterGifDescription": "converter vídeos em loop para .gif reduz a qualidade e aumenta significativamente o tamanho do arquivo. se você quiser uma melhor eficiência, mantenha essa configuração desativada.",
"ErrorTweetProtected": "esse tweet é de uma conta privada, então eu não o consigo ver. tente um diferente!",
"ErrorTweetNSFW": "esse tweet contém conteúdo sensível, então eu não o consigo ver. tente um diferente! ",
"UpdateEncryption": "encriptação e novos serviços",
"PrivateAnalytics": "analíticas privadas",
"SettingsDisableAnalytics": "sair das analíticas privadas",
"SettingsAnalyticsExplanation": "habilite se você não quiser ser incluído nas estáticas anonimas de tráfego. leia mais sobre isso em sobre > política de privacidade (Muito Longo; Não Li: nada sobre você é armazenado ou rastreado, nenhum cookie é usado).",
"AnalyticsDescription": "cobalt utiliza uma instância plausible auto-hospedada para conseguir um número aproximado de quantas pessoas o utilizam. \n\nplausible está totalmente em conformidade com o RGPD, CCPA e PECR, não utiliza cookies e nunca guarda nenhuma informação identificável, nem mesmo o seu endereço de IP\n\ntodos os dados são agregados e nunca personalizados. nada sobre o que você baixa é salvo em qualquer lugar. é usado apenas para estáticas de tráfego anonimas, nada mais. \n\nplausible é totalmente de código aberto, que nem o cobalt, e se você quiser aprender mais sobre, você o pode fazer <a class=\"text-backdrop link\" href=\"https://plausible.io\" target=\"_blank\">aqui</a>. se você quiser sair das estáticas de trafego, você o pode fazer em configurações > outros."
}
}

View file

@ -91,7 +91,6 @@
"DonateSub": "ты можешь помочь!",
"DonateExplanation": "кобальт не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно</span> для всех. но разработка и поддержка медиа сервиса, которым пользуются более 750 тысяч людей, обходится довольно затратно.\n\nесли кобальт тебе помог и ты хочешь, чтобы он продолжал расти и развиваться, то это можно сделать через донаты!\n\nтвой донат поможет всем, кто пользуется кобальтом: преподавателям, студентам, музыкантам, художникам, контент-мейкерам и многим-многим другим!\n\nв прошлом донаты помогли кобальту:\n*; повысить стабильность и аптайм почти до 100%.\n*; ускорить ВСЕ загрузки, особенно наиболее тяжёлые.\n*; открыть api для бесплатного использования.\n*; выдержать несколько огромных наплывов пользователей без перебоев.\n*; добавить ресурсоемкие фичи (например конвертацию в gif).\n*; продолжать улучшать нашу инфраструктуру.\n*; радовать разработчиков.\n\n<span class=\"text-backdrop\">каждый донат невероятно ценится</span> и помогает кобальту развиваться!\n\nесли ты не можешь отправить донат, то поделись кобальтом с другом! мы нигде не размещаем рекламу, поэтому кобальт распространяется из уст в уста.\nподелиться - самый простой способ помочь достичь цели лучшего интернета для всех.",
"DonateVia": "открыть",
"DonateHireMe": "...или же ты можешь <a class=\"text-backdrop link\" href=\"{s}\" target=\"_blank\">пригласить меня на работу</a> :)",
"SettingsVideoMute": "убрать аудио",
"SettingsVideoMuteExplanation": "убирает звук при загрузке видео, но только когда это возможно.",
"ErrorSoundCloudNoClientId": "мне не удалось достать временный токен, который необходим для скачивания аудио из soundcloud. попробуй ещё раз, но если так и не получится, {ContactLink}.",
@ -107,11 +106,6 @@
"SettingsCodecSubtitle": "кодек для youtube видео",
"SettingsCodecDescription": "h264: лучшая совместимость, средний уровень детализированности. максимальное качество - 1080p.\nav1: лучшее качество, маленький размер файла, наибольшее количество деталей. поддерживает 8k и HDR.\nvp9: такая же детализированность, как и у av1, но файл в 2 раза больше. поддерживает 4k и HDR.\n\nвыбирай h264, если тебе нужна наилучшая совместимость.\nвыбирай av1, если ты хочешь лучшее качество и эффективность.",
"SettingsAudioDub": "звуковая дорожка для youtube видео",
"SettingsAudioDubDescription": "определяет, какая звуковая дорожка используется при скачивании видео. если дублированная дорожка недоступна, то вместо неё используется оригинальная.\n\nоригинал: используется оригинальная дорожка.\nавто: используется язык браузера и интерфейса кобальта.",
"SettingsDubDefault": "оригинал",
"SettingsDubAuto": "авто",
"SettingsVimeoPrefer": "тип загрузок с vimeo",
"SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: кобальт совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, кобальт скачает \"dash\".",
"ShareURL": "поделиться",
"ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость ограничена. попробуй другой!",
"PopupCloseDone": "готово",
@ -159,6 +153,10 @@
"PrivateAnalytics": "приватная аналитика",
"SettingsDisableAnalytics": "отключить приватную аналитику",
"SettingsAnalyticsExplanation": "включи, если не хочешь быть частью анонимной статистики трафика. подробнее об этом можно прочитать в политике конфиденциальности (tl;dr: ничего о тебе или твоих действиях не хранится и не отслеживается, даже куки нет).",
"AnalyticsDescription": "кобальт использует собственный инстанс plausible чтобы иметь приблизительное представление о том, сколько людей им пользуются.\n\nplausible полностью соответствует GDPR, CCPA и PECR, не использует куки и никогда не хранит никакой идентифицируемой информации, даже ip-адрес.\n\nвсе данные агрегируются и никогда не персонализируются. ничего о том, что ты скачиваешь, никогда не сохраняется. это просто анонимная статистика трафика, ничего больше.\n\nplausible также как и кобальт имеет открытый исходный код, и, если ты хочешь узнать о нём больше, то это можно сделать <a class=\"text-backdrop link\" href=\"https://plausible.io\" target=\"_blank\">здесь</a>. а если же ты хочешь исключить себя из статистики, то это можно сделать в настройках > другое."
"AnalyticsDescription": "кобальт использует собственный инстанс plausible чтобы иметь приблизительное представление о том, сколько людей им пользуются.\n\nplausible полностью соответствует GDPR, CCPA и PECR, не использует куки и никогда не хранит никакой идентифицируемой информации, даже ip-адрес.\n\nвсе данные агрегируются и никогда не персонализируются. ничего о том, что ты скачиваешь, никогда не сохраняется. это просто анонимная статистика трафика, ничего больше.\n\nplausible также как и кобальт имеет открытый исходный код, и, если ты хочешь узнать о нём больше, то это можно сделать <a class=\"text-backdrop link\" href=\"https://plausible.io\" target=\"_blank\">здесь</a>. а если же ты хочешь исключить себя из статистики, то это можно сделать в настройках > другое.",
"SettingsTikTokH265": "предпочитать h265",
"SettingsTikTokH265Description": "скачивает видео с tiktok в 1080p и h265/hevc, когда это возможно.",
"SettingsYoutubeDub": "использовать язык браузера",
"SettingsYoutubeDubDescription": "использует главный язык браузера для аудиодорожек на youtube. работает даже если кобальт не переведён в твой язык."
}
}

View file

@ -1,10 +1,30 @@
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, socialLinks, urgentNotice, keyboardShortcuts, webLoc, sponsoredList, betaTag, linkSVG } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio, links, env } from "../config.js";
import { services as s, version, repo, donations, supportedAudio, links, env } from "../config.js";
import { getCommitInfo } from "../sub/currentCommit.js";
import loc from "../../localization/manager.js";
import emoji from "../emoji.js";
import changelogManager from "../changelog/changelogManager.js";
import {
checkbox,
collapsibleList,
explanation,
footerButtons,
multiPagePopup,
popup,
popupWithBottomButtons,
sep,
settingsCategory,
switcher,
socialLink,
socialLinks,
urgentNotice,
keyboardShortcuts,
webLoc,
sponsoredList,
betaTag,
linkSVG
} from "./elements.js";
let com = getCommitInfo();
let enabledServices = Object.keys(s).filter(p => s[p].enabled).sort().map((p) => {
@ -98,7 +118,7 @@ export default function(obj) {
header: {
aboveTitle: {
text: t('MadeWithLove'),
url: authorInfo.link
url: repo
},
closeAria: t('AccessibilityGoBack'),
title: `${emoji("🔮", 30)} ${t('TitlePopupAbout')}`
@ -285,12 +305,6 @@ export default function(obj) {
}, {
text: donate.replace(/REPLACEME/g, t('ClickToCopy')),
classes: ["desc-padding"]
}, {
text: sep(),
raw: true
}, {
text: t('DonateHireMe', authorInfo.link),
classes: ["desc-padding"]
}]
})
}],
@ -338,16 +352,6 @@ export default function(obj) {
}]
})
})
+ settingsCategory({
name: "twitter",
title: "twitter",
body: checkbox([{
action: "twitterGif",
name: t("SettingsTwitterGif"),
padding: "no-margin"
}])
+ explanation(t('SettingsTwitterGifDescription'))
})
+ settingsCategory({
name: "codec",
title: t('SettingsCodecSubtitle'),
@ -367,19 +371,24 @@ export default function(obj) {
})
})
+ settingsCategory({
name: "vimeo",
title: t('SettingsVimeoPrefer'),
body: switcher({
name: "vimeoDash",
explanation: t('SettingsVimeoPreferDescription'),
items: [{
action: "false",
text: "progressive"
}, {
action: "true",
text: "dash"
}]
})
name: "twitter",
title: "twitter",
body: checkbox([{
action: "twitterGif",
name: t("SettingsTwitterGif"),
padding: "no-margin"
}])
+ explanation(t('SettingsTwitterGifDescription'))
})
+ settingsCategory({
name: "tiktok",
title: "tiktok",
body: checkbox([{
action: "tiktokH265",
name: t("SettingsTikTokH265"),
padding: "no-margin"
}])
+ explanation(t('SettingsTikTokH265Description'))
})
}, {
name: "audio",
@ -401,19 +410,14 @@ export default function(obj) {
+ explanation(t('SettingsVideoMuteExplanation'))
})
+ settingsCategory({
name: "dub",
name: "youtube-dub",
title: t("SettingsAudioDub"),
body: switcher({
name: "dubLang",
explanation: t('SettingsAudioDubDescription'),
items: [{
action: "original",
text: t('SettingsDubDefault')
}, {
action: "auto",
text: t('SettingsDubAuto')
}]
})
body: checkbox([{
action: "ytDub",
name: t("SettingsYoutubeDub"),
padding: "no-margin"
}])
+ explanation(t('SettingsYoutubeDubDescription'))
})
+ settingsCategory({
name: "tiktok-audio",

View file

@ -88,7 +88,8 @@ export default async function(host, patternMatch, url, lang, obj) {
postId: patternMatch.postId,
id: patternMatch.id,
fullAudio: obj.isTTFullAudio,
isAudioOnly: isAudioOnly
isAudioOnly: isAudioOnly,
h265: obj.tiktokH265
});
break;
case "tumblr":
@ -103,8 +104,7 @@ export default async function(host, patternMatch, url, lang, obj) {
id: patternMatch.id.slice(0, 11),
password: patternMatch.password,
quality: obj.vQuality,
isAudioOnly: isAudioOnly,
forceDash: isAudioOnly ? true : obj.vimeoDash
isAudioOnly: isAudioOnly
});
break;
case "soundcloud":

View file

@ -47,8 +47,8 @@ export default async function(obj) {
images = detail.image_post_info?.images;
let playAddr = detail.video.play_addr_h264;
if (!playAddr) playAddr = detail.video.play_addr;
if ((obj.h265 || !playAddr) && detail.video.play_addr)
playAddr = detail.video.play_addr;
if (!obj.isAudioOnly && !images) {
video = playAddr.url_list[0];

View file

@ -39,7 +39,7 @@ export default async function(obj) {
if (!api) return { error: 'ErrorCouldntFetch' };
let downloadType = "dash";
if (!obj.forceDash && JSON.stringify(api).includes('"progressive":[{')) downloadType = "progressive";
if (!obj.isAudioOnly && JSON.stringify(api).includes('"progressive":[{')) downloadType = "progressive";
let fileMetadata = {
title: cleanString(api.video.title.trim()),

View file

@ -79,6 +79,7 @@
},
"instagram": {
"alias": "instagram reels, posts & stories",
"altDomains": ["ddinstagram.com"],
"patterns": [
"reels/:postId", ":username/reel/:postId", "reel/:postId", "p/:postId", ":username/p/:postId",
"tv/:postId", "stories/:username/:storyId"

View file

@ -64,6 +64,12 @@ export function aliasURL(url) {
if (url.hostname === 'dai.ly' && parts.length === 2) {
url = new URL(`https://dailymotion.com/video/${parts[1]}`)
}
break;
case "ddinstagram":
if (services.instagram.altDomains.includes(host.domain) && [null, 'd', 'g'].includes(host.subdomain)) {
url.hostname = 'instagram.com';
}
break;
}
return url

View file

@ -9,7 +9,15 @@ const apiVar = {
aFormat: ["best", "mp3", "ogg", "wav", "opus"],
filenamePattern: ["classic", "pretty", "basic", "nerdy"]
},
booleanOnly: ["isAudioOnly", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash", "disableMetadata", "twitterGif"]
booleanOnly: [
"isAudioOnly",
"isTTFullAudio",
"isAudioMuted",
"dubLang",
"disableMetadata",
"twitterGif",
"tiktokH265"
]
}
const forbiddenCharsString = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '='];
@ -83,8 +91,8 @@ export function checkJSONPost(obj) {
isAudioMuted: false,
disableMetadata: false,
dubLang: false,
vimeoDash: false,
twitterGif: false
twitterGif: false,
tiktokH265: false
}
try {
let objKeys = Object.keys(obj);

View file

@ -878,6 +878,30 @@
"code": 200,
"status": "redirect"
}
}, {
"name": "ddinstagram link",
"url": "https://ddinstagram.com/p/CmCVWoIr9OH/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "d.ddinstagram.com link",
"url": "https://d.ddinstagram.com/p/CmCVWoIr9OH/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}, {
"name": "g.ddinstagram.com link",
"url": "https://g.ddinstagram.com/p/CmCVWoIr9OH/",
"params": {},
"expected": {
"code": 200,
"status": "redirect"
}
}],
"vine": [{
"name": "regular vine link (9+10=21)",