From 7f533b3485c8770bf5ad1307492ac911f741e8a9 Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 24 Mar 2023 23:16:10 +0600 Subject: [PATCH] retweet links, new clipboard icon, mobile layout and loc improvements - added support for retweet links - updated spaces endpoint - bumped up the user agent version - new clipboard icon - new clipboard + auto mode layout on mobile, less wasted space - fixed button press animations for safari on mobile - tons of localization improvements for english and russian - bumped up youtubei.js to 4.1.0 --- .gitignore | 5 +- package.json | 2 +- src/config.json | 2 +- src/front/cobalt.css | 21 +++-- src/front/emoji/clipboard.svg | 17 ++-- src/localization/languages/en.json | 69 ++++++++-------- src/localization/languages/ru.json | 95 +++++++++++----------- src/modules/pageRender/page.js | 2 +- src/modules/processing/services/twitter.js | 21 +++-- src/test/tests.json | 12 +++ 10 files changed, 137 insertions(+), 109 deletions(-) diff --git a/.gitignore b/.gitignore index 9cf0b29..305746b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,8 @@ package-lock.json # esbuild min -#page build +# page build build + +# stuff i already made but delayed +future diff --git a/package.json b/package.json index d91e86f..d2ebe13 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,6 @@ "node-cache": "^5.1.2", "url-pattern": "1.0.3", "xml-js": "^1.6.11", - "youtubei.js": "^3.3.0" + "youtubei.js": "4.1.0" } } diff --git a/src/config.json b/src/config.json index 7828252..5635c17 100644 --- a/src/config.json +++ b/src/config.json @@ -1,7 +1,7 @@ { "streamLifespan": 120000, "maxVideoDuration": 10800000, - "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", + "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", "authorInfo": { "name": "wukko", "link": "https://wukko.me/", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index 88c5b13..dc7015e 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -192,10 +192,6 @@ input[type="checkbox"] { flex-direction: row; justify-content: space-between; } -#cobalt-main-box #bottom button { - width: auto!important; - padding: 0.6rem 1.2rem!important; -} .box { background: var(--background); border: var(--border-15); @@ -593,6 +589,10 @@ input[type="checkbox"] { margin-top: 0!important; margin-bottom: var(--padding-1); } +#cobalt-main-box #bottom button { + width: auto; + padding: 0.6rem 1.2rem; +} .collapse-list { background: var(--accent-press); user-select: none; @@ -746,11 +746,16 @@ input[type="checkbox"] { } } @media screen and (max-width: 720px) { - #cobalt-main-box #bottom { - flex-direction: column; - } #cobalt-main-box #bottom button { - width: 100%!important; + width: 100%; + } + #pasteFromClipboard .emoji { + margin-right: 0; + } + #pasteFromClipboard { + width: 20%!important; + min-width: 15%; + font-size: 0; } #footer { bottom: 4%; diff --git a/src/front/emoji/clipboard.svg b/src/front/emoji/clipboard.svg index 600e532..b4d2822 100644 --- a/src/front/emoji/clipboard.svg +++ b/src/front/emoji/clipboard.svg @@ -1,10 +1,11 @@ - - - - - - - - + + + + + + + + + diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 1fe25bf..7f1f591 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -1,12 +1,12 @@ { "name": "english", "substrings": { - "ContactLink": "file an issue on github" + "ContactLink": "create an issue on github" }, "strings": { "LinkInput": "paste the link here", - "AboutSummary": "{appName} is your go-to place for social media downloads. zero ads, trackers, or any other creepy bullshit. simply paste a share link and you're ready to rock!", - "EmbedBriefDescription": "save content from social media without annoyances", + "AboutSummary": "{appName} 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 without ads, trackers, or other creepy bullshit.", "MadeWithLove": "made with <3 by wukko", "AccessibilityInputArea": "link input area", "AccessibilityOpenAbout": "open about popup", @@ -19,20 +19,19 @@ "TitlePopupError": "uh-oh...", "TitlePopupChangelog": "what's new?", "TitlePopupDonate": "support {appName}", - "TitlePopupDownload": "download", - "ErrorSomethingWentWrong": "something went wrong and i couldn't get anything for you. you can try again, but if issue persists, please {ContactLink}.", - "ErrorUnsupported": "it seems like this service is not supported yet or your link is invalid.", + "TitlePopupDownload": "how to continue?", + "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": "something went wrong and page couldn't render. if it's a recurring or critical issue, please {ContactLink}. it'd be useful if you provided current commit hash ({s}) and error recreation steps. thank you in advance :D", + "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 a minute!", - "ErrorCouldntFetch": "couldn't get any info about your link. check if it's correct and try again.", - "ErrorLengthLimit": "current length limit is {s} minutes. video that you tried to download is longer than {s} minutes. pick something else!", - "ErrorBadFetch": "an error occurred when i tried to get info about your link. are you sure it works? check if it does, and try again.", - "ErrorCorruptedStream": "this download is unfortunately corrupted. try again!", + "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 {appName} api is down. check your connection and try again.", - "ErrorCantConnectToServiceAPI": "i couldn't connect to the service api. it could be down, or {appName} could've gotten blocked. try again a bit later!", - "ErrorEmptyDownload": "i don't see anything i could download from here. try a different link.", + "ErrorCantConnectToServiceAPI": "i couldn't connect to the service api. maybe it's down, or {appName} 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", @@ -43,11 +42,11 @@ "SettingsThemeDark": "dark", "SettingsKeepDownloadButton": "keep >> visible", "AccessibilityKeepDownloadButton": "keep the download button always visible", - "SettingsEnableDownloadPopup": "ask for a way to save", + "SettingsEnableDownloadPopup": "ask how to save", "AccessibilityEnableDownloadPopup": "ask what to do with downloads", "SettingsQualityDescription": "if selected quality isn't available, closest one is used instead.", "LinkGitHubChanges": ">> see previous commits and contribute on github", - "NoScriptMessage": "{appName} uses javascript for api requests and interactive interface. you have to allow javascript to use this site. i don't have any ads or trackers, pinky promise.", + "NoScriptMessage": "{appName} 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": "press and hold the download button, hide the video preview, and then select \"download linked file\" to save.", "DownloadPopupDescription": "download button opens a new tab with requested file. you can disable this popup in settings.", "DownloadPopupWayToSave": "pick a way to save", @@ -62,45 +61,45 @@ "SettingsOtherTab": "other", "ChangelogLastMajor": "current version & commit", "AccessibilityModeToggle": "toggle download mode", - "DonateLinksDescription": "donation links open in a new tab. this is the best way to donate if you want me to receive your donation directly.", + "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 in best quality available, it's not re-encoded. everything else will be re-encoded.", + "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", "SettingsRemoveWatermark": "disable watermark", "ErrorPopupCloseButton": "got it", - "ErrorLengthAudioConvert": "current length limit for audio conversion is {s} minutes. pick \"best\" format if you want to avoid limitations.", - "SettingsAudioFullTikTok": "download full audio", - "SettingsAudioFullTikTokDescription": "downloads original audio or sound used in video without any additional changes by the video author.", - "ErrorCantGetID": "i couldn't get the full info from the shortened link. make sure it works or try a full one.", - "ErrorNoVideosInTweet": "i couldn't find any videos or gifs in this tweet. try another one!", + "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": "there are no videos or gifs 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": "server didn't return a download link. this should never happen. reload the page and try again, but if it doesn't help, {ContactLink}.", - "ErrorUnknownStatus": "i received a response i can't process. most likely something with status is wrong. this should never happen. reload the page and try again, but if it doesn't help, {ContactLink}.", - "PasteFromClipboard": "paste from clipboard", + "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": "press to expand", + "ChangelogPressToExpand": "expand", "Miscellaneous": "miscellaneous", "ModeToggleAuto": "auto mode", "ModeToggleAudio": "audio mode", - "SettingsDisableNotifications": "hide notification dots", + "SettingsDisableNotifications": "hide notifications", "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.", "MediaPickerExplanationPhoneIOS": "press and hold, hide the preview, and then select \"download linked file\" to save.", "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": "press to collapse", + "ChangelogPressToHide": "collapse", "Donate": "donate", "DonateSub": "help me keep it up", - "DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's completely free to use. but turns out keeping up a web service used by thousands of people is somewhat costly.\n\nif you ever found {appName} useful and want to keep it online, or simply want to thank the developer, consider chipping in! each and every cent helps and is VERY appreciated.", + "DonateExplanation": "{appName} does not (and will never) serve ads or sell your data, therefore it's completely free to use. but turns out keeping up a web service used by over 40 thousand of people is somewhat costly.\n\nif you ever found {appName} useful and want to keep it online, or simply want to thank the developer, consider chipping in! each and every cent helps and is VERY appreciated :D", "DonateVia": "donate via", - "DonateHireMe": "or, as an alternative, you can hire me.", + "DonateHireMe": "or you can hire me", "SettingsVideoMute": "mute audio", - "SettingsVideoMuteExplanation": "disables audio in downloaded video when possible.", - "ErrorSoundCloudNoClientId": "couldn't find client_id that is required to fetch audio data from soundcloud. try again, and if issue persists, {ContactLink}.", + "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", @@ -108,7 +107,7 @@ "FollowSupport": "follow {appName} on mastodon or twitter for support, polls, news, and more:", "SupportNote": "please note that questions and issues may take a while to respond to, there's only one person managing everything.", "SourceCode": "report issues, explore source code, star or fork the repo:", - "PrivacyPolicy": "{appName}'s privacy policy is simple: no data about you is collected or stored. zero, zilch, nada, nothing.\nwhat you download is your business, not mine.\n\nsome non-backtraceable data does get temporarily stored when requested download requires live render. it's necessary for that feature to function.\n\nin that case, salted sha256 hash of your ip address and information about requested stream are temporarily stored in server's RAM for 2 minutes. after 2 minutes all previously stored information is permanently removed. hash of your ip address is used for limiting stream access only to you.\nno one (even me) has access to this data, because official {appName} codebase doesn't provide a way to read it outside of processing functions in the first place.\n\nyou can check {appName}'s github repo for yourself and see that indeed nothing is stored permanently.", + "PrivacyPolicy": "{appName}'s privacy policy is simple: no data about you is ever collected or stored. zero, zilch, nada, nothing.\nwhat you download is your business, not mine.\n\nsome non-backtraceable data does get temporarily stored when requested download requires live render. it's necessary for that feature to function.\n\nin that case, salted sha256 hash of your ip address and information about requested stream are temporarily stored in server's RAM for 2 minutes. after 2 minutes all previously stored information is permanently removed. hash of your ip address is used for limiting stream access only to you.\nno one (even me) has access to this data, because official {appName} codebase doesn't provide a way to read it outside of processing functions in the first place.\n\nyou can check {appName}'s github repo yourself and see that everything is as stated.", "ErrorYTUnavailable": "this youtube video is unavailable or age restricted. i am currently unable to download videos with sensitive content. try another one!", "ErrorYTTryOtherCodec": "i couldn't find anything to download with your settings. try another codec or quality!\n\nnote: youtube api sometimes acts unexpectedly. blame google for this, not me.", "SettingsCodecSubtitle": "youtube codec", @@ -118,6 +117,6 @@ "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 {appName} into one file. max quality is 4k.\n\npick progressive if you want best editor/player/social media compatibility. sometimes progressive downloads aren't available, and then dash is used instead." + "SettingsVimeoPreferDescription": "progressive: direct file link to vimeo's cdn. max quality is 1080p.\ndash: video and audio are merged by {appName} 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." } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index e448398..c9b4522 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -1,12 +1,12 @@ { "name": "русский", "substrings": { - "ContactLink": "напиши об этом на github" + "ContactLink": "напиши об этом на github (можно на русском)" }, "strings": { "LinkInput": "вставь ссылку сюда", - "AboutSummary": "{appName} — твой друг при скачивании контента из соц. сетей. никакой рекламы или трекеров. вставляешь ссылку и получаешь файл. ничего лишнего.", - "EmbedBriefDescription": "сохраняй что хочешь, без мороки и вторжения в личное пространство", + "AboutSummary": "{appName} - твой друг при скачивании контента из соцсетей и других сервисов. никакой рекламы, трекеров и прочего мусора. вставляешь ссылку и получаешь файл. всё. ничего лишнего.", + "EmbedBriefDescription": "сохраняй то, что любишь. без рекламы, трекеров и лишней мороки.", "MadeWithLove": "сделано wukko, с <3", "AccessibilityInputArea": "зона вставки ссылки", "AccessibilityOpenAbout": "открыть окно с инфой", @@ -19,21 +19,20 @@ "TitlePopupError": "опаньки...", "TitlePopupChangelog": "что нового?", "TitlePopupDonate": "поддержи {appName}", - "TitlePopupDownload": "скачивание", - "ErrorSomethingWentWrong": "что-то пошло совсем не так, и у меня не получилось ничего для тебя достать. ты можешь попробовать ещё раз, но если так и не получится, {ContactLink}.", + "TitlePopupDownload": "как продолжить?", + "ErrorSomethingWentWrong": "что-то пошло совсем не так и у меня не получилось ничего для тебя достать. попробуй ещё раз, но если так и не получится, {ContactLink}.", "ErrorUnsupported": "с твоей ссылкой что-то не так, или же этот сервис ещё не поддерживается. может быть, ты вставил не ту ссылку?", "ErrorBrokenLink": "{s} поддерживается, но с твоей ссылкой что-то не так. может быть, ты её не полностью скопировал?", "ErrorNoLink": "пока что я не умею угадывать, что ты хочешь скачать. дай мне, пожалуйста, ссылку :(", - "ErrorPageRenderFail": "что-то пошло не так и у меня не получилось срендерить страницу. если это повторится ещё раз, пожалуйста, {ContactLink}. также приложи хэш текущего коммита ({s}) с действиями для повторения этой ошибки. можно на русском языке. спасибо :)", + "ErrorPageRenderFail": "если ты видишь этот текст, значит что-то не так с рендером страницы. пожалуйста, {ContactLink}. также приложи домен, на котором присутсвует эта ошибка, и хэш коммита ({s}). спасибо :)", "ErrorRateLimit": "ты делаешь слишком много запросов. попробуй ещё раз через минуту!", - "ErrorCouldntFetch": "мне не удалось получить инфу о твоей ссылке. проверь её и попробуй ещё раз.", - "ErrorLengthLimit": "твоё видео длиннее чем {s} минут(ы). это превышает текущий лимит. скачай что-нибудь покороче, а не экранизацию \"войны и мира\".", - "ErrorBadFetch": "произошла ошибка при получении инфы о твоей ссылке. ты уверен, что она работает? проверь её, и попробуй ещё раз.", - "ErrorCorruptedStream": "этот файл сломан на стороне сервера. попробуй ещё раз чуть позже!.", - "ErrorNoInternet": "кажется, нет подключения к интернету. возможно лежит сервер {appName}. в любом случае, проверь подключение к интернету и попробуй ещё раз.", - "ErrorCantConnectToServiceAPI": "у меня не получилось подключиться к серверу этого сервиса. скорее всего он лежит, или же ip адрес {appName} добавили в чёрный список. попробуй ещё раз чуть позже!", + "ErrorCouldntFetch": "у меня не получилось ничего найти по этой ссылке. убедись, что она работает, и попробуй ещё раз. некоторый контент может быть залочен на регион.", + "ErrorLengthLimit": "я не могу обрабатывать видео длиннее чем {s} минут(ы), так что скачай что-нибудь покороче!", + "ErrorBadFetch": "произошла какая-то ошибка при получении данных по твоей ссылке. убедись, что она работает, и попробуй ещё раз.", + "ErrorNoInternet": "не получилось подключиться к серверу. проверь подключение к интернету и попробуй ещё раз!", + "ErrorCantConnectToServiceAPI": "у меня не получилось подключиться к серверу этого сервиса. возможно он лежит, или же {appName} заблокировали. попробуй ещё раз, но если так и не получится, {ContactLink}.", "ErrorEmptyDownload": "я не нашёл того, что могу скачать. попробуй другую ссылку!", - "ErrorLiveVideo": "я не гадалка, и пока что не умею заглядывать в будущее. дождись окончания прямого эфира и попробуй ещё раз!", + "ErrorLiveVideo": "я пока что не умею заглядывать в будущее, поэтому дождись окончания прямого эфира, и потом уже скачивай видео!", "SettingsAppearanceSubtitle": "внешний вид", "SettingsThemeSubtitle": "тема", "SettingsFormatSubtitle": "формат", @@ -41,15 +40,15 @@ "SettingsThemeAuto": "авто", "SettingsThemeLight": "светлая", "SettingsThemeDark": "тёмная", - "SettingsKeepDownloadButton": "оставлять >> на экране", - "AccessibilityKeepDownloadButton": "оставлять кнопку скачивания на экране", - "SettingsEnableDownloadPopup": "спрашивать, что делать при скачивании", + "SettingsKeepDownloadButton": "всегда показывать >>", + "AccessibilityKeepDownloadButton": "всегда показывать кнопку скачивания на экране", + "SettingsEnableDownloadPopup": "выбор метода скачивания", "AccessibilityEnableDownloadPopup": "спрашивать, что делать с загрузками", "SettingsQualityDescription": "если выбранное качество недоступно, то выбирается ближайшее к нему.", "LinkGitHubChanges": ">> смотри предыдущие изменения на github", - "NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких трекеров или рекламы, обещаю.", - "DownloadPopupDescriptionIOS": "зажми кнопку \"скачать\", затем скрой превью видео и выбери \"загрузить файл по ссылке\" в появившемся окне.", - "DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода сохранения файла в настройках.", + "NoScriptMessage": "{appName} использует javascript для обработки ссылок и интерактивного интерфейса. ты должен разрешить использование javascript, чтобы пользоваться сайтом. тут нет никаких зловредных скриптов, обещаю.", + "DownloadPopupDescriptionIOS": "зажми кнопку \"скачать\", затем скрой превью и выбери \"загрузить файл по ссылке\" в появившемся окне.", + "DownloadPopupDescription": "кнопка скачивания открывает новое окно с файлом. ты можешь отключить выбор метода скачивания файла в настройках.", "DownloadPopupWayToSave": "выбери, как сохранить", "ClickToCopy": "нажми, чтобы скопировать", "Download": "скачать", @@ -62,60 +61,62 @@ "SettingsOtherTab": "другое", "ChangelogLastMajor": "текущая версия и коммит (на английском)", "AccessibilityModeToggle": "переключить режим скачивания", - "DonateLinksDescription": "ссылки на донаты открываются в новой вкладке. это наилучший способ отправить донат, если ты хочешь, чтобы я получил его напрямую.", + "DonateLinksDescription": "это лучший способ отправить донат, если ты хочешь, чтобы я получил его лично.", "SettingsAudioFormatBest": "лучший", - "SettingsAudioFormatDescription": "когда выбран \"лучший\" формат, ты получишь аудио наилучшего качества, так как оно не будет сконвертировано. если же выбрано что-то другое, то аудио будет немного сжато.", + "SettingsAudioFormatDescription": "когда выбран \"лучший\", ты получишь аудио без каких-либо изменений. такое, какое оно есть на стороне сервиса. если же выбрано что-то другое, то аудио будет немного сжато.", "Keyphrase": "сохраняй то, что любишь", - "SettingsRemoveWatermark": "убрать ватермарку", + "SettingsRemoveWatermark": "убирать ватермарку", "ErrorPopupCloseButton": "ясно", - "ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат аудио, чтобы избежать ограничения.", - "SettingsAudioFullTikTok": "скачивать полное аудио", - "SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, который использован в видео, без каких-либо изменений от автора видео.", - "ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, или же попробуй позже.", + "ErrorLengthAudioConvert": "я не могу конвертировать аудио дольше чем {s} минут(ы). выбери \"лучший\" формат, чтобы обойти ограничения.", + "SettingsAudioFullTikTok": "полное аудио", + "SettingsAudioFullTikTokDescription": "скачивает оригинальный звук, использованный в видео. без каких-либо изменений от автора поста.", + "ErrorCantGetID": "у меня не получилось достать инфу по этой короткой ссылке. попробуй полную ссылку, а если так и не получится, то {ContactLink}.", "ErrorNoVideosInTweet": "в этом твите нет ни видео, ни гифок. попробуй другой!", "ImagePickerTitle": "выбери картинки для скачивания", - "ImagePickerDownloadAudio": "скачать аудио", - "ImagePickerExplanationPC": "нажми правой кнопкой мыши на изображение, чтобы его сохранить.", - "ImagePickerExplanationPhone": "зажми и удерживай изображение, чтобы его сохранить.", - "ErrorNoUrlReturned": "я не получил ссылку для скачивания от сервера. такого происходить не должно. перезагрузи страницу, а если не поможет, то {ContactLink}.", - "ErrorUnknownStatus": "сервер ответил мне чем-то непонятным. такого происходить не должно. перезагрузи страницу, а если не поможет, то {ContactLink}.", - "PasteFromClipboard": "вставить из буфера обмена", + "ImagePickerDownloadAudio": "скачать звук", + "ImagePickerExplanationPC": "нажми правой кнопкой мыши на картинку, чтобы её сохранить.", + "ImagePickerExplanationPhone": "зажми и удерживай картинку, чтобы её сохранить.", + "ErrorNoUrlReturned": "я не получил ссылку для скачивания от сервера. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.", + "ErrorUnknownStatus": "сервер ответил мне чем-то непонятным. такого происходить не должно. попробуй ещё раз, а если не поможет, то {ContactLink}.", + "PasteFromClipboard": "вставить", "ChangelogOlder": "предыдущие версии (на английском)", - "ChangelogPressToExpand": "нажми, чтобы раскрыть", + "ChangelogPressToExpand": "раскрыть", "Miscellaneous": "разное", "ModeToggleAuto": "авто режим", "ModeToggleAudio": "аудио режим", - "SettingsDisableNotifications": "cкрыть значки уведомлений", + "SettingsDisableNotifications": "cкрыть уведомления", "MediaPickerTitle": "выбери, что сохранить", "MediaPickerExplanationPC": "кликни, чтобы скачать. также можно скачать через контекстное меню правой кнопки мыши.", "MediaPickerExplanationPhone": "нажми, или нажми и удерживай, чтобы скачать.", - "MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью, и наконец выбери \"загрузить файл по ссылке\".", - "TwitterSpaceWasntRecorded": "этот twitter space не был записан, поэтому я не могу его скачать. попробуй другой!", + "MediaPickerExplanationPhoneIOS": "нажми и удерживай, затем скрой превью и выбери \"загрузить файл по ссылке\", чтобы скачать.", + "TwitterSpaceWasntRecorded": "мне нечего скачать, так как этот twitter space не был записан. попробуй другой!", "ErrorCantProcess": "я не смог обработать твой запрос :(\nты можешь попробовать ещё раз, но если не поможет, то {ContactLink}.", - "ChangelogPressToHide": "нажми, чтобы скрыть", - "Donate": "донаты", + "ChangelogPressToHide": "скрыть", + "Donate": "задонатить", "DonateSub": "ты можешь помочь!", - "DonateExplanation": "{appName} не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает совершенно бесплатно. но оказывается, что хостинг сервиса, которым пользуются тысячи людей, обходится довольно дорого.\n\nесли ты хочешь, чтобы твой любимый загрузчик оставался онлайн, а разработчик не помер с голоду вместе с двумя котами, то подумай над тем, чтобы задонатить. каждый рубль поможет мне, моим котам, и {appName}!", + "DonateExplanation": "{appName} не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает совершенно бесплатно. но оказывается, что хостинг сервиса, которым пользуются более 40 тысяч людей, обходится довольно дорого.\n\nесли {appName} тебе помог и ты хочешь поблагодарить или помочь разработчику, то это можно сделать через донаты! каждый рубль помогает мне, моим котам, и {appName}! спасибо :)", "DonateVia": "открыть", - "DonateHireMe": "или же ты можешь пригласить меня на работу.", - "SettingsVideoMute": "отключить аудио", - "SettingsVideoMuteExplanation": "убирает аудио при загрузке видео когда это возможно.", - "ErrorSoundCloudNoClientId": "мне не удалось достать client_id, который необходим для получения аудио из soundcloud. попробуй ещё раз, но если так и не получится, {ContactLink}.", + "DonateHireMe": "или же ты можешь пригласить меня на работу", + "SettingsVideoMute": "убрать аудио", + "SettingsVideoMuteExplanation": "убирает аудио при загрузке видео, но только когда это возможно.", + "ErrorSoundCloudNoClientId": "мне не удалось достать временный токен, который необходим для скачивания аудио из soundcloud. попробуй ещё раз, но если так и не получится, {ContactLink}.", "CollapseServices": "что поддерживается?", "CollapseSupport": "поддержка и исходный код", "CollapsePrivacy": "политика конфиденциальности", "ServicesNote": "этот список далеко не финальный и постоянно пополняется. заглядывай сюда почаще, тогда точно будешь знать, что поддерживается!", "FollowSupport": "подписывайся на аккаунты {appName} на mastodon или twitter для новостей, поддержки, участия в опросах, и многого другого:", - "SupportNote": "помни, что ответ на твой вопрос может занять время, так как только один человек занимается и разработкой и поддержкой.", + "SupportNote": "так как я один занимаюсь разработкой и поддержкой в одиночку, время ожидания ответа может достигать нескольких часов. я отвечаю всем, не стесняйся.", "SourceCode": "пиши о проблемах, шарься в исходнике, или же форкай репозиторий:", - "PrivacyPolicy": "политика конфиденциальности {appName} довольно проста: ничего не хранится об истории твоих действий или загрузок. совсем. даже ошибки.\nто, что ты скачиваешь - не моё дело, а только твоё.\n\nв случаях, когда твоей загрузке требуется лайв-рендер, временно хранится неотслеживаемая информация. это необходимо для работы данной функции.\n\nв этом случае, sha256 хэш (с солью) твоего ip адреса и данные о запрошенном стриме хранятся в оперативной памяти сервера в течение 2-х минут. по истечении этого времени всё стирается. хэш твоего ip адреса используется для предоставления доступа к стриму только тебе. ни у кого (даже у меня) нет доступа к временно хранящимся данным, так как код {appName} специально не позволяет читать такие данные снаружи.\n\nты можешь посмотреть исходный код {appName} и убедиться, что всё так, как описано.", + "PrivacyPolicy": "политика конфиденциальности {appName} довольно проста: ничего не хранится об истории твоих действий или загрузок. совсем. даже ошибки.\nто, что ты скачиваешь - только твоё личное дело.\n\nв случаях, когда твоей загрузке требуется лайв-рендер, временно хранится неотслеживаемая информация. это необходимо для работы такого типа загрузок.\n\nв этом случае, sha256 хэш (с солью) твоего ip адреса и данные о запрошенном стриме хранятся в ОЗУ сервера в течение двух минут. по истечении этого периода всё стирается. хэш твоего ip адреса используется для предоставления доступа к стриму только тебе. ни у кого (даже у меня) нет доступа к временно хранящимся данным, так как оригинальный код {appName} не предоставляет такой возможности.\n\nты всегда можешь посмотреть исходный код {appName} и убедиться, что всё так, как описано.", "ErrorYTUnavailable": "это видео недоступно или же ограничено по возрасту на youtube. пока что я не умею скачивать подобные видео. попробуй другое!", "ErrorYTTryOtherCodec": "я не нашёл того, что мог бы скачать с твоими настройками. попробуй другой кодек или качество!", "SettingsCodecSubtitle": "кодек для видео с youtube", - "SettingsCodecDescription": "h264: обширная поддержка плеерами, но макс. качество всего лишь 1080p.\nav1: слабая поддержка плеерами, но поддерживает 8k и HDR.\nvp9: обычно наиболее высокий битрейт, лучше сохраняется качество видео. поддерживает 4k и HDR.\n\nесли тебе нужна максимальная совместимость с плеерами/редакторами/соц.сетями, то выбирай h264.", + "SettingsCodecDescription": "h264: обширная поддержка плеерами, но макс. качество всего лишь 1080p.\nav1: слабая поддержка плеерами, но поддерживает 8k и HDR.\nvp9: обычно наиболее высокий битрейт, лучше сохраняется качество видео. поддерживает 4k и HDR.\n\nвыбирай h264, если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями.", "SettingsAudioDub": "звуковая дорожка для видео с youtube", "SettingsAudioDubDescription": "определяет, какая звуковая дорожка используется при скачивании видео. если дублированная дорожка недоступна, то вместо неё используется оригинальная.\n\nоригинал: используется оригинальная дорожка.\nавто: используется язык браузера (и {appName}).", "SettingsDubDefault": "оригинал", - "SettingsDubAuto": "авто" + "SettingsDubAuto": "авто", + "SettingsVimeoPrefer": "тип загрузок с vimeo", + "SettingsVimeoPreferDescription": "progressive: прямая ссылка на файл с сервера vimeo. максимальное качество: 1080p.\ndash: {appName} совмещает видео и аудио в один файл. максимальное качество: 4k.\n\nвыбирай \"progressive\", если тебе нужна наилучшая совместимость с плеерами/редакторами/соцсетями. если \"progressive\" файл недоступен, {appName} скачает \"dash\"." } } diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index 395d7e6..aaae41a 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -68,7 +68,7 @@ export default function(obj) { - + ${multiPagePopup({ name: "about", closeAria: t('AccessibilityClosePopup'), diff --git a/src/modules/processing/services/twitter.js b/src/modules/processing/services/twitter.js index 79f43c8..3323c1d 100644 --- a/src/modules/processing/services/twitter.js +++ b/src/modules/processing/services/twitter.js @@ -39,9 +39,16 @@ export default async function(obj) { req_status = await fetch(showURL, { headers: _headers }).then((r) => { return r.status === 200 ? r.json() : false }).catch(() => { return false }); } if (!req_status) return { error: 'ErrorCouldntFetch' }; - if (!req_status["extended_entities"] || !req_status["extended_entities"]["media"]) return { error: 'ErrorNoVideosInTweet' }; - let single, multiple = [], media = req_status["extended_entities"]["media"]; + let baseStatus; + if (req_status["extended_entities"] && req_status["extended_entities"]["media"]) { + baseStatus = req_status["extended_entities"] + } else if (req_status["retweeted_status"] && req_status["retweeted_status"]["extended_entities"] && req_status["retweeted_status"]["extended_entities"]["media"]) { + baseStatus = req_status["retweeted_status"]["extended_entities"] + } + if (!baseStatus) return { error: 'ErrorNoVideosInTweet' }; + + let single, multiple = [], media = baseStatus["media"]; media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true }) if (media.length > 1) { for (let i in media) { multiple.push({type: "video", thumb: media[i]["media_url_https"], url: bestQuality(media[i]["video_info"]["variants"])}) } @@ -59,16 +66,16 @@ export default async function(obj) { return { error: 'ErrorNoVideosInTweet' } } } else { - _headers["host"] = "twitter.com" - _headers["content-type"] = "application/json" + _headers["host"] = "twitter.com"; + _headers["content-type"] = "application/json"; let query = { - variables: {"id": obj.spaceId,"isMetatagsQuery":true,"withSuperFollowsUserFields":true,"withDownvotePerspective":false,"withReactionsMetadata":false,"withReactionsPerspective":false,"withSuperFollowsTweetFields":true,"withReplays":true}, - features: {"spaces_2022_h2_clipping":true,"spaces_2022_h2_spaces_communities":true,"verified_phone_label_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_uc_gql_enabled":true,"vibe_api_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":false,"interactive_text_enabled":true,"responsive_web_text_conversations_enabled":false,"responsive_web_enhance_cards_enabled":true} + variables: {"id": obj.spaceId,"isMetatagsQuery":true,"withDownvotePerspective":false,"withReactionsMetadata":false,"withReactionsPerspective":false,"withReplays":true}, + features: {"spaces_2022_h2_clipping":true,"spaces_2022_h2_spaces_communities":true,"responsive_web_twitter_blue_verified_badge_is_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"vibe_api_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":false,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"interactive_text_enabled":true,"responsive_web_text_conversations_enabled":false,"longform_notetweets_richtext_consumption_enabled":false,"responsive_web_enhance_cards_enabled":false} } query.variables = new URLSearchParams(JSON.stringify(query.variables)).toString().slice(0, -1); query.features = new URLSearchParams(JSON.stringify(query.features)).toString().slice(0, -1); - query = `https://twitter.com/i/api/graphql/wJ5g4zf7v8qPHSQbaozYuw/AudioSpaceById?variables=${query.variables}&features=${query.features}` + query = `https://twitter.com/i/api/graphql/Gdz2uCtmIGMmhjhHG3V7nA/AudioSpaceById?variables=${query.variables}&features=${query.features}`; let AudioSpaceById = await fetch(query, { headers: _headers }).then((r) => {return r.status === 200 ? r.json() : false}).catch((e) => { return false }); if (!AudioSpaceById) return { error: 'ErrorEmptyDownload' }; diff --git a/src/test/tests.json b/src/test/tests.json index d46baf8..1da56fd 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -95,6 +95,18 @@ "code": 200, "status": "stream" } + }, { + "name": "retweeted video", + "url": "https://twitter.com/winload_exe/status/1633091769482063874", + "params": { + "aFormat": "mp3", + "isAudioOnly": false, + "isAudioMuted": true + }, + "expected": { + "code": 200, + "status": "stream" + } }, { "name": "inexistent post", "url": "https://twitter.com/test/status/9487653",