From a63a35c74d916c77b80aa6441aa0d9e2e12fd465 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 11:38:51 +0600 Subject: [PATCH 1/9] twitter: add option to convert .mp4 to .gif --- src/config.json | 3 +- src/front/cobalt.js | 2 ++ src/localization/languages/en.json | 4 ++- src/modules/pageRender/page.js | 10 ++++++ src/modules/processing/match.js | 9 +++-- src/modules/processing/matchActionDecider.js | 9 +++-- src/modules/processing/services/twitter.js | 9 +++-- src/modules/stream/stream.js | 5 ++- src/modules/stream/types.js | 37 ++++++++++++++++++++ src/modules/sub/utils.js | 5 +-- 10 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/config.json b/src/config.json index 91922a5e..923f4d7b 100644 --- a/src/config.json +++ b/src/config.json @@ -90,7 +90,8 @@ "mp4": ["-c:v", "copy", "-c:a", "copy", "-movflags", "faststart+frag_keyframe+empty_moov"], "copy": ["-c:a", "copy"], "audio": ["-ar", "48000", "-ac", "2", "-b:a", "320k"], - "m4a": ["-movflags", "frag_keyframe+empty_moov"] + "m4a": ["-movflags", "frag_keyframe+empty_moov"], + "gif": ["-vf", "scale=-1:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", "-loop", "0"] }, "sponsors": [{ "name": "royale", diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 31196215..de45aa3a 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -30,6 +30,7 @@ const checkboxes = [ "reduceTransparency", "disableAnimations", "disableMetadata", + "twitterGif", ]; const exceptions = { // used for mobile devices "vQuality": "720" @@ -381,6 +382,7 @@ async function download(url) { } if (sGet("disableMetadata") === "true") req.disableMetadata = true; + if (sGet("twitterGif") === "true") req.twitterGif = true; let j = await fetch(`${apiURL}/api/json`, { method: "POST", diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 70161ef0..71f55117 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -158,6 +158,8 @@ "UrgentTwitterPatch": "fixes and easier downloads", "StatusPage": "service status page", "TroubleshootingGuide": "self-troubleshooting guide", - "UpdateNewYears": "new years clean up" + "UpdateNewYears": "new years clean up", + "SettingsTwitterGif": "convert gifs to .gif", + "SettingsTwitterGifDescription": ".gif is lossy and extremely inefficient. file sizes may be larger than expected. use only when necessary." } } diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index c3722f0d..affc4fbb 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -327,6 +327,16 @@ export default function(obj) { padding: "no-margin" }]) }) + + settingsCategory({ + name: "twitter", + title: "twitter", + body: checkbox([{ + action: "twitterGif", + name: t("SettingsTwitterGif"), + padding: "no-margin" + }]) + + explanation(t('SettingsTwitterGifDescription')) + }) + settingsCategory({ name: "codec", title: t('SettingsCodecSubtitle'), diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 12c0bc10..267fc258 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -37,7 +37,8 @@ export default async function(host, patternMatch, url, lang, obj) { case "twitter": r = await twitter({ id: patternMatch.id, - index: patternMatch.index - 1 + index: patternMatch.index - 1, + toGif: obj.twitterGif }); break; case "vk": @@ -166,7 +167,11 @@ export default async function(host, patternMatch, url, lang, obj) { : loc(lang, r.error) }) - return matchActionDecider(r, host, obj.aFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, obj.filenamePattern) + return matchActionDecider( + r, host, obj.aFormat, isAudioOnly, + lang, isAudioMuted, disableMetadata, + obj.filenamePattern, obj.twitterGif + ) } catch (e) { return apiJSON(0, { t: genericError(lang, host) }) } diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index a7b8740b..f015ea13 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -3,7 +3,7 @@ import { apiJSON } from "../sub/utils.js"; import loc from "../../localization/manager.js"; import createFilename from "./createFilename.js"; -export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern) { +export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern, toGif) { let action, responseType = 2, defaultParams = { @@ -14,13 +14,14 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di fileMetadata: !disableMetadata ? r.fileMetadata : false }, params = {}, - audioFormat = String(userFormat) + audioFormat = String(userFormat); if (r.isPhoto) action = "photo"; else if (r.picker) action = "picker" else if (isAudioMuted) action = "muteVideo"; else if (isAudioOnly) action = "audio"; else if (r.isM3U8) action = "singleM3U8"; + else if (r.isGif && toGif) action = "gif"; else action = "video"; if (action === "picker" || action === "audio") { @@ -39,6 +40,10 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di case "photo": responseType = 1; break; + + case "gif": + params = { type: "gif" } + break; case "singleM3U8": params = { type: "remux" } diff --git a/src/modules/processing/services/twitter.js b/src/modules/processing/services/twitter.js index 31b26b48..ee6a9f12 100644 --- a/src/modules/processing/services/twitter.js +++ b/src/modules/processing/services/twitter.js @@ -72,7 +72,7 @@ const requestTweet = (tweetId, token) => { }) } -export default async function({ id, index }) { +export default async function({ id, index, toGif }) { let guestToken = await getGuestToken(); if (!guestToken) return { error: 'ErrorCouldntFetch' }; @@ -110,7 +110,8 @@ export default async function({ id, index }) { type: needsFixing(media[0]) ? "remux" : "normal", urls: bestQuality(media[0].video_info.variants), filename: `twitter_${id}.mp4`, - audioFilename: `twitter_${id}_audio` + audioFilename: `twitter_${id}_audio`, + isGif: media[0].type === "animated_gif" }; default: const picker = media.map((video, i) => { @@ -120,7 +121,9 @@ export default async function({ id, index }) { service: 'twitter', type: 'remux', u: url, - filename: `twitter_${id}_${i + 1}.mp4` + filename: `twitter_${id}_${i + 1}.mp4`, + isGif: media[0].type === "animated_gif", + toGif: toGif ?? false }) } return { diff --git a/src/modules/stream/stream.js b/src/modules/stream/stream.js index fb4f7ed6..f254dacc 100644 --- a/src/modules/stream/stream.js +++ b/src/modules/stream/stream.js @@ -1,4 +1,4 @@ -import { streamAudioOnly, streamDefault, streamLiveRender, streamVideoOnly } from "./types.js"; +import { streamAudioOnly, streamDefault, streamLiveRender, streamVideoOnly, convertToGif } from "./types.js"; export default async function(res, streamInfo) { try { @@ -10,6 +10,9 @@ export default async function(res, streamInfo) { case "render": await streamLiveRender(streamInfo, res); break; + case "gif": + convertToGif(streamInfo, res); + break; case "remux": case "mute": streamVideoOnly(streamInfo, res); diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index c2bd2910..88ebc61e 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -212,3 +212,40 @@ export function streamVideoOnly(streamInfo, res) { shutdown(); } } + +export function convertToGif(streamInfo, res) { + let process; + const shutdown = () => (killProcess(process), closeResponse(res)); + + try { + let args = [ + '-loglevel', '-8' + ] + if (streamInfo.service === "twitter") { + args.push('-seekable', '0') + } + args.push('-i', streamInfo.urls) + args = args.concat(ffmpegArgs["gif"]); + args.push('-f', "gif", 'pipe:3'); + + process = spawn(ffmpeg, args, { + windowsHide: true, + stdio: [ + 'inherit', 'inherit', 'inherit', + 'pipe' + ], + }); + + const [,,, muxOutput] = process.stdio; + + res.setHeader('Connection', 'keep-alive'); + res.setHeader('Content-Disposition', contentDisposition(streamInfo.filename.split('.')[0] + ".gif")); + + pipe(muxOutput, res, shutdown); + + process.on('close', shutdown); + res.on('finish', shutdown); + } catch { + shutdown(); + } +} diff --git a/src/modules/sub/utils.js b/src/modules/sub/utils.js index 28d37c6c..8b5a2c84 100644 --- a/src/modules/sub/utils.js +++ b/src/modules/sub/utils.js @@ -8,7 +8,7 @@ const apiVar = { aFormat: ["best", "mp3", "ogg", "wav", "opus"], filenamePattern: ["classic", "pretty", "basic", "nerdy"] }, - booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash", "disableMetadata"] + booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash", "disableMetadata", "twitterGif"] } const forbiddenChars = ['}', '{', '(', ')', '\\', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '==']; const forbiddenCharsString = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '=']; @@ -84,7 +84,8 @@ export function checkJSONPost(obj) { isAudioMuted: false, disableMetadata: false, dubLang: false, - vimeoDash: false + vimeoDash: false, + twitterGif: false } try { let objKeys = Object.keys(obj); From 5b1e9f1fa63e09d221cfbd82a21a4ca9a0433435 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 13:02:52 +0600 Subject: [PATCH 2/9] add support for ok videos & clean up --- src/modules/processing/match.js | 43 ++++++++------ src/modules/processing/services/ok.js | 56 +++++++++++++++++++ src/modules/processing/services/vk.js | 6 +- src/modules/processing/servicesConfig.json | 8 ++- .../processing/servicesPatternTesters.js | 3 + 5 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 src/modules/processing/services/ok.js diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 267fc258..75ce5b78 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -13,6 +13,7 @@ import reddit from "./services/reddit.js"; import twitter from "./services/twitter.js"; import youtube from "./services/youtube.js"; import vk from "./services/vk.js"; +import ok from "./services/ok.js"; import tiktok from "./services/tiktok.js"; import tumblr from "./services/tumblr.js"; import vimeo from "./services/vimeo.js"; @@ -43,19 +44,25 @@ export default async function(host, patternMatch, url, lang, obj) { break; case "vk": r = await vk({ - userId: patternMatch["userId"], - videoId: patternMatch["videoId"], + userId: patternMatch.userId, + videoId: patternMatch.videoId, + quality: obj.vQuality + }); + break; + case "ok": + r = await ok({ + id: patternMatch.id, quality: obj.vQuality }); break; case "bilibili": r = await bilibili({ - id: patternMatch["id"].slice(0, 12) + id: patternMatch.id.slice(0, 12) }); break; case "youtube": let fetchInfo = { - id: patternMatch["id"].slice(0, 11), + id: patternMatch.id.slice(0, 11), quality: obj.vQuality, format: obj.vCodec, isAudioOnly: isAudioOnly, @@ -73,16 +80,16 @@ export default async function(host, patternMatch, url, lang, obj) { break; case "reddit": r = await reddit({ - sub: patternMatch["sub"], - id: patternMatch["id"] + sub: patternMatch.sub, + id: patternMatch.id }); break; case "douyin": case "tiktok": r = await tiktok({ host: host, - postId: patternMatch["postId"], - id: patternMatch["id"], + postId: patternMatch.postId, + id: patternMatch.id, noWatermark: obj.isNoTTWatermark, fullAudio: obj.isTTFullAudio, isAudioOnly: isAudioOnly @@ -97,7 +104,7 @@ export default async function(host, patternMatch, url, lang, obj) { break; case "vimeo": r = await vimeo({ - id: patternMatch["id"].slice(0, 11), + id: patternMatch.id.slice(0, 11), quality: obj.vQuality, isAudioOnly: isAudioOnly, forceDash: isAudioOnly ? true : obj.vimeoDash @@ -107,10 +114,10 @@ export default async function(host, patternMatch, url, lang, obj) { isAudioOnly = true; r = await soundcloud({ url, - author: patternMatch["author"], - song: patternMatch["song"], - shortLink: patternMatch["shortLink"] || false, - accessKey: patternMatch["accessKey"] || false + author: patternMatch.author, + song: patternMatch.song, + shortLink: patternMatch.shortLink || false, + accessKey: patternMatch.accessKey || false }); break; case "instagram": @@ -121,31 +128,31 @@ export default async function(host, patternMatch, url, lang, obj) { break; case "vine": r = await vine({ - id: patternMatch["id"] + id: patternMatch.id }); break; case "pinterest": r = await pinterest({ - id: patternMatch["id"] + id: patternMatch.id }); break; case "streamable": r = await streamable({ - id: patternMatch["id"], + id: patternMatch.id, quality: obj.vQuality, isAudioOnly: isAudioOnly, }); break; case "twitch": r = await twitch({ - clipId: patternMatch["clip"] || false, + clipId: patternMatch.clip || false, quality: obj.vQuality, isAudioOnly: obj.isAudioOnly }); break; case "rutube": r = await rutube({ - id: patternMatch["id"], + id: patternMatch.id, quality: obj.vQuality, isAudioOnly: isAudioOnly }); diff --git a/src/modules/processing/services/ok.js b/src/modules/processing/services/ok.js new file mode 100644 index 00000000..bde163f6 --- /dev/null +++ b/src/modules/processing/services/ok.js @@ -0,0 +1,56 @@ +import { genericUserAgent, maxVideoDuration } from "../../config.js"; +import { cleanString } from "../../sub/utils.js"; + +const resolutions = { + "ultra": "2160", + "quad": "1440", + "full": "1080", + "hd": "720", + "sd": "480", + "low": "360", + "lowest": "240", + "mobile": "144" +} + +export default async function(o) { + let quality = o.quality === "max" ? "2160" : o.quality; + + let html = await fetch(`https://ok.ru/video/${o.id}`, { + headers: { "user-agent": genericUserAgent } + }).then((r) => { return r.text() }).catch(() => { return false }); + + if (!html) return { error: 'ErrorCouldntFetch' }; + if (!html.includes(`
maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; + + let videos = videoData.videos.filter(v => !v.disallowed); + let bestVideo = videos.find(v => resolutions[v.name] === quality) || videos[videos.length - 1]; + + let fileMetadata = { + title: cleanString(videoData.movie.title.trim()), + author: cleanString(videoData.author.name.trim()), + } + + if (bestVideo) return { + urls: bestVideo.url, + filenameAttributes: { + service: "ok", + id: o.id, + title: fileMetadata.title, + author: fileMetadata.author, + resolution: `${resolutions[bestVideo.name]}p`, + qualityLabel: `${resolutions[bestVideo.name]}p`, + extension: "mp4" + } + } + + return { error: 'ErrorEmptyDownload' } +} diff --git a/src/modules/processing/services/vk.js b/src/modules/processing/services/vk.js index 30d05f29..d956c3b9 100644 --- a/src/modules/processing/services/vk.js +++ b/src/modules/processing/services/vk.js @@ -12,7 +12,7 @@ export default async function(o) { if (!html) return { error: 'ErrorCouldntFetch' }; - // decode cyrillic from windows-1251 because vk still uses apis from prehistoring times + // decode cyrillic from windows-1251 because vk still uses apis from prehistoric times let decoder = new TextDecoder('windows-1251'); html = decoder.decode(html); @@ -35,7 +35,7 @@ export default async function(o) { let fileMetadata = { title: cleanString(js.player.params[0].md_title.trim()), - artist: cleanString(js.player.params[0].md_author.trim()), + author: cleanString(js.player.params[0].md_author.trim()), } if (url) return { @@ -44,7 +44,7 @@ export default async function(o) { service: "vk", id: `${o.userId}_${o.videoId}`, title: fileMetadata.title, - author: fileMetadata.artist, + author: fileMetadata.author, resolution: `${quality}p`, qualityLabel: `${quality}p`, extension: "mp4" diff --git a/src/modules/processing/servicesConfig.json b/src/modules/processing/servicesConfig.json index 6ecd2745..a4bbf493 100644 --- a/src/modules/processing/servicesConfig.json +++ b/src/modules/processing/servicesConfig.json @@ -1,5 +1,5 @@ { - "audioIgnore": ["vk"], + "audioIgnore": ["vk", "ok"], "config": { "bilibili": { "alias": "bilibili.com videos", @@ -28,6 +28,12 @@ "patterns": ["video:userId_:videoId", "clip:userId_:videoId", "clips:duplicate?z=clip:userId_:videoId"], "enabled": true }, + "ok": { + "alias": "ok video", + "tld": "ru", + "patterns": ["video/:id"], + "enabled": true + }, "youtube": { "alias": "youtube videos, shorts & music", "patterns": ["watch?v=:id", "embed/:id", "watch/:id"], diff --git a/src/modules/processing/servicesPatternTesters.js b/src/modules/processing/servicesPatternTesters.js index e1f3d01c..6bfd1d48 100644 --- a/src/modules/processing/servicesPatternTesters.js +++ b/src/modules/processing/servicesPatternTesters.js @@ -5,6 +5,9 @@ export const testers = { "instagram": (patternMatch) => patternMatch.postId?.length <= 12 || (patternMatch.username?.length <= 30 && patternMatch.storyId?.length <= 24), + + "ok": (patternMatch) => + patternMatch.id?.length <= 16, "pinterest": (patternMatch) => patternMatch.id?.length <= 128, From 8c868c43853649797ef03e24f6abf7640ac0fc6c Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 13:17:26 +0600 Subject: [PATCH 3/9] tests: add ok --- src/test/tests.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/tests.json b/src/test/tests.json index c224f5e4..d3170858 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -1162,5 +1162,14 @@ "code": 200, "status": "stream" } + }], + "ok": [{ + "name": "regular video", + "url": "https://ok.ru/video/7204071410346", + "params": {}, + "expected": { + "code": 200, + "status": "stream" + } }] } From 9aabb4d738ca688f50c90b6501a58bbf4053000a Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 15:05:39 +0600 Subject: [PATCH 4/9] pinterest: pin.it support & fix parsing --- src/modules/pageRender/page.js | 2 +- src/modules/processing/match.js | 3 +- src/modules/processing/services/pinterest.js | 47 +++++++++++-------- src/modules/processing/servicesConfig.json | 2 +- .../processing/servicesPatternTesters.js | 2 +- src/modules/processing/url.js | 7 +++ 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/modules/pageRender/page.js b/src/modules/pageRender/page.js index affc4fbb..80f29ac4 100644 --- a/src/modules/pageRender/page.js +++ b/src/modules/pageRender/page.js @@ -578,7 +578,7 @@ export default function(obj) {
- +
diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 75ce5b78..0d5980ca 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -133,7 +133,8 @@ export default async function(host, patternMatch, url, lang, obj) { break; case "pinterest": r = await pinterest({ - id: patternMatch.id + id: patternMatch.id, + shortLink: patternMatch.shortLink || false }); break; case "streamable": diff --git a/src/modules/processing/services/pinterest.js b/src/modules/processing/services/pinterest.js index 086573c6..156b65cd 100644 --- a/src/modules/processing/services/pinterest.js +++ b/src/modules/processing/services/pinterest.js @@ -1,29 +1,36 @@ -import { maxVideoDuration } from "../../config.js"; +import { genericUserAgent } from "../../config.js"; -export default async function(obj) { - const pinId = obj.id.split('--').reverse()[0]; - if (!(/^\d+$/.test(pinId))) return { error: 'ErrorCantGetID' }; - let data = await fetch(`https://www.pinterest.com/resource/PinResource/get?data=${encodeURIComponent(JSON.stringify({ - options: { - field_set_key: "unauth_react_main_pin", - id: pinId - } - }))}`).then((r) => { return r.json() }).catch(() => { return false }); - if (!data) return { error: 'ErrorCouldntFetch' }; +const videoLinkBase = { + "regular": "https://v1.pinimg.com/videos/mc/720p/", + "story": "https://v1.pinimg.com/videos/mc/720p/" +} - data = data["resource_response"]["data"]; +export default async function(o) { + let id = o.id, type = "regular"; - let video = null; + if (id.includes("--")) { + id = id.split("--")[1]; + type = "story"; + } + if (!o.id && o.shortLink) { + id = await fetch(`https://api.pinterest.com/url_shortener/${o.shortLink}/redirect/`, { redirect: "manual" }).then((r) => { + return r.headers.get("location").split('pin/')[1].split('/')[0] + }).catch(() => {}); + } + if (!id) return { error: 'ErrorCouldntFetch' }; - if (data.videos !== null) video = data.videos.video_list.V_720P; - else if (data.story_pin_data !== null) video = data.story_pin_data.pages[0].blocks[0].video.video_list.V_EXP7; + let html = await fetch(`https://www.pinterest.com/pin/${id}/`, { + headers: { "user-agent": genericUserAgent } + }).then((r) => { return r.text() }).catch(() => { return false }); - if (!video) return { error: 'ErrorEmptyDownload' }; - if (video.duration > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; + if (!html) return { error: 'ErrorCouldntFetch' }; + + let videoLink = html.split(`"url":"${videoLinkBase[type]}`)[1]?.split('"')[0]; + if (!html.includes(videoLink)) return { error: 'ErrorEmptyDownload' }; return { - urls: video.url, - filename: `pinterest_${pinId}.mp4`, - audioFilename: `pinterest_${pinId}_audio` + urls: `${videoLinkBase[type]}${videoLink}`, + filename: `pinterest_${o.id}.mp4`, + audioFilename: `pinterest_${o.id}_audio` } } diff --git a/src/modules/processing/servicesConfig.json b/src/modules/processing/servicesConfig.json index a4bbf493..e9021490 100644 --- a/src/modules/processing/servicesConfig.json +++ b/src/modules/processing/servicesConfig.json @@ -86,7 +86,7 @@ }, "pinterest": { "alias": "pinterest videos & stories", - "patterns": ["pin/:id"], + "patterns": ["pin/:id", "url_shortener/:shortLink"], "enabled": true }, "streamable": { diff --git a/src/modules/processing/servicesPatternTesters.js b/src/modules/processing/servicesPatternTesters.js index 6bfd1d48..970e8f40 100644 --- a/src/modules/processing/servicesPatternTesters.js +++ b/src/modules/processing/servicesPatternTesters.js @@ -10,7 +10,7 @@ export const testers = { patternMatch.id?.length <= 16, "pinterest": (patternMatch) => - patternMatch.id?.length <= 128, + patternMatch.id?.length <= 128 || patternMatch.shortLink?.length <= 32, "reddit": (patternMatch) => patternMatch.sub?.length <= 22 && patternMatch.id?.length <= 10, diff --git a/src/modules/processing/url.js b/src/modules/processing/url.js index 2f1ac87f..9c87889d 100644 --- a/src/modules/processing/url.js +++ b/src/modules/processing/url.js @@ -25,6 +25,13 @@ export function aliasURL(url) { }`) } break; + case "pin": + if (url.hostname === 'pin.it' && parts.length === 2) { + url = new URL(`https://pinterest.com/url_shortener/${ + encodeURIComponent(parts[1]) + }`) + } + break; case "vxtwitter": case "fixvx": From 18c0dbadfd1fb218924baa6441691e8b13d12e51 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 15:30:35 +0600 Subject: [PATCH 5/9] loc: update gif strings and translate them to russian --- src/localization/languages/en.json | 2 +- src/localization/languages/ru.json | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 71f55117..febadd7c 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -160,6 +160,6 @@ "TroubleshootingGuide": "self-troubleshooting guide", "UpdateNewYears": "new years clean up", "SettingsTwitterGif": "convert gifs to .gif", - "SettingsTwitterGifDescription": ".gif is lossy and extremely inefficient. file sizes may be larger than expected. use only when necessary." + "SettingsTwitterGifDescription": "converting looping videos to .gif reduces quality and majorly increases file size. if you want best efficiency, keep this setting off." } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index df3ae5a6..1013532d 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -160,6 +160,8 @@ "UrgentTwitterPatch": "фиксы и удобное скачивание", "StatusPage": "статус серверов", "TroubleshootingGuide": "гайд по устранению проблем", - "UpdateNewYears": "новогодняя уборка" + "UpdateNewYears": "новогодняя уборка", + "SettingsTwitterGif": "конвертировать гифки в .gif", + "SettingsTwitterGifDescription": "конвертирование зацикленного видео в .gif снижает качество и значительно увеличивает размер файла. если важна максимальная эффективность, то не используй эту функцию." } } From 4d850c5d64f13c4ea1059544445424c26bf6c5d7 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 15:54:52 +0600 Subject: [PATCH 6/9] vimeo: fix 1440p bug and format filtering --- src/modules/processing/services/vimeo.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index 90dc9b3d..b2a031cd 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -4,6 +4,7 @@ import { cleanString } from '../../sub/utils.js'; const resolutionMatch = { "3840": "2160", "2732": "1440", + "2560": "1440", "2048": "1080", "1920": "1080", "1366": "720", @@ -63,8 +64,9 @@ export default async function(obj) { if (!masterJSON) return { error: 'ErrorCouldntFetch' }; if (!masterJSON.video) return { error: 'ErrorEmptyDownload' }; - let masterJSON_Video = masterJSON.video.sort((a, b) => Number(b.width) - Number(a.width)).filter(a => a['format'] === "mp42"), + let masterJSON_Video = masterJSON.video.sort((a, b) => Number(b.width) - Number(a.width)).filter(a => ["dash", "mp42"].includes(a['format'])), bestVideo = masterJSON_Video[0]; + console.log(masterJSON_Video) if (Number(quality) < Number(resolutionMatch[bestVideo["width"]])) { bestVideo = masterJSON_Video.find(i => resolutionMatch[i["width"]] === quality) } From 58a0547def651dad1db49a923537faa63ac6fc08 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 15:55:45 +0600 Subject: [PATCH 7/9] vimeo: remove debugging (oops) --- src/modules/processing/services/vimeo.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/processing/services/vimeo.js b/src/modules/processing/services/vimeo.js index b2a031cd..db623bdb 100644 --- a/src/modules/processing/services/vimeo.js +++ b/src/modules/processing/services/vimeo.js @@ -66,7 +66,6 @@ export default async function(obj) { let masterJSON_Video = masterJSON.video.sort((a, b) => Number(b.width) - Number(a.width)).filter(a => ["dash", "mp42"].includes(a['format'])), bestVideo = masterJSON_Video[0]; - console.log(masterJSON_Video) if (Number(quality) < Number(resolutionMatch[bestVideo["width"]])) { bestVideo = masterJSON_Video.find(i => resolutionMatch[i["width"]] === quality) } From 4fa6608bde15055b5bf23966588b8ad727471585 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 15:57:58 +0600 Subject: [PATCH 8/9] package: bump version to 7.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a240a87..c49adf8e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "7.8.6", + "version": "7.9", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", From 64f5f360c6991a13d680c4324edd02deaf56af90 Mon Sep 17 00:00:00 2001 From: wukko Date: Wed, 17 Jan 2024 16:18:38 +0600 Subject: [PATCH 9/9] loc: remove outdated update strings --- src/localization/languages/en.json | 10 ++-------- src/localization/languages/ru.json | 9 ++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index febadd7c..b2c67263 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -117,7 +117,6 @@ "ShareURL": "share", "ErrorTweetUnavailable": "couldn't find anything about this tweet. this could be because its visibility is limited. try another one!", "ErrorTwitterRIP": "twitter has restricted access to any content to unauthenticated users. while there's a way to get regular tweets, spaces are, unfortunately, impossible to get at this time. i am looking into possible solutions.", - "UrgentDonate": "cobalt needs your help!", "PopupCloseDone": "done", "Accessibility": "accessibility", "SettingsReduceTransparency": "reduce transparency", @@ -134,10 +133,7 @@ "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 zero liability. processing servers work like limited proxies, 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.", - "UrgentFeatureUpdate71": "more supported services!", - "UrgentThanks": "thank you for support!", "SettingsDisableMetadata": "don't add metadata", - "UrgentNewDomain": "new domain, same cobalt", "NewDomainWelcomeTitle": "hey there!", "NewDomainWelcome": "cobalt is moving! same features, same owner, simply a more rememberable domain. and still no ads.\n\ncobalt.tools 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 :)", @@ -154,12 +150,10 @@ "FilenamePreviewVideoTitle": "Video Title", "FilenamePreviewAudioTitle": "Audio Title", "FilenamePreviewAudioAuthor": "Audio Author", - "UrgentFilenameUpdate": "customizable file names!", - "UrgentTwitterPatch": "fixes and easier downloads", "StatusPage": "service status page", "TroubleshootingGuide": "self-troubleshooting guide", - "UpdateNewYears": "new years clean up", "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." + "SettingsTwitterGifDescription": "converting looping videos to .gif reduces quality and majorly increases file size. if you want best efficiency, keep this setting off.", + "UpdateTwitterGif": "twitter gifs and pinterest" } } diff --git a/src/localization/languages/ru.json b/src/localization/languages/ru.json index 1013532d..168a57c8 100644 --- a/src/localization/languages/ru.json +++ b/src/localization/languages/ru.json @@ -118,7 +118,6 @@ "ShareURL": "поделиться", "ErrorTweetUnavailable": "не смог найти что-либо об этом твите. возможно его видимость была ограничена. попробуй другой!", "ErrorTwitterRIP": "твиттер ограничил доступ к любому контенту на сайте для пользователей без аккаунтов. я нашёл лазейку, чтобы доставать обычные твиты, а для spaces, к сожалению, нет. я ищу возможные варианты выхода из ситуации.", - "UrgentDonate": "нужна твоя помощь!", "PopupCloseDone": "готово", "Accessibility": "общедоступность", "SettingsReduceTransparency": "уменьшить прозрачность", @@ -135,10 +134,7 @@ "KeyboardShortcutClosePopup": "закрыть все окна", "CollapseLegal": "принципы и этика", "FairUse": "кобальт - это веб инструмент для облегчения скачивания контента из интернета. сервера обработки работают как ограниченные прокси, так что ничего никогда не сохраняется или кэшируется.\n\nкобальт не несёт никакой ответственности, только ты (конечный пользователь) несёшь ответственность за то, что скачиваешь, как используешь и распространяешь скачанный контент. будь сознателен при использовании чужого контента и всегда указывай авторов!\n\nприкладывай ссылку на источник при использовании в образовательных целях (лекции, домашние задания и т.п.)\n\nчестное использование и указание авторства выгодно всем.", - "UrgentFeatureUpdate71": "расширение поддержки сервисов!", - "UrgentThanks": "спасибо за поддержку!", "SettingsDisableMetadata": "не добавлять метаданные", - "UrgentNewDomain": "новый домен, тот же кобальт", "NewDomainWelcomeTitle": "привет!", "NewDomainWelcome": "кобальт переезжает! те же функции, тот же владелец, просто более запоминающийся домен. по-прежнему без рекламы.\n\ncobalt.tools - новый основной домен, т.е. где ты сейчас находишься. не забудь обновить закладки и переустановить веб-приложение!", "DataTransferSuccess": "кстати, твои настройки были перенесены автоматически :)", @@ -156,12 +152,11 @@ "FilenamePreviewVideoTitle": "Название Видео", "FilenamePreviewAudioTitle": "Название Аудио", "FilenamePreviewAudioAuthor": "Автор Аудио", - "UrgentFilenameUpdate": "изменяемые названия файлов!", - "UrgentTwitterPatch": "фиксы и удобное скачивание", "StatusPage": "статус серверов", "TroubleshootingGuide": "гайд по устранению проблем", "UpdateNewYears": "новогодняя уборка", "SettingsTwitterGif": "конвертировать гифки в .gif", - "SettingsTwitterGifDescription": "конвертирование зацикленного видео в .gif снижает качество и значительно увеличивает размер файла. если важна максимальная эффективность, то не используй эту функцию." + "SettingsTwitterGifDescription": "конвертирование зацикленного видео в .gif снижает качество и значительно увеличивает размер файла. если важна максимальная эффективность, то не используй эту функцию.", + "UpdateTwitterGif": "гифки с твиттера и одноклассники" } }