soundcloud and tooltip and some other stuff (3.3)

This commit is contained in:
wukko 2022-08-22 20:10:54 +06:00
parent 08cbc05018
commit 189ecf8fe7
17 changed files with 135 additions and 37 deletions

View file

@ -29,6 +29,13 @@ It preserves original media quality so you get best downloads possible (unless y
- Polish: [@hexandcube](https://github.com/hexandcube) - Polish: [@hexandcube](https://github.com/hexandcube)
- Ukrainian: Löffel - Ukrainian: Löffel
## Languages that are always up to date
- English
- Russian
- Ukrainian
Other languages may be missing some strings or changes, you can help with updating those!
## How you can help cobalt speak your language ## How you can help cobalt speak your language
Take English or Russian localization from [this directory](https://github.com/wukko/cobalt/tree/current/src/localization/languages) and use it as a base for your translation. Then simply make a pull request and it'll be out for everyone upon review! Take English or Russian localization from [this directory](https://github.com/wukko/cobalt/tree/current/src/localization/languages) and use it as a base for your translation. Then simply make a pull request and it'll be out for everyone upon review!

View file

@ -1,7 +1,7 @@
{ {
"name": "cobalt", "name": "cobalt",
"description": "save what you love", "description": "save what you love",
"version": "3.2", "version": "3.3",
"author": "wukko", "author": "wukko",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",

View file

@ -1,6 +1,7 @@
{ {
"streamLifespan": 3600000, "streamLifespan": 3600000,
"maxVideoDuration": 1920000, "maxVideoDuration": 1920000,
"maxAudioDuration": 4200000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
"authorInfo": { "authorInfo": {
"name": "wukko", "name": "wukko",

View file

@ -453,6 +453,18 @@ input[type="checkbox"] {
.emoji { .emoji {
margin-right: 0.4rem; margin-right: 0.4rem;
} }
.tooltip {
position: absolute;
margin-top: -6rem;
margin-left: -0.5rem;
line-height: 1.2;
text-align: left;
pointer-events: none;
color: var(--accent-unhover-2)!important;
}
.button:active .tooltip {
display: none;
}
/* adapt the page according to screen size */ /* adapt the page according to screen size */
@media screen and (min-width: 2300px) { @media screen and (min-width: 2300px) {
html { html {

View file

@ -1,5 +1,5 @@
let isIOS = navigator.userAgent.toLowerCase().match("iphone os"); let isIOS = navigator.userAgent.toLowerCase().match("iphone os");
let version = 4 let version = 5;
let switchers = { let switchers = {
"theme": ["auto", "light", "dark"], "theme": ["auto", "light", "dark"],
@ -7,7 +7,7 @@ let switchers = {
"quality": ["max", "hig", "mid", "low"], "quality": ["max", "hig", "mid", "low"],
"audioFormat": ["best", "mp3", "ogg", "wav", "opus"] "audioFormat": ["best", "mp3", "ogg", "wav", "opus"]
} }
let exceptions = { // fuck you apple let exceptions = { // used solely for ios devices, because they're less capable than everything else.
"ytFormat": "mp4", "ytFormat": "mp4",
"audioFormat": "mp3" "audioFormat": "mp3"
} }
@ -197,6 +197,7 @@ function toggle(toggle) {
let state = sGet(toggle); let state = sGet(toggle);
if (state) { if (state) {
sSet(toggle, opposite(state)) sSet(toggle, opposite(state))
if (opposite(state) == "true") sSet(`${toggle}ToggledOnce`, "true");
} else { } else {
sSet(toggle, "false") sSet(toggle, "false")
} }
@ -208,7 +209,7 @@ function updateToggle(toggle, state) {
eid(toggle).innerHTML = loc.toggleAudio; eid(toggle).innerHTML = loc.toggleAudio;
break; break;
case "false": case "false":
eid(toggle).innerHTML = loc.toggleDefault; eid(toggle).innerHTML = sGet(`${toggle}ToggledOnce`) == "true" ? loc.toggleDefault : loc.pressToChange + loc.toggleDefault;
break; break;
} }
} }

View file

@ -5,10 +5,10 @@
"ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">let me know</a>" "ContactLink": "<a class=\"text-backdrop\" href=\"{repo}\" target=\"_blank\">let me know</a>"
}, },
"strings": { "strings": {
"ChangelogContentTitle": "ukrainian localization and new error popup (3.2)", "ChangelogContentTitle": "soundcloud and better usability (3.3)",
"FollowTwitter": "follow cobalt's twitter account for polls, updates, and more: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>", "ChangelogContent": "- full support for soundcloud is here. you now can save your favorite songs from there, if you want to.\n- did you know that there's an audio download mode in cobalt? if you didn't, there's now a tooltip that shows you how to switch between modes.\n- added length limit to conversion of audios, because converting a 3 hour audio to wav will give you a 4gb file, and that's just unreasonable. you can still download audio in original (best) format without any limits.\n- if best and preferred audio format match, cobalt won't needlessly convert the audio anymore.\n- fixed format override for ios, you still might have to toggle between them once.\n- increased input area length limit on frontend because some reddit and soundcloud links wouldn't fit.\n- version in settings now opens current commit page on github, instead of general commits page. it also opens in a new tab instead of replacing the current one.\n- fixed some localization stuff in english, russian, and ukrainian. it's now easier to understand what mode is on, and general cobalt description in russian doesn't sound awkward anymore.",
"ChangelogContent": "- added ukrainian localization (thanks to löffel).\n- new error popup! it's now prettier, more compact, and has an easily accessible close button.\n- russian localization has been patched up a bit\n- cleaned up css a bit\n- added github contributors to made with love message.\n- emojis have been tuned to have the same shade of yellow.\n- updated translation guidelines in readme a bit.",
"FollowTwitter": "follow cobalt's twitter account for polls, updates, and more: <a class=\"text-backdrop\" href=\"https://twitter.com/justusecobalt\" target=\"_blank\">@justusecobalt</a>",
"LinkInput": "paste the link here", "LinkInput": "paste the link here",
"AboutSummary": "{appName} is your go-to place for social media downloads. zero ads, trackers, or any other creepy bullshit attached. simply paste a share link and you're ready to rock!", "AboutSummary": "{appName} is your go-to place for social media downloads. zero ads, trackers, or any other creepy bullshit attached. simply paste a share link and you're ready to rock!",
"AboutSupportedServices": "currently supported services:", "AboutSupportedServices": "currently supported services:",
@ -57,7 +57,6 @@
"SettingsQualitySwitchMedium": "medium\n", "SettingsQualitySwitchMedium": "medium\n",
"SettingsQualitySwitchLow": "low\n", "SettingsQualitySwitchLow": "low\n",
"SettingsQualitySwitchLowest": "lowest", "SettingsQualitySwitchLowest": "lowest",
"SettingsFormatSwitchAudio": "only audio",
"SettingsKeepDownloadButton": "keep &gt;&gt; visible", "SettingsKeepDownloadButton": "keep &gt;&gt; visible",
"AccessibilityKeepDownloadButton": "keep the download button always visible", "AccessibilityKeepDownloadButton": "keep the download button always visible",
"SettingsEnableDownloadPopup": "ask for a way to save", "SettingsEnableDownloadPopup": "ask for a way to save",
@ -73,7 +72,7 @@
"DownloadPopupDescriptionIOS": "since you have an ios device, you have to press and hold the download button and then select \"download video\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.", "DownloadPopupDescriptionIOS": "since you have an ios device, you have to press and hold the download button and then select \"download video\" in appeared popup to save the video. this will be required for as long as apple forces safari webview upon all browser developers on ios.",
"DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.", "DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.",
"DownloadPopupWayToSave": "pick a way to save", "DownloadPopupWayToSave": "pick a way to save",
"ClickToCopy": "click to copy", "ClickToCopy": "press to copy",
"Download": "download", "Download": "download",
"CopyURL": "copy url", "CopyURL": "copy url",
"AboutTab": "about", "AboutTab": "about",
@ -84,7 +83,6 @@
"SettingsOtherTab": "other", "SettingsOtherTab": "other",
"ChangelogLastCommit": "last commit", "ChangelogLastCommit": "last commit",
"ChangelogLastMajor": "last major update", "ChangelogLastMajor": "last major update",
"ModeToggleDefault": "smart mode",
"AccessibilityModeToggle": "toggle download mode", "AccessibilityModeToggle": "toggle download mode",
"DonateLinksDescription": "donation links open in a new tab. this is the best way to donate money, if you want me to receive it directly.", "DonateLinksDescription": "donation links open in a new tab. this is the best way to donate money, if you want me to receive it directly.",
"SettingsAudioFormatBest": "best", "SettingsAudioFormatBest": "best",
@ -92,6 +90,10 @@
"Keyphrase": "save what you love", "Keyphrase": "save what you love",
"SettingsDisableChangelogOnUpdate": "don't show changelog after major updates", "SettingsDisableChangelogOnUpdate": "don't show changelog after major updates",
"SettingsRemoveWatermark": "disable watermark", "SettingsRemoveWatermark": "disable watermark",
"ErrorPopupCloseButton": "got it" "ErrorPopupCloseButton": "got it",
"ModeToggle": "mode",
"ModeToggleSmart": "smart",
"PressToChange": "press to change",
"ErrorLengthAudioConvert": "current length limit for audio conversion is {s} minutes. pick \"best\" format instead!"
} }
} }

View file

@ -6,10 +6,10 @@
}, },
"strings": { "strings": {
"LinkInput": "вставь ссылку сюда", "LinkInput": "вставь ссылку сюда",
"AboutSummary": "{appName} — твой друг при скачивании контента из соц. сетей. никакой рекламы или трекеров. вставляешь ссылку, получаешь файл, наслаждаешься жизнью.", "AboutSummary": "{appName} — твой друг при скачивании контента из соц. сетей. никакой рекламы или трекеров. вставляешь ссылку и получаешь файл. ничего лишнего.",
"AboutSupportedServices": "что поддерживается:", "AboutSupportedServices": "что поддерживается:",
"EmbedBriefDescription": "сохраняй что хочешь, без мороки и вторжения в личное пространство", "EmbedBriefDescription": "сохраняй что хочешь, без мороки и вторжения в личное пространство",
"MadeWithLove": "сделано с <3 всеми контрибьюторами на гитхабе и wukko", "MadeWithLove": "сделано с <3 контрибьюторами на гитхабе и wukko",
"AccessibilityInputArea": "зона вставки ссылки", "AccessibilityInputArea": "зона вставки ссылки",
"AccessibilityOpenAbout": "открыть окно с информацией", "AccessibilityOpenAbout": "открыть окно с информацией",
"AccessibilityDownloadButton": "кнопка скачивания", "AccessibilityDownloadButton": "кнопка скачивания",
@ -53,7 +53,6 @@
"SettingsQualitySwitchMedium": "среднее\n", "SettingsQualitySwitchMedium": "среднее\n",
"SettingsQualitySwitchLow": "низкое\n", "SettingsQualitySwitchLow": "низкое\n",
"SettingsQualitySwitchLowest": "худшее", "SettingsQualitySwitchLowest": "худшее",
"SettingsFormatSwitchAudio": "только аудио",
"SettingsKeepDownloadButton": "оставлять &gt;&gt; на экране", "SettingsKeepDownloadButton": "оставлять &gt;&gt; на экране",
"AccessibilityKeepDownloadButton": "оставлять кнопку скачивания на экране", "AccessibilityKeepDownloadButton": "оставлять кнопку скачивания на экране",
"SettingsEnableDownloadPopup": "спрашивать, как сохранять", "SettingsEnableDownloadPopup": "спрашивать, как сохранять",
@ -80,7 +79,6 @@
"SettingsOtherTab": "другое", "SettingsOtherTab": "другое",
"ChangelogLastCommit": "последний коммит (на английском)", "ChangelogLastCommit": "последний коммит (на английском)",
"ChangelogLastMajor": "последнее обновление (на английском)", "ChangelogLastMajor": "последнее обновление (на английском)",
"ModeToggleDefault": "умный режим",
"AccessibilityModeToggle": "переключить режим скачивания", "AccessibilityModeToggle": "переключить режим скачивания",
"DonateLinksDescription": "ссылки на донаты открываются в новой вкладке. это лучший метод пожертвовать деньги, если ты хочешь, чтобы я получил их лично, а не в виде крипто.", "DonateLinksDescription": "ссылки на донаты открываются в новой вкладке. это лучший метод пожертвовать деньги, если ты хочешь, чтобы я получил их лично, а не в виде крипто.",
"SettingsAudioFormatBest": "лучший", "SettingsAudioFormatBest": "лучший",
@ -88,6 +86,10 @@
"Keyphrase": "сохраняй то, что любишь", "Keyphrase": "сохраняй то, что любишь",
"SettingsDisableChangelogOnUpdate": "не показывать изменения после обновлений", "SettingsDisableChangelogOnUpdate": "не показывать изменения после обновлений",
"SettingsRemoveWatermark": "убрать ватермарку", "SettingsRemoveWatermark": "убрать ватермарку",
"ErrorPopupCloseButton": "ясно" "ErrorPopupCloseButton": "ясно",
"ModeToggle": "режим",
"ModeToggleSmart": "умный",
"PressToChange": "нажми, чтобы изменить",
"ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат аудио, чтобы скачать аудио такой продолжительности."
} }
} }

View file

@ -9,7 +9,7 @@
"AboutSummary": "{appName} твій помічник із завантаження контенту з соцмереж. ніякої реклами, ніяких трекерів. вставляєш лінк, отримаєш файл, і допиваєш у спокої свій смузі.", "AboutSummary": "{appName} твій помічник із завантаження контенту з соцмереж. ніякої реклами, ніяких трекерів. вставляєш лінк, отримаєш файл, і допиваєш у спокої свій смузі.",
"AboutSupportedServices": "ось що підтримується:", "AboutSupportedServices": "ось що підтримується:",
"EmbedBriefDescription": "зберігай контент із соцмереж без реклами і трекерів", "EmbedBriefDescription": "зберігай контент із соцмереж без реклами і трекерів",
"MadeWithLove": "зроблено з <3 всіма контриб'юторами на github и wukko <3", "MadeWithLove": "зроблено з <3 контриб'юторами на github и wukko <3",
"AccessibilityInputArea": "строка, щоб вставити в неї лінк", "AccessibilityInputArea": "строка, щоб вставити в неї лінк",
"AccessibilityOpenAbout": "відкрити інфу про {appName}", "AccessibilityOpenAbout": "відкрити інфу про {appName}",
"AccessibilityDownloadButton": "кнопка завантаження", "AccessibilityDownloadButton": "кнопка завантаження",
@ -53,7 +53,6 @@
"SettingsQualitySwitchMedium": "середня\n", "SettingsQualitySwitchMedium": "середня\n",
"SettingsQualitySwitchLow": "низька\n", "SettingsQualitySwitchLow": "низька\n",
"SettingsQualitySwitchLowest": "найнижча", "SettingsQualitySwitchLowest": "найнижча",
"SettingsFormatSwitchAudio": "тільки аудіо",
"SettingsKeepDownloadButton": "зробити &gt;&gt; видимим", "SettingsKeepDownloadButton": "зробити &gt;&gt; видимим",
"AccessibilityKeepDownloadButton": "завжди показувати кнопку завантаження", "AccessibilityKeepDownloadButton": "завжди показувати кнопку завантаження",
"SettingsEnableDownloadPopup": "питати щодо засобу зберігання", "SettingsEnableDownloadPopup": "питати щодо засобу зберігання",
@ -80,7 +79,6 @@
"SettingsOtherTab": "інше", "SettingsOtherTab": "інше",
"ChangelogLastCommit": "останній коммит (англійською мовою)", "ChangelogLastCommit": "останній коммит (англійською мовою)",
"ChangelogLastMajor": "останнє велике оновлення (англійською мовою)", "ChangelogLastMajor": "останнє велике оновлення (англійською мовою)",
"ModeToggleDefault": "розумний режим",
"AccessibilityModeToggle": "змінити режим завантаження", "AccessibilityModeToggle": "змінити режим завантаження",
"DonateLinksDescription": "лінки на донати відкриваються у нових вкладках. це найкращій спосіб донатити, якщо хочеш, щоб ми безпосередньо отримували гроші.", "DonateLinksDescription": "лінки на донати відкриваються у нових вкладках. це найкращій спосіб донатити, якщо хочеш, щоб ми безпосередньо отримували гроші.",
"SettingsAudioFormatBest": "найкращий", "SettingsAudioFormatBest": "найкращий",
@ -88,6 +86,10 @@
"Keyphrase": "збережи, що любиш", "Keyphrase": "збережи, що любиш",
"SettingsDisableChangelogOnUpdate": "не показувати \"що нового\" після великих оновлень", "SettingsDisableChangelogOnUpdate": "не показувати \"що нового\" після великих оновлень",
"SettingsRemoveWatermark": "прибрати вотермарку", "SettingsRemoveWatermark": "прибрати вотермарку",
"ErrorPopupCloseButton": "ясно" "ErrorPopupCloseButton": "ясно",
"ModeToggle": "режим",
"ModeToggleSmart": "розумний",
"PressToChange": "натисни, щоб змінити",
"ErrorLengthAudioConvert": "я не можу конвертувати аудіо довше ніж {s} хвилин (и). вибери \"найкращий\" формат аудіо, щоб завантажити аудіо такої тривалості."
} }
} }

