api: remove localization, renovate error response

This commit is contained in:
wukko 2024-08-03 13:51:09 +06:00
parent 3fdf266ad0
commit aff22e8560
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
9 changed files with 60 additions and 420 deletions

View file

@ -5,7 +5,6 @@ import express from "express";
import { Bright, Green, Red } from "./modules/sub/consoleText.js"; import { Bright, Green, Red } from "./modules/sub/consoleText.js";
import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js"; import { getCurrentBranch, shortCommit } from "./modules/sub/currentCommit.js";
import { loadLoc } from "./localization/manager.js";
import { env } from "./modules/config.js" import { env } from "./modules/config.js"
import path from 'path'; import path from 'path';
@ -21,8 +20,6 @@ const __dirname = path.dirname(__filename).slice(0, -4);
app.disable('x-powered-by'); app.disable('x-powered-by');
await loadLoc();
if (env.apiURL) { if (env.apiURL) {
const { runAPI } = await import('./core/api.js'); const { runAPI } = await import('./core/api.js');
runAPI(express, app, gitCommit, gitBranch, __dirname) runAPI(express, app, gitCommit, gitBranch, __dirname)

View file

@ -7,7 +7,6 @@ import { env, version } from "../modules/config.js";
import { generateHmac, generateSalt } from "../modules/sub/crypto.js"; import { generateHmac, generateSalt } from "../modules/sub/crypto.js";
import { Bright, Cyan } from "../modules/sub/consoleText.js"; import { Bright, Cyan } from "../modules/sub/consoleText.js";
import { languageCode } from "../modules/sub/utils.js"; import { languageCode } from "../modules/sub/utils.js";
import loc from "../localization/manager.js";
import { createResponse, normalizeRequest, getIP } from "../modules/processing/request.js"; import { createResponse, normalizeRequest, getIP } from "../modules/processing/request.js";
import { verifyStream, getInternalStream } from "../modules/stream/manage.js"; import { verifyStream, getInternalStream } from "../modules/stream/manage.js";
@ -45,8 +44,14 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
keyGenerator: req => generateHmac(getIP(req), ipSalt), keyGenerator: req => generateHmac(getIP(req), ipSalt),
handler: (req, res) => { handler: (req, res) => {
return res.status(429).json({ return res.status(429).json({
"status": "rate-limit", status: "error",
"text": loc(languageCode(req), 'ErrorRateLimit', env.rateLimitWindow) error: {
code: "ErrorRateLimit",
context: {
limit: env.rateLimitWindow
},
text: "ErrorRateLimit" // temporary backwards compatibility
}
}); });
} }
}) })
@ -92,7 +97,10 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
if (err) { if (err) {
return res.status(400).json({ return res.status(400).json({
status: "error", status: "error",
text: "invalid json body" error: {
code: "error.body.invalid",
},
text: "invalid json body", // temporary backwards compatibility
}); });
} }
@ -103,8 +111,8 @@ export function runAPI(express, app, gitCommit, gitBranch, __dirname) {
const request = req.body; const request = req.body;
const lang = languageCode(req); const lang = languageCode(req);
const fail = (t) => { const fail = (code) => {
const { status, body } = createResponse("error", { t: loc(lang, t) }); const { status, body } = createResponse("error", { code });
res.status(status).json(body); res.status(status).json(body);
} }

View file

@ -1,166 +0,0 @@
{
"name": "english",
"substrings": {
"ContactLink": "check the <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">status page</a> or <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>"
},
"strings": {
"AppTitleCobalt": "cobalt",
"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 imput",
"AccessibilityInputArea": "link input area",
"AccessibilityOpenAbout": "open about popup",
"AccessibilityDownloadButton": "download button",
"AccessibilityOpenSettings": "open settings popup",
"AccessibilityOpenDonate": "open donation popup",
"TitlePopupAbout": "what's cobalt?",
"TitlePopupSettings": "settings",
"TitlePopupChangelog": "what's new?",
"TitlePopupDonate": "support cobalt",
"TitlePopupDownload": "how to save?",
"ErrorSomethingWentWrong": "something went wrong and i couldn't get anything for you. try again, but if issue persists, {ContactLink}.",
"ErrorUnsupported": "it seems like this service is not supported yet or your link is invalid. have you pasted the right link?",
"ErrorBrokenLink": "{s} is supported, but something is wrong with your link. maybe you didn't copy it fully?",
"ErrorNoLink": "i can't guess what you want to download! please give me a link :(",
"ErrorPageRenderFail": "if you're reading this, then there's something wrong with the page renderer. please {ContactLink}. make sure to provide the domain this error is present on and current commit hash ({s}). thank you in advance :D",
"ErrorRateLimit": "you're making too many requests. try again in {s} seconds!",
"ErrorCouldntFetch": "i couldn't find anything about this link. check if it works and try again! some content may be region restricted, so keep that in mind.",
"ErrorLengthLimit": "i can't process videos longer than {s} minutes, so pick something shorter instead!",
"ErrorBadFetch": "something went wrong when i tried getting info about your link. are you sure it works? check if it does, and try again.",
"ErrorNoInternet": "there's no internet or cobalt api is temporarily unavailable. check your connection and try again.",
"ErrorCantConnectToServiceAPI": "i couldn't connect to the service api. maybe it's down, or cobalt got blocked. try again, but if error persists, {ContactLink}.",
"ErrorEmptyDownload": "i don't see anything i could download by your link. try a different one!",
"ErrorLiveVideo": "this is a live video, i am yet to learn how to look into future. wait for the stream to finish and try again!",
"SettingsAppearanceSubtitle": "appearance",
"SettingsThemeSubtitle": "theme",
"SettingsFormatSubtitle": "format",
"SettingsQualitySubtitle": "quality",
"SettingsThemeAuto": "auto",
"SettingsThemeLight": "light",
"SettingsThemeDark": "dark",
"SettingsKeepDownloadButton": "keep &gt;&gt; visible",
"AccessibilityKeepDownloadButton": "keep the download button always visible",
"SettingsEnableDownloadPopup": "ask how to save",
"AccessibilityEnableDownloadPopup": "ask what to do with downloads",
"SettingsQualityDescription": "if selected quality isn't available, closest one is used instead.",
"NoScriptMessage": "cobalt uses javascript for api requests and interactive interface. you have to allow javascript to use this site. there are no pesty scripts, pinky promise.",
"DownloadPopupDescriptionIOS": "how to save to photos:\n1. add <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">save to photos shortcut</a>.\n2. press \"share\" button above this text.\n3. select \"save to photos\" in the share sheet.\n\nhow to save to files:\n1. add <a class=\"text-backdrop link\" href=\"{saveToFilesShortcut}\" target=\"_blank\">save to files shortcut</a>.\n2. press \"share\" button above this text.\n3. select \"save to files\" in the share sheet.\n4. select a folder to save the file to and press \"open\".\n\nboth shortcuts can only be used from the cobalt web app.",
"DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.",
"ClickToCopy": "press to copy",
"Download": "download",
"CopyURL": "copy",
"AboutTab": "about",
"ChangelogTab": "changelog",
"DonationsTab": "donations",
"SettingsVideoTab": "video",
"SettingsAudioTab": "audio",
"SettingsOtherTab": "other",
"ChangelogLastMajor": "current version & commit",
"AccessibilityModeToggle": "toggle download mode",
"DonateLinksDescription": "this is the best way to donate if you want me to receive your donation directly.",
"SettingsAudioFormatBest": "best",
"SettingsAudioFormatDescription": "when \"best\" format is selected, you get audio the way it is on service's side. it's not re-encoded. everything else will be re-encoded.",
"Keyphrase": "save what you love",
"ErrorPopupCloseButton": "got it",
"ErrorLengthAudioConvert": "i can't convert audio longer than {s} minutes. pick \"best\" format if you want to avoid limitations!",
"SettingsAudioFullTikTok": "full audio",
"SettingsAudioFullTikTokDescription": "downloads original sound used in the video without any additional changes by the post's author.",
"ErrorCantGetID": "i couldn't get the full info from the shortened link. make sure it works or try a full one! if issue persists, {ContactLink}.",
"ErrorNoVideosInTweet": "i couldn't find any media content in this tweet. try another one!",
"ImagePickerTitle": "pick images to download",
"ImagePickerDownloadAudio": "download audio",
"ImagePickerExplanationPC": "right click an image to save it.",
"ImagePickerExplanationPhone": "press and hold an image to save it.",
"ErrorNoUrlReturned": "i didn't get a download link from the server. this should never happen. try again, but if it still doesn't work, {ContactLink}.",
"ErrorUnknownStatus": "i received a response i can't process. this should never happen. try again, but if it still doesn't work, {ContactLink}.",
"PasteFromClipboard": "paste",
"ChangelogOlder": "previous versions",
"ChangelogPressToExpand": "expand",
"Miscellaneous": "miscellaneous",
"ModeToggleAuto": "auto",
"ModeToggleAudio": "audio",
"MediaPickerTitle": "pick what to save",
"MediaPickerExplanationPC": "click or right click to download what you want.",
"MediaPickerExplanationPhone": "press or press and hold to download what you want.",
"TwitterSpaceWasntRecorded": "this twitter space wasn't recorded, so there's nothing to download. try another one!",
"ErrorCantProcess": "i couldn't process your request :(\nyou can try again, but if issue persists, please {ContactLink}.",
"ChangelogPressToHide": "collapse",
"Donate": "donate",
"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 1 million 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",
"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}.",
"CollapseServices": "supported services",
"CollapseSupport": "support & source code",
"CollapsePrivacy": "privacy policy",
"ServicesNote": "this list is not final and keeps expanding over time, make sure to check it once in a while!",
"FollowSupport": "keep in touch with cobalt for news, support, and more:",
"SourceCode": "explore source code, report issues, star or fork the repo:",
"PrivacyPolicy": "cobalt's privacy policy is simple: no data about you is ever collected or stored. zero, zilch, nada, nothing.\nwhat you download is solely your business, not mine or anyone else's.\n\nif your download requires rendering, then data about requested content is encrypted and temporarily stored in server's RAM. it's necessary for this feature to function.\n\nencrypted data is stored for <span class=\"text-backdrop\">90 seconds</span> and then permanently removed.\n\nstored data is only possible to decrypt with unique encryption keys from your download link. furthermore, the official cobalt codebase doesn't provide a way to read temporarily stored data outside of processing functions.\n\nyou can check cobalt's <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">source code</a> yourself and see that everything is as stated.",
"ErrorYTUnavailable": "this youtube video is unavailable. it could be visibility or region restricted. try another one!",
"ErrorYTTryOtherCodec": "i couldn't find anything to download with your settings. try another codec or quality in settings!",
"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",
"ShareURL": "share",
"ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!",
"PopupCloseDone": "done",
"Accessibility": "accessibility",
"SettingsReduceTransparency": "reduce transparency",
"SettingsDisableAnimations": "disable animations",
"FeatureErrorGeneric": "your browser doesn't allow or support this feature. check if there are any updates available and try again!",
"ClipboardErrorFirefox": "you're using firefox where all clipboard reading functionality is disabled.\n\nyou can fix this by following steps listed <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">here!</a>\n\n...or you can paste the link manually instead.",
"ClipboardErrorNoPermission": "cobalt can't access the most recent item in your clipboard without your permission.\n\nif you don't want to give access, just paste the link manually instead.\n\nif you do, go to site settings and enable the clipboard permission.",
"SupportSelfTroubleshooting": "experiencing issues? try one of these first:",
"AccessibilityGoBack": "go back and close the popup",
"CollapseKeyboard": "keyboard shortcuts",
"KeyboardShortcutsIntro": "use cobalt even faster with keyboard shortcuts:",
"KeyboardShortcutQuickPaste": "paste the link",
"KeyboardShortcutClear": "clear link input area",
"KeyboardShortcutClosePopup": "close all popups",
"CollapseLegal": "terms and ethics",
"FairUse": "cobalt is a web tool that makes it easier to download content from the internet and takes <span class=\"text-backdrop\">zero liability</span>. processing servers work like <span class=\"text-backdrop\">limited proxies</span>, so no media content is ever cached or stored.\n\nyou (end user) are responsible for what you download, how you use and distribute that content. please be mindful when using content of others and always credit original creators.\n\nwhen used in education purposes (lecture, homework, etc) please attach the source link.\n\nfair use and credits benefit everyone.",
"SettingsDisableMetadata": "don't add metadata",
"NewDomainWelcomeTitle": "hey there!",
"NewDomainWelcome": "cobalt is moving! same features, same owner, simply a more rememberable domain. and still no ads.\n\n<span class=\"text-backdrop\">cobalt.tools</span> is the new main domain, aka where you are now. make sure to update your bookmarks and reinstall the web app!",
"DataTransferSuccess": "btw, your settings have been transferred automatically :)",
"DataTransferError": "something went wrong when transferring your preferences. you'll have to open settings and configure cobalt by hand.",
"SupportNotAffiliated": "cobalt is <span class=\"text-backdrop\">not affiliated</span> with any services listed above.",
"SponsoredBy": "sponsored by",
"FilenameTitle": "file name style",
"FilenamePatternClassic": "classic",
"FilenamePatternPretty": "pretty",
"FilenamePatternBasic": "basic",
"FilenamePatternNerdy": "nerdy",
"FilenameDescription": "classic: default cobalt file name pattern.\nbasic: title and basic info in brackets.\npretty: title and info in brackets.\nnerdy: title and all info in brackets.\n\nsome services dont support rich file names and always use the classic style.",
"Preview": "preview",
"FilenamePreviewVideoTitle": "Video Title",
"FilenamePreviewAudioTitle": "Audio Title",
"FilenamePreviewAudioAuthor": "Audio Author",
"StatusPage": "service status page",
"TroubleshootingGuide": "self-troubleshooting guide",
"DonateImageDescription": "cat sleeping on a laptop keyboard and typing letters repeatedly",
"SettingsTwitterGif": "convert gifs to .gif",
"SettingsTwitterGifDescription": "converting looping videos to .gif reduces quality and majorly increases file size. if you want best efficiency, keep this setting off.",
"ErrorTweetProtected": "this tweet is from a private account, so i can't see it. try another one!",
"ErrorTweetNSFW": "this tweet contains sensitive content, so i can't see it. try another one!",
"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.",
"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.",
"ErrorInvalidContentType": "invalid content type header",
"UpdateOneMillion": "1 million users and blazing speed",
"ErrorYTAgeRestrict": "this youtube video is age-restricted, so i can't see it. try another one!",
"ErrorYTLogin": "couldn't get this youtube video because it requires an account to view.\n\nthis limitation is done by google to seemingly stop scraping, affecting all 3rd party tools and even their own clients.\n\ntry again, but if issue persists, {ContactLink}.",
"ErrorYTRateLimit": "i got rate limited by youtube. try again in a few seconds, but if issue persists, {ContactLink}.",
"ErrorInvalidAcceptHeader": "invalid accept header",
"ErrorYoutubeDecipher": "youtube updated its decipher algorithm, so i can't get the video info. try again, but if it still doesn't work, please <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>."
}
}

View file

@ -1,165 +0,0 @@
{
"name": "русский",
"substrings": {
"ContactLink": "глянь <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">статус серверов</a> или <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши о проблеме на github</a>"
},
"strings": {
"AppTitleCobalt": "кобальт",
"LinkInput": "вставь ссылку сюда",
"AboutSummary": "кобальт - твой друг при скачивании контента из соцсетей и других сервисов. никакой рекламы, трекеров и прочего мусора. вставляешь ссылку и получаешь файл. всё. ничего лишнего.",
"EmbedBriefDescription": "сохраняй то, что любишь. без рекламы, трекеров и лишней мороки.",
"MadeWithLove": "сделано с любовью &lt;3",
"AccessibilityInputArea": "зона вставки ссылки",
"AccessibilityOpenAbout": "открыть окно с инфой",
"AccessibilityDownloadButton": "кнопка скачивания",
"AccessibilityOpenSettings": "открыть настройки",
"AccessibilityOpenDonate": "сделать пожертвование",
"TitlePopupAbout": "что за кобальт?",
"TitlePopupSettings": "настройки",
"TitlePopupChangelog": "что нового?",
"TitlePopupDonate": "поддержи кобальт",
"TitlePopupDownload": "как сохранить?",
"ErrorSomethingWentWrong": "что-то пошло совсем не так и у меня не получилось ничего для тебя достать. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"ErrorUnsupported": "с твоей ссылкой что-то не так или же этот сервис ещё не поддерживается. может быть, ты вставил не ту ссылку?",
"ErrorBrokenLink": "{s} поддерживается, но с твоей ссылкой что-то не так. может быть, ты её не полностью скопировал?",
"ErrorNoLink": "пока что я не умею угадывать, что ты хочешь скачать. дай мне, пожалуйста, ссылку :(",
"ErrorPageRenderFail": "если ты видишь этот текст, значит что-то не так с рендером страницы. пожалуйста, {ContactLink}. также приложи домен, на котором присутсвует эта ошибка, и хэш коммита ({s}). спасибо :)",
"ErrorRateLimit": "ты делаешь слишком много запросов. попробуй ещё раз через {s} секунд!",
"ErrorCouldntFetch": "у меня не получилось ничего найти по этой ссылке. убедись, что она работает, и попробуй ещё раз. некоторый контент может быть залочен на регион.",
"ErrorLengthLimit": "я не могу обрабатывать видео длиннее чем {s} минут(ы), так что скачай что-нибудь покороче!",
"ErrorBadFetch": "произошла какая-то ошибка при получении данных по твоей ссылке. убедись, что она работает, и попробуй ещё раз.",
"ErrorNoInternet": "не получилось подключиться к серверу. проверь подключение к интернету и попробуй ещё раз!",
"ErrorCantConnectToServiceAPI": "у меня не получилось подключиться к серверу этого сервиса. возможно он лежит, или же кобальт заблокировали. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"ErrorEmptyDownload": "я не нашёл того, что могу скачать. попробуй другую ссылку!",
"ErrorLiveVideo": "я пока что не умею заглядывать в будущее, поэтому дождись окончания прямого эфира, и потом уже скачивай видео!",
"SettingsAppearanceSubtitle": "внешний вид",
"SettingsThemeSubtitle": "тема",
"SettingsFormatSubtitle": "формат",
"SettingsQualitySubtitle": "качество",
"SettingsThemeAuto": "авто",
"SettingsThemeLight": "светлый",
"SettingsThemeDark": "тёмный",
"SettingsKeepDownloadButton": "всегда показывать &gt;&gt;",
"AccessibilityKeepDownloadButton": "всегда показывать кнопку скачивания на экране",
"SettingsEnableDownloadPopup": "выбор метода скачивания",
"AccessibilityEnableDownloadPopup": "спрашивать, что делать с загрузками",
"SettingsQualityDescription": "если выбранное качество недоступно, то выбирается ближайшее к нему.",
"NoScriptMessage": "кобальт использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.",
"DownloadPopupDescriptionIOS": "как сохранить в фото:\n1. добавь этот сценарий siri: <a class=\"text-backdrop link\" href=\"{saveToGalleryShortcut}\" target=\"_blank\">save to photos</a>.\n2. нажми \"поделиться\" выше этого текста.\n3. выбери \"save to photos\" в открывшемся окне.\n\nкак сохранить в файлы:\n1. добавь этот сценарий siri: <a class=\"text-backdrop link\" href=\"{saveToFilesShortcut}\" target=\"_blank\">save to files</a>.\n2. нажми \"поделиться\" выше этого текста.\n3. выбери \"save to files\" в открывшемся окне.\n4. выбери папку для сохранения файла и нажми \"открыть\".\n\nоба сценария работают только вместе с веб-приложением кобальта.",
"DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода скачивания файла в настройках.",
"ClickToCopy": "нажми, чтобы скопировать",
"Download": "скачать",
"CopyURL": "скопировать",
"AboutTab": "о сайте",
"ChangelogTab": "изменения",
"DonationsTab": "донаты",
"SettingsVideoTab": "видео",
"SettingsAudioTab": "аудио",
"SettingsOtherTab": "другое",
"ChangelogLastMajor": "текущая версия и коммит (на английском)",
"AccessibilityModeToggle": "переключить режим скачивания",
"DonateLinksDescription": "это лучший способ отправить донат, если ты хочешь, чтобы я получил его лично.",
"SettingsAudioFormatBest": "лучший",
"SettingsAudioFormatDescription": "когда выбран \"лучший\", ты получишь аудио без каких-либо изменений. такое, какое оно есть на стороне сервиса. если же выбрано что-то другое, то аудио будет немного сжато.",
"Keyphrase": "сохраняй то, что любишь",
"ErrorPopupCloseButton": "ясно",
"ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат, чтобы обойти ограничения.",
"SettingsAudioFullTikTok": "полное аудио",
"SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео, без каких-либо изменений от автора поста.",
"ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, а если так и не получится, то {ContactLink}.",
"ErrorNoVideosInTweet": "я не смог найти никакого медиа контента в этом твите. попробуй другой!",
"ImagePickerTitle": "выбери картинки для скачивания",
"ImagePickerDownloadAudio": "скачать звук",
"ImagePickerExplanationPC": "нажми правой кнопкой мыши на картинку, чтобы её сохранить.",
"ImagePickerExplanationPhone": "зажми и удерживай картинку, чтобы её сохранить.",
"ErrorNoUrlReturned": "я не получил ссылку для скачивания от сервера. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"ErrorUnknownStatus": "сервер ответил мне чем-то непонятным. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.",
"PasteFromClipboard": "вставить",
"ChangelogOlder": "предыдущие версии (тоже на английском)",
"ChangelogPressToExpand": "раскрыть",
"Miscellaneous": "разное",
"ModeToggleAuto": "авто",
"ModeToggleAudio": "аудио",
"MediaPickerTitle": "выбери, что сохранить",
"MediaPickerExplanationPC": "кликни то, что хочешь скачать. также можно скачать правой кнопки мыши.",
"MediaPickerExplanationPhone": "нажми, или нажми и удерживай, чтобы скачать.",
"MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью и выбери \"загрузить файл по ссылке\", чтобы скачать.",
"TwitterSpaceWasntRecorded": "мне нечего скачать, так как этот twitter space не был записан. попробуй другой!",
"ErrorCantProcess": "я не смог обработать твой запрос :(\nты можешь попробовать ещё раз, но если не поможет, то {ContactLink}.",
"ChangelogPressToHide": "скрыть",
"Donate": "донаты",
"DonateSub": "ты можешь помочь!",
"DonateExplanation": "кобальт не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно</span> для всех. но разработка и поддержка медиа сервиса, которым пользуются более миллиона людей, обходится довольно затратно.\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": "открыть",
"SettingsVideoMute": "убрать аудио",
"SettingsVideoMuteExplanation": "убирает звук при загрузке видео, но только когда это возможно.",
"ErrorSoundCloudNoClientId": "мне не удалось достать временный токен, который необходим для скачивания аудио из soundcloud. попробуй ещё раз, но если так и не получится, {ContactLink}.",
"CollapseServices": "что поддерживается?",
"CollapseSupport": "поддержка и исходный код",
"CollapsePrivacy": "политика конфиденциальности",
"ServicesNote": "этот список далеко не финальный и постоянно пополняется, заглядывай сюда почаще!",
"FollowSupport": "подписывайся на соц.сети кобальта для новостей и поддержки:",
"SourceCode": "шарься в исходнике, пиши о проблемах, или же форкай репозиторий:",
"PrivacyPolicy": "политика конфиденциальности кобальта довольно проста: никакие данные о тебе никогда не собираются и не хранятся. нуль, ноль, нада, ничего.\nто, что ты скачиваешь, - твоё личное дело, а не чьё-либо ещё.\n\nесли твоей загрузке требуется рендер, то зашифрованные данные о ней временно хранятся в ОЗУ сервера. это необходимо для работы данной функции.\n\nзашифрованные данные хранятся в течение <span class=\"text-backdrop\">90 секунд</span> и затем безвозвратно удаляются.\n\ncохранённые данные можно расшифровать только с помощью уникальных ключей шифрования из твоей ссылки на скачивание. кроме того, официальная кодовая база кобальта не предусматривает возможности чтения эти данные вне функций обработки.\n\nты всегда можешь посмотреть <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">исходный код кобальта</a> и убедиться, что всё так, как заявлено.",
"ErrorYTUnavailable": "это видео недоступно. возможно оно ограничено по доступу или региону. попробуй другое!",
"ErrorYTTryOtherCodec": "я не нашёл того, что мог бы скачать с твоими настройками. попробуй другой кодек или качество в настройках!",
"SettingsCodecSubtitle": "кодек для youtube видео",
"SettingsCodecDescription": "h264: лучшая совместимость, средний уровень детализированности. максимальное качество - 1080p.\nav1: лучшее качество, маленький размер файла, наибольшее количество деталей. поддерживает 8k и HDR.\nvp9: такая же детализированность, как и у av1, но файл в 2 раза больше. поддерживает 4k и HDR.\n\nвыбирай h264, если тебе нужна наилучшая совместимость.\nвыбирай av1, если ты хочешь лучшее качество и эффективность.",
"SettingsAudioDub": "звуковая дорожка для youtube видео",
"ShareURL": "поделиться",
"ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость ограничена. попробуй другой!",
"PopupCloseDone": "готово",
"Accessibility": "общедоступность",
"SettingsReduceTransparency": "уменьшить прозрачность",
"SettingsDisableAnimations": "убрать анимации",
"FeatureErrorGeneric": "твой браузер не разрешает или не поддерживает эту функцию. проверь наличие обновлений и попробуй ещё раз!",
"ClipboardErrorFirefox": "ты используешь firefox в котором все функции чтения из буфера обмена отключены по умолчанию.\n\nно это можно исправить следуя шагам, описанным <a class=\"text-backdrop link\" href=\"{repo}/blob/current/docs/troubleshooting.md#how-to-fix-clipboard-pasting-in-firefox\" target=\"_blank\">здесь</a>\n\n...или же ты можешь просто вставить ссылку вручную.",
"ClipboardErrorNoPermission": "кобальт не может прочитать последний элемент в буфере обмена без твоего разрешения.\n\nесли ты не хочешь давать доступ, просто вставь ссылку вручную.\n\nну а если хочешь, то открой настройки сайта и разреши доступ на чтение буфера обмена.",
"SupportSelfTroubleshooting": "возникли проблемы? попробуй сначала что-то из этого:",
"AccessibilityGoBack": "вернуться назад и закрыть окно",
"CollapseKeyboard": "горячие клавиши",
"KeyboardShortcutsIntro": "пользуйся кобальтом ещё быстрее с горячими клавишами:",
"KeyboardShortcutQuickPaste": "вставить ссылку",
"KeyboardShortcutClear": "очистить зону вставки ссылки",
"KeyboardShortcutClosePopup": "закрыть все окна",
"CollapseLegal": "принципы и этика",
"FairUse": "кобальт - это веб инструмент для облегчения скачивания контента из интернета. сервера обработки работают как <span class=\"text-backdrop\">ограниченные прокси</span>, так что ничего никогда не сохраняется или кэшируется.\n\nкобальт <span class=\"text-backdrop\">не несёт никакой ответственности</span>, только ты (конечный пользователь) несёшь ответственность за то, что скачиваешь, как используешь и распространяешь скачанный контент. будь сознателен при использовании чужого контента и всегда указывай авторов!\n\nприкладывай ссылку на источник при использовании в образовательных целях (лекции, домашние задания и т.п.)\n\nчестное использование и указание авторства выгодно всем.",
"SettingsDisableMetadata": "не добавлять метаданные",
"NewDomainWelcomeTitle": "привет!",
"NewDomainWelcome": "кобальт переезжает! те же функции, тот же владелец, просто более запоминающийся домен. по-прежнему без рекламы.\n\n<span class=\"text-backdrop\">cobalt.tools</span> - новый основной домен, т.е. где ты сейчас находишься. не забудь обновить закладки и переустановить веб-приложение!",
"DataTransferSuccess": "кстати, твои настройки были перенесены автоматически :)",
"DataTransferError": "при переносе настроек что-то пошло не так. придётся зайти в настройки и настроить кобальт вручную.",
"SupportNotAffiliated": "кобальт <span class=\"text-backdrop\">не аффилирован</span> ни с одним из перечисленных выше сервисов.",
"SupportMetaNoticeRU": "деятельность meta platforms inc. (владелец instagram) запрещена на территории россии.",
"SponsoredBy": "спонсируется",
"FilenameTitle": "стиль названий файлов",
"FilenamePatternClassic": "классический",
"FilenamePatternPretty": "красивый",
"FilenamePatternBasic": "простой",
"FilenamePatternNerdy": "полный",
"FilenameDescription": "классический: стандартный стиль названия файлов кобальта.\nпростой: название и основная инфа в скобках.\nкрасивый: название и инфа в скобках.\nполный: название и вся инфа в скобках.\n\nнекоторые сервисы не поддерживают красивые имена файлов и всегда используют классический стиль.",
"Preview": "превью",
"FilenamePreviewVideoTitle": "Название Видео",
"FilenamePreviewAudioTitle": "Название Аудио",
"FilenamePreviewAudioAuthor": "Автор Аудио",
"StatusPage": "статус серверов",
"TroubleshootingGuide": "гайд по устранению проблем",
"DonateImageDescription": "кошка спит на клавиатуре ноутбука и многократно печатает буквы",
"SettingsTwitterGif": "конвертировать гифки в .gif",
"SettingsTwitterGifDescription": "конвертирование зацикленного видео в .gif снижает качество и значительно увеличивает размер файла. если важна максимальная эффективность, то не используй эту функцию.",
"ErrorTweetProtected": "этот твит из закрытого аккаунта, поэтому я не могу его увидеть. попробуй другой!",
"ErrorTweetNSFW": "этот твит содержит деликатный контент, поэтому я не могу его увидеть. попробуй другой!",
"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>. а если же ты хочешь исключить себя из статистики, то это можно сделать в настройках > другое.",
"SettingsTikTokH265": "предпочитать h265",
"SettingsTikTokH265Description": "скачивает видео с tiktok в 1080p и h265/hevc, когда это возможно.",
"SettingsYoutubeDub": "использовать язык браузера",
"SettingsYoutubeDubDescription": "использует главный язык браузера для аудиодорожек на youtube. работает даже если кобальт не переведён в твой язык.",
"UpdateOneMillion": "миллион и невероятная скорость",
"ErrorYTAgeRestrict": "это видео ограничено по возрасту, поэтому я не могу его скачать. попробуй другое!",
"ErrorYTLogin": "не удалось получить это видео с youtube, т. к. для его просмотра требуется учетная запись.\n\nтакое ограничение сделано google, чтобы, по-видимому, помешать скрапингу, но в итоге ломает сторонние программы и даже собственные клиенты.\n\nпопробуй ещё раз, но если проблема останется, то {ContactLink}.",
"ErrorYTRateLimit": "youtube ограничил мне частоту запросов. попробуй ещё раз через несколько секунд, но если проблема останется, то {ContactLink}."
}
}

View file

@ -1,51 +0,0 @@
import * as fs from "fs";
import { links, repo } from "../modules/config.js";
import { loadJSON } from "../modules/sub/loadFromFs.js";
const locPath = './src/localization/languages';
let loc = {}
let languages = [];
export async function loadLoc() {
const files = await fs.promises.readdir(locPath).catch(() => []);
files.forEach(file => {
loc[file.split('.')[0]] = loadJSON(`${locPath}/${file}`);
languages.push(file.split('.')[0])
});
}
export function replaceBase(s) {
return s
.replace(/\n/g, '<br>')
.replace(/{saveToGalleryShortcut}/g, links.saveToGalleryShortcut)
.replace(/{saveToFilesShortcut}/g, links.saveToFilesShortcut)
.replace(/{repo}/g, repo)
.replace(/{statusPage}/g, links.statusPage)
.replace(/\*;/g, "&bull;");
}
export function replaceAll(lang, str, string, replacement) {
let s = replaceBase(str[string])
if (replacement) s = s.replace(/{s}/g, replacement);
if (s.match('{')) {
Object.keys(loc[lang]["substrings"]).forEach(sub => {
s = replaceBase(s.replace(`{${sub}}`, loc[lang]["substrings"][sub]))
});
}
return s
}
export default function(lang, string, replacement) {
try {
if (!Object.keys(loc).includes(lang)) lang = 'en';
let str = loc[lang]["strings"];
if (str && str[string]) {
return replaceAll(lang, str, string, replacement)
} else {
str = loc["en"]["strings"];
return replaceAll(lang, str, string, replacement)
}
} catch (e) {
return `!!${string}!!`
}
}
export const languageList = languages;

View file

@ -19,10 +19,8 @@ export const
audioIgnore = servicesConfigJson.audioIgnore, audioIgnore = servicesConfigJson.audioIgnore,
version = packageJson.version, version = packageJson.version,
genericUserAgent = config.genericUserAgent, genericUserAgent = config.genericUserAgent,
repo = packageJson.bugs.url.replace('/issues', ''),
ffmpegArgs = config.ffmpegArgs, ffmpegArgs = config.ffmpegArgs,
supportedAudio = config.supportedAudio, supportedAudio = config.supportedAudio,
links = config.links,
env = { env = {
apiURL: process.env.API_URL || '', apiURL: process.env.API_URL || '',
apiPort: process.env.API_PORT || 9000, apiPort: process.env.API_PORT || 9000,

View file

@ -2,7 +2,6 @@ import { strict as assert } from "node:assert";
import { env } from '../config.js'; import { env } from '../config.js';
import { createResponse } from "../processing/request.js"; import { createResponse } from "../processing/request.js";
import loc from "../../localization/manager.js";
import { testers } from "./servicesPatternTesters.js"; import { testers } from "./servicesPatternTesters.js";
import matchActionDecider from "./matchActionDecider.js"; import matchActionDecider from "./matchActionDecider.js";
@ -51,12 +50,15 @@ export default async function(host, patternMatch, lang, obj) {
if (!testers[host]) { if (!testers[host]) {
return createResponse("error", { return createResponse("error", {
t: loc(lang, 'ErrorUnsupported') code: "ErrorUnsupported"
}); });
} }
if (!(testers[host](patternMatch))) { if (!(testers[host](patternMatch))) {
return createResponse("error", { return createResponse("error", {
t: loc(lang, 'ErrorBrokenLink', host) code: "ErrorBrokenLink",
context: {
service: host
}
}); });
} }
@ -208,7 +210,7 @@ export default async function(host, patternMatch, lang, obj) {
break; break;
default: default:
return createResponse("error", { return createResponse("error", {
t: loc(lang, 'ErrorUnsupported') code: "ErrorUnsupported"
}); });
} }
@ -217,14 +219,14 @@ export default async function(host, patternMatch, lang, obj) {
if (r.error && r.critical) { if (r.error && r.critical) {
return createResponse("critical", { return createResponse("critical", {
t: loc(lang, r.error) code: r.error
}) })
} }
if (r.error) { if (r.error) {
return createResponse("error", { return createResponse("error", {
t: Array.isArray(r.error) code: r.error,
? loc(lang, r.error[0], r.error[1]) context: response?.context
: loc(lang, r.error)
}) })
} }
@ -236,7 +238,10 @@ export default async function(host, patternMatch, lang, obj) {
) )
} catch { } catch {
return createResponse("error", { return createResponse("error", {
t: loc(lang, 'ErrorBadFetch', host) code: "ErrorBadFetch",
context: {
service: host
}
}) })
} }
} }

