diff --git a/package.json b/package.json index 1c272a79..2c61d5b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "3.3", + "version": "3.3.5", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/cobalt.js b/src/cobalt.js index fb45a3a0..6694b53a 100644 --- a/src/cobalt.js +++ b/src/cobalt.js @@ -61,16 +61,15 @@ if (fs.existsSync('./.env')) { switch (req.params.type) { case 'json': if (req.query.url && req.query.url.length < 150) { - let j = await getJSON( - req.query.url.trim(), - req.header('x-forwarded-for') ? req.header('x-forwarded-for') : req.ip, - languageCode(req), - req.query.format ? req.query.format.slice(0, 5) : "webm", - req.query.quality ? req.query.quality.slice(0, 3) : "max", - req.query.audioFormat ? req.query.audioFormat.slice(0, 4) : false, - req.query.audio ? true : false, - req.query.nw ? true : false - ) + let j = await getJSON(req.query.url.trim(), languageCode(req), { + ip: req.header('x-forwarded-for') ? req.header('x-forwarded-for') : req.ip, + format: req.query.format ? req.query.format.slice(0, 5) : "webm", + quality: req.query.quality ? req.query.quality.slice(0, 3) : "max", + audioFormat: req.query.audioFormat ? req.query.audioFormat.slice(0, 4) : false, + isAudioOnly: req.query.audio ? true : false, + noWatermark: req.query.nw ? true : false, + fullAudio: req.query.ttfull ? true : false, + }) res.status(j.status).json(j.body); } else { let j = apiJSON(3, { t: loc(languageCode(req), 'ErrorNoLink', process.env.selfURL) }) diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 85ba641e..0e044d4a 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -7,6 +7,7 @@ let switchers = { "quality": ["max", "hig", "mid", "low"], "audioFormat": ["best", "mp3", "ogg", "wav", "opus"] } +let checkboxes = ["disableTikTokWatermark", "fullTikTokAudio"] let exceptions = { // used solely for ios devices, because they're less capable than everything else. "ytFormat": "mp4", "audioFormat": "mp3" @@ -176,8 +177,8 @@ function loadSettings() { if (!sGet("audioMode")) { toggle("audioMode") } - if (sGet("disableTikTokWatermark") == "true") { - eid("disableTikTokWatermark").checked = true; + for (let i = 0; i < checkboxes.length; i++) { + if (sGet(checkboxes[i]) == "true") eid(checkboxes[i]).checked = true; } updateToggle("audioMode", sGet("audioMode")); for (let i in switchers) { @@ -226,6 +227,7 @@ async function download(url) { } } else { format = `&nw=true` + if (sGet("fullTikTokAudio") == "true") format += `&ttfull=true` } let mode = (sGet("audioMode") == "true") ? `audio=true` : `quality=${sGet("quality")}` fetch(`/api/json?audioFormat=${sGet("audioFormat")}&${mode}${format}&url=${encodeURIComponent(url)}`).then(async (response) => { diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 212b57cc..9e699430 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -6,7 +6,7 @@ }, "strings": { "ChangelogContentTitle": "soundcloud and better usability (3.3)", - "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": "- full support for soundcloud is here. you now can save your favorite songs from there, if you want to.\n- added ability to download full audios from tiktok/douyin, and made tiktok audio downloads better in general.\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.", "FollowTwitter": "follow cobalt's twitter account for polls, updates, and more: @justusecobalt", "LinkInput": "paste the link here", @@ -94,6 +94,8 @@ "ModeToggle": "mode", "ModeToggleSmart": "smart", "PressToChange": "press to change", - "ErrorLengthAudioConvert": "current length limit for audio conversion is {s} minutes. pick \"best\" format instead!" + "ErrorLengthAudioConvert": "current length limit for audio conversion is {s} minutes. pick \"best\" format instead!", + "SettingsAudioFullTikTok": "download full audio", + "SettingsAudioFullTikTokDescription": "this audio is most often music or original sound used in video. aka audio without voiceover, tts, or trimming will be downloaded, if it's available, of course." } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index 203bdd71..eea5bfad 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -90,6 +90,8 @@ "ModeToggle": "режим", "ModeToggleSmart": "умный", "PressToChange": "нажми, чтобы изменить", - "ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат аудио, чтобы скачать аудио такой продолжительности." + "ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат аудио, чтобы скачать аудио такой продолжительности.", + "SettingsAudioFullTikTok": "скачивать полное аудио", + "SettingsAudioFullTikTokDescription": "обычно такое аудио - оригинальный звук или песня, которое используется в видео. то есть, это аудио без обрезаний, голоса за кадром, и чего-либо подобного." } } diff --git a/src/localization/languages/uk.json b/src/localization/languages/uk.json index e9fe44fc..d20a9ee0 100644 --- a/src/localization/languages/uk.json +++ b/src/localization/languages/uk.json @@ -90,6 +90,8 @@ "ModeToggle": "режим", "ModeToggleSmart": "розумний", "PressToChange": "натисни, щоб змінити", - "ErrorLengthAudioConvert": "я не можу конвертувати аудіо довше ніж {s} хвилин (и). вибери \"найкращий\" формат аудіо, щоб завантажити аудіо такої тривалості." + "ErrorLengthAudioConvert": "я не можу конвертувати аудіо довше ніж {s} хвилин (и). вибери \"найкращий\" формат аудіо, щоб завантажити аудіо такої тривалості.", + "SettingsAudioFullTikTok": "завантажувати повне аудіо", + "SettingsAudioFullTikTokDescription": "зазвичай таке аудіо-оригінальний звук або пісня, яке використовується в відео. тобто, це аудіо без обрізань, голосу за кадром, і чого-небудь подібного." } } diff --git a/src/modules/api.js b/src/modules/api.js index ac42c380..ffa1cf0c 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -7,7 +7,7 @@ import { errorUnsupported } from "./sub/errors.js"; import loc from "../localization/manager.js"; import match from "./match.js"; -export async function getJSON(originalURL, ip, lang, format, quality, audioFormat, isAudioOnly, noWatermark) { +export async function getJSON(originalURL, lang, obj) { try { let url = decodeURI(originalURL); if (!url.includes('http://')) { @@ -32,7 +32,7 @@ export async function getJSON(originalURL, ip, lang, format, quality, audioForma if (patternMatch) break; } if (patternMatch) { - return await match(host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly, noWatermark); + return await match(host, patternMatch, url, lang, obj); } return apiJSON(0, { t: errorUnsupported(lang) }) } return apiJSON(0, { t: errorUnsupported(lang) }) } else { diff --git a/src/modules/config.js b/src/modules/config.js index 297f15f1..bd093946 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -1,9 +1,11 @@ import loadJson from "./sub/loadJSON.js"; const config = loadJson("./src/config.json"); const packageJson = loadJson("./package.json"); +const servicesConfigJson = loadJson("./src/modules/servicesConfig.json"); export const - services = loadJson("./src/modules/servicesConfig.json"), + services = servicesConfigJson.config, + audioIgnore = servicesConfigJson.audioIgnore, appName = packageJson.name, version = packageJson.version, streamLifespan = config.streamLifespan, diff --git a/src/modules/match.js b/src/modules/match.js index 5ab12dbc..e29c8487 100644 --- a/src/modules/match.js +++ b/src/modules/match.js @@ -15,7 +15,7 @@ import matchActionDecider from "./sub/matchActionDecider.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, lang, obj) { try { if (!testers[host]) return apiJSON(0, { t: errorUnsupported(lang) }); if (!(testers[host](patternMatch))) throw Error(); @@ -32,7 +32,7 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit r = await vk({ userId: patternMatch["userId"], videoId: patternMatch["videoId"], - lang: lang, quality: quality + lang: lang, quality: obj.quality }); break; case "bilibili": @@ -44,11 +44,11 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit case "youtube": let fetchInfo = { id: patternMatch["id"].slice(0, 11), - lang: lang, quality: quality, + lang: lang, quality: obj.quality, format: "webm" }; - if (url.match('music.youtube.com') || isAudioOnly == true) format = "audio"; - switch (format) { + if (url.match('music.youtube.com') || obj.isAudioOnly == true) obj.format = "audio"; + switch (obj.format) { case "mp4": fetchInfo["format"] = "mp4"; break; @@ -56,7 +56,7 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit fetchInfo["format"] = "webm"; fetchInfo["isAudioOnly"] = true; fetchInfo["quality"] = "max"; - isAudioOnly = true; + obj.isAudioOnly = true; break; } r = await youtube(fetchInfo); @@ -71,13 +71,17 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit case "tiktok": r = await tiktok({ postId: patternMatch["postId"], - id: patternMatch["id"], lang: lang, noWatermark: noWatermark + id: patternMatch["id"], lang: lang, + noWatermark: obj.noWatermark, fullAudio: obj.fullAudio, + isAudioOnly: obj.isAudioOnly }); break; case "douyin": r = await douyin({ postId: patternMatch["postId"], - id: patternMatch["id"], lang: lang, noWatermark: noWatermark + id: patternMatch["id"], lang: lang, + noWatermark: obj.noWatermark, fullAudio: obj.fullAudio, + isAudioOnly: obj.isAudioOnly }); break; case "tumblr": @@ -88,23 +92,23 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit break; case "vimeo": r = await vimeo({ - id: patternMatch["id"].slice(0, 11), quality: quality, + id: patternMatch["id"].slice(0, 11), quality: obj.quality, lang: lang }); break; case "soundcloud": - isAudioOnly = true; + obj.isAudioOnly = true; r = await soundcloud({ author: patternMatch["author"], song: patternMatch["song"], url: url, shortLink: patternMatch["shortLink"] ? patternMatch["shortLink"] : false, - format: audioFormat, + format: obj.audioFormat, lang: lang }); break; default: return apiJSON(0, { t: errorUnsupported(lang) }); } - return matchActionDecider(r, host, ip, audioFormat, isAudioOnly) + return matchActionDecider(r, host, obj.ip, obj.audioFormat, obj.isAudioOnly) } catch (e) { return apiJSON(0, { t: genericError(lang, host) }) } diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index 3a406c5c..399be2a5 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -212,6 +212,10 @@ export default function(obj) { explanation: loc(obj.lang, 'SettingsAudioFormatDescription'), items: audioFormats }) + }) + settingsCategory({ + name: "tiktok", + title: "tiktok & douyin", + body: checkbox("fullTikTokAudio", loc(obj.lang, 'SettingsAudioFullTikTok'), loc(obj.lang, 'SettingsAudioFullTikTok')) + `