View file

@ -18,6 +18,10 @@ export async function getJSON(originalURL, ip, lang, format, quality, audioForma
host = "youtube"; host = "youtube";
url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`; url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`;
} }
if (host == "goo" && url.substring(0, 30) == "https://soundcloud.app.goo.gl/") {
host = "soundcloud"
url = `https://soundcloud.com/${url.replace("https://soundcloud.app.goo.gl/", "").split('/')[0]}`
}
if (host == "tumblr" && !url.includes("blog/view")) { if (host == "tumblr" && !url.includes("blog/view")) {
if (url.slice(-1) == '/') url = url.slice(0, -1); if (url.slice(-1) == '/') url = url.slice(0, -1);
url = url.replace(url.split('/')[5], ''); url = url.replace(url.split('/')[5], '');

View file

@ -8,6 +8,7 @@ export const
version = packageJson.version, version = packageJson.version,
streamLifespan = config.streamLifespan, streamLifespan = config.streamLifespan,
maxVideoDuration = config.maxVideoDuration, maxVideoDuration = config.maxVideoDuration,
maxAudioDuration = config.maxAudioDuration,
genericUserAgent = config.genericUserAgent, genericUserAgent = config.genericUserAgent,
repo = packageJson["bugs"]["url"].replace('/issues', ''), repo = packageJson["bugs"]["url"].replace('/issues', ''),
authorInfo = config.authorInfo, authorInfo = config.authorInfo,

View file

@ -13,6 +13,7 @@ import douyin from "./services/douyin.js";
import tumblr from "./services/tumblr.js"; import tumblr from "./services/tumblr.js";
import matchActionDecider from "./sub/matchActionDecider.js"; import matchActionDecider from "./sub/matchActionDecider.js";
import vimeo from "./services/vimeo.js"; import vimeo from "./services/vimeo.js";
import soundcloud from "./services/soundcloud.js";
export default async function (host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly, noWatermark) { export default async function (host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly, noWatermark) {
try { try {
@ -91,6 +92,15 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit
lang: lang lang: lang
}); });
break; break;
case "soundcloud":
isAudioOnly = true;
r = await soundcloud({
author: patternMatch["author"], song: patternMatch["song"], url: url,
shortLink: patternMatch["shortLink"] ? patternMatch["shortLink"] : false,
format: audioFormat,
lang: lang
});
break;
default: default:
return apiJSON(0, { t: errorUnsupported(lang) }); return apiJSON(0, { t: errorUnsupported(lang) });
} }