View file

@ -1,6 +1,5 @@
import { audioIgnore, services, supportedAudio } from "../config.js"; import { audioIgnore, services, supportedAudio } from "../config.js";
import { createResponse } from "../processing/request.js"; import { createResponse } from "../processing/request.js";
import loc from "../../localization/manager.js";
import createFilename from "./createFilename.js"; import createFilename from "./createFilename.js";
import { createStream } from "../stream/manage.js"; import { createStream } from "../stream/manage.js";
@ -38,7 +37,9 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di
switch (action) { switch (action) {
default: default:
return createResponse("error", { t: loc(lang, 'ErrorEmptyDownload') }); return createResponse("error", {
code: "ErrorEmptyDownload"
});
case "photo": case "photo":
responseType = "redirect"; responseType = "redirect";
@ -145,9 +146,10 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di
break; break;
case "audio": case "audio":
if (audioIgnore.includes(host) if (audioIgnore.includes(host) || (host === "reddit" && r.typeId === "redirect")) {
|| (host === "reddit" && r.typeId === "redirect")) { return createResponse("error", {
return createResponse("error", { t: loc(lang, 'ErrorEmptyDownload') }) code: "ErrorEmptyDownload"
})
} }
let processType = "render", let processType = "render",

View file

@ -23,12 +23,16 @@ const apiVar = {
} }
export function createResponse(responseType, responseData) { export function createResponse(responseType, responseData) {
const internalError = (text) => { const internalError = (code) => {
let error = code || "Internal Server Error";
return { return {
status: 500, status: 500,
body: { body: {
status: "error", status: "error",
text: text || "Internal Server Error", error: {
code: code || "Internal Server Error",
},
text: error, // temporary backwards compatibility
critical: true critical: true
} }
} }
@ -50,22 +54,30 @@ export function createResponse(responseType, responseData) {
switch (responseType) { switch (responseType) {
case "error": case "error":
response = {
error: {
code: responseData.code,
context: responseData?.context,
},
text: responseData.code, // temporary backwards compatibility
}
break;
case "success": case "success":
case "rate-limit": case "rate-limit":
response = { response = {
text: responseData.t text: responseData.t,
} }
break; break;
case "redirect": case "redirect":
response = { response = {
url: responseData.u url: responseData.u,
} }
break; break;
case "stream": case "stream":
response = { response = {
url: createStream(responseData) url: createStream(responseData),
} }
break; break;
@ -74,18 +86,18 @@ export function createResponse(responseType, responseData) {
audio = false; audio = false;
if (responseData.service === "tiktok") { if (responseData.service === "tiktok") {
audio = responseData.u audio = responseData.u;
pickerType = "images" pickerType = "images";
} }
response = { response = {
pickerType: pickerType, pickerType: pickerType,
picker: responseData.picker, picker: responseData.picker,
audio: audio audio: audio,
} }
break; break;
case "critical": case "critical":
return internalError(responseData.t) return internalError(responseData.code);
default: default:
throw "unreachable" throw "unreachable"
} }