View file

@ -44,7 +44,7 @@ export function popup(obj) {
${obj.buttonOnly ? obj.emoji : ``} ${obj.buttonOnly ? obj.emoji : ``}
<div id="popup-header" class="popup-header"> <div id="popup-header" class="popup-header">
${obj.standalone && !obj.buttonOnly ? `<button id="popup-close" class="button mono" onclick="popup('${obj.name}', 0)" ${obj.header.closeAria ? `aria-label="${obj.header.closeAria}"` : ''}>x</button>` : ''} ${obj.standalone && !obj.buttonOnly ? `<button id="popup-close" class="button mono" onclick="popup('${obj.name}', 0)" ${obj.header.closeAria ? `aria-label="${obj.header.closeAria}"` : ''}>x</button>` : ''}
${obj.header.aboveTitle ? `<a id="popup-above-title" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''} ${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''} ${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''} ${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}
</div> </div>
@ -52,7 +52,7 @@ export function popup(obj) {
${body}${obj.buttonOnly ? `<button id="close-error" class="switch" onclick="popup('${obj.name}', 0)">${obj.buttonText}</button>` : ''} ${body}${obj.buttonOnly ? `<button id="close-error" class="switch" onclick="popup('${obj.name}', 0)">${obj.buttonText}</button>` : ''}
</div> </div>
${obj.footer ? `<div id="popup-footer" class="popup-footer"> ${obj.footer ? `<div id="popup-footer" class="popup-footer">
<a id="popup-bottom" class="popup-footer-content" href="${obj.footer.url}">${obj.footer.text}</a> <a id="popup-bottom" class="popup-footer-content" target="_blank" href="${obj.footer.url}">${obj.footer.text}</a>
</div>` : ''} </div>` : ''}
${obj.standalone ? `</div>` : ''}` ${obj.standalone ? `</div>` : ''}`
} }
@ -69,7 +69,7 @@ export function multiPagePopup(obj) {
<div id="popup-${obj.name}" class="popup center box scrollable" style="visibility: hidden;"> <div id="popup-${obj.name}" class="popup center box scrollable" style="visibility: hidden;">
<div id="popup-content">${obj.header ? `<div id="popup-header" class="popup-header"> <div id="popup-content">${obj.header ? `<div id="popup-header" class="popup-header">
${obj.header.aboveTitle ? `<a id="popup-above-title" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''} ${obj.header.aboveTitle ? `<a id="popup-above-title" target="_blank" href="${obj.header.aboveTitle.url}">${obj.header.aboveTitle.text}</a>` : ''}
${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''} ${obj.header.title ? `<div id="popup-title">${obj.header.title}</div>` : ''}
${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}</div>` : ''}${tabContent}</div> ${obj.header.subtitle ? `<div id="popup-subtitle">${obj.header.subtitle}</div>` : ''}</div>` : ''}${tabContent}</div>
<div id="popup-tabs" class="switches popup-tabs">${tabs}</div> <div id="popup-tabs" class="switches popup-tabs">${tabs}</div>
@ -90,8 +90,8 @@ export function settingsCategory(obj) {
export function footerButtons(obj) { export function footerButtons(obj) {
let items = `` let items = ``
for (let i = 0; i < obj.length; i++) { for (let i = 0; i < obj.length; i++) {
let func = `${obj[i]["type"] == "toggle" ? `toggle('${obj[i]["name"]}')` : `popup('${obj[i]["name"]}', 1)`}` let func = `${obj[i]["type"] == "toggle" ? `toggle('${obj[i]["name"]}')` : `popup('${obj[i]["name"]}', 1)`}`;
items += `<button id="${obj[i]["name"]}" class="button footer-button" onclick="${func}" aria-label="${obj[i]["aria"]}">${obj[i]["icon"]}</button> ` items += `<button id="${obj[i]["name"]}" class="button footer-button" onclick="${func}" aria-label="${obj[i]["aria"]}">${obj[i]["icon"]}</button>`;
} }
return ` return `
<div id="footer-buttons">${items}</div>` <div id="footer-buttons">${items}</div>`

View file

@ -153,7 +153,7 @@ export default function(obj) {
header: { header: {
aboveTitle: { aboveTitle: {
text: `v.${version} ~ ${obj.hash}`, text: `v.${version} ~ ${obj.hash}`,
url: repo url: `${repo}/commit/${obj.hash}`
}, },
title: `${emoji("⚙️", 30)} ${loc(obj.lang, 'TitlePopupSettings')}` title: `${emoji("⚙️", 30)} ${loc(obj.lang, 'TitlePopupSettings')}`
}, },
@ -268,7 +268,7 @@ export default function(obj) {
<div id="cobalt-main-box" class="center box" style="visibility: hidden;"> <div id="cobalt-main-box" class="center box" style="visibility: hidden;">
<div id="logo-area">${appName}</div> <div id="logo-area">${appName}</div>
<div id="download-area" class="mobile-center"> <div id="download-area" class="mobile-center">
<input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="110" autocapitalize="off" placeholder="${loc(obj.lang, 'LinkInput')}" aria-label="${loc(obj.lang, 'AccessibilityInputArea')}" oninput="button()"> <input id="url-input-area" class="mono" type="text" autocorrect="off" maxlength="128" autocapitalize="off" placeholder="${loc(obj.lang, 'LinkInput')}" aria-label="${loc(obj.lang, 'AccessibilityInputArea')}" oninput="button()">
<input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${loc(obj.lang, 'AccessibilityDownloadButton')}"> <input id="download-button" class="mono dontRead" onclick="download(document.getElementById('url-input-area').value)" type="submit" value="" disabled=true aria-label="${loc(obj.lang, 'AccessibilityDownloadButton')}">
</div> </div>
</div> </div>
@ -292,7 +292,13 @@ export default function(obj) {
)} )}
</footer> </footer>
</body> </body>
<script type="text/javascript">const loc = {noInternet:"${loc(obj.lang, 'ErrorNoInternet')}", noURLReturned: "${loc(obj.lang, 'ErrorBadFetch')}", toggleDefault:'${emoji("✨")} ${loc(obj.lang, "ModeToggleDefault")}', toggleAudio:'${emoji("🎶")} ${loc(obj.lang, "SettingsFormatSwitchAudio")}'};</script> <script type="text/javascript">const loc = {
noInternet: "${loc(obj.lang, 'ErrorNoInternet')}",
noURLReturned: "${loc(obj.lang, 'ErrorBadFetch')}",
toggleDefault: '${emoji("✨")} ${loc(obj.lang, "ModeToggleSmart")} ${loc(obj.lang, "ModeToggle")}',
toggleAudio: '${emoji("🎶")} ${loc(obj.lang, "SettingsAudioTab")} ${loc(obj.lang, "ModeToggle")}',
pressToChange: '<div class="tooltip">▼ ${loc(obj.lang, 'PressToChange')}</div>'
};</script>
<script type="text/javascript" src="cobalt.js"></script> <script type="text/javascript" src="cobalt.js"></script>
</html>`; </html>`;
} catch (err) { } catch (err) {

View file

@ -0,0 +1,42 @@
import got from "got";
import loc from "../../localization/manager.js";
import { genericUserAgent, maxAudioDuration, services } from "../config.js";
export default async function(obj) {
try {
let html;
if (!obj.author && !obj.song && obj.shortLink) {
html = await got.get(`https://soundcloud.app.goo.gl/${obj.shortLink}/`, { headers: { "user-agent": genericUserAgent } });
html.on('error', (err) => {
return { error: loc(obj.lang, 'ErrorCouldntFetch', 'soundcloud') };
});
html = html.body
}
if (obj.author && obj.song) {
html = await got.get(`https://soundcloud.com/${obj.author}/${obj.song}`, { headers: { "user-agent": genericUserAgent } });
html.on('error', (err) => {
return { error: loc(obj.lang, 'ErrorCouldntFetch', 'soundcloud') };
});
html = html.body
}
if (html.includes('<script>window.__sc_hydration = ') && html.includes('"format":{"protocol":"progressive","mime_type":"audio/mpeg"},') && html.includes('{"hydratable":"sound","data":')) {
let json = JSON.parse(html.split('{"hydratable":"sound","data":')[1].split('}];</script>')[0])
if (json["media"]["transcodings"]) {
let fileUrl = `${json.media.transcodings[0]["url"].replace("/hls", "/progressive")}?client_id=${services["soundcloud"]["clientid"]}&track_authorization=${json.track_authorization}`;
if (fileUrl.substring(0, 54) == "https://api-v2.soundcloud.com/media/soundcloud:tracks:") {
if ((json.duration < maxAudioDuration) || obj.format == "best" || obj.format == "mp3") {
let file = await got.get(fileUrl, { headers: { "user-agent": genericUserAgent } });
file.on('error', (err) => {
return { error: loc(obj.lang, 'ErrorCouldntFetch', 'soundcloud') };
});
file = JSON.parse(file.body).url
return { urls: file, audioFilename: `soundcloud_${json.id}` }
} else return { error: loc(obj.lang, 'ErrorLengthAudioConvert', maxAudioDuration / 60000) }
}
} else return { error: loc(obj.lang, 'ErrorEmptyDownload') }
} else return { error: loc(obj.lang, 'ErrorBrokenLink', 'soundcloud') }
} catch (e) {
console.log(e)
return { error: loc(obj.lang, 'ErrorBadFetch') };
}
}

View file

@ -43,6 +43,7 @@
"alias": "youtube, youtube music", "alias": "youtube, youtube music",
"patterns": ["watch?v=:id"], "patterns": ["watch?v=:id"],
"quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"], "quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"],
"bestAudio": "opus",
"quality": { "quality": {
"1080": "hig", "1080": "hig",
"720": "mid", "720": "mid",
@ -65,5 +66,11 @@
"vimeo": { "vimeo": {
"patterns": [":id"], "patterns": [":id"],
"enabled": true "enabled": true
},
"soundcloud": {
"patterns": [":author/:song", ":shortLink"],
"bestAudio": "mp3",
"clientid": "lnFbWHXluNwOkW7TxTYUXrrse0qj1C72",
"enabled": true
} }
} }

View file

@ -21,4 +21,7 @@ export let testers = {
(patternMatch["id"] && patternMatch["id"].length < 21 && patternMatch["user"] && patternMatch["user"].length <= 32)), (patternMatch["id"] && patternMatch["id"].length < 21 && patternMatch["user"] && patternMatch["user"].length <= 32)),
"vimeo": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length <= 11)), "vimeo": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length <= 11)),
"soundcloud": (patternMatch) => ((patternMatch["author"] && patternMatch["song"] && (patternMatch["author"].length + patternMatch["song"].length) <= 96) ||
(patternMatch["shortLink"] && patternMatch["shortLink"].length <= 32))
}; };

View file

@ -1,4 +1,4 @@
import { supportedAudio } from "../config.js" import { services, supportedAudio } from "../config.js"
import { apiJSON } from "./utils.js" import { apiJSON } from "./utils.js"
export default function(r, host, ip, audioFormat, isAudioOnly) { export default function(r, host, ip, audioFormat, isAudioOnly) {
@ -48,14 +48,12 @@ export default function(r, host, ip, audioFormat, isAudioOnly) {
let type = "render" let type = "render"
let copy = false let copy = false
if (!supportedAudio.includes(audioFormat)) audioFormat = "best"; if (!supportedAudio.includes(audioFormat)) audioFormat = "best";
if (audioFormat == "best") { if ((audioFormat == "best" && services[host]["bestAudio"]) || services[host]["bestAudio"] && (audioFormat == services[host]["bestAudio"])) {
if (host != "youtube") { audioFormat = services[host]["bestAudio"]
audioFormat = "m4a" type = "bridge"
copy = true } else if (audioFormat == "best") {
} else { audioFormat = "m4a"
audioFormat = "opus" copy = true
type = "bridge"
}
} }
if (host == "reddit" && r.typeId == 1 || host == "vk" || host == "vimeo") return apiJSON(0, { t: r.audioFilename }); if (host == "reddit" && r.typeId == 1 || host == "vk" || host == "vimeo") return apiJSON(0, { t: r.audioFilename });
return apiJSON(2, { return apiJSON(2, {