api: update error codes in services, add more error codes where needed

This commit is contained in:
wukko 2024-08-20 21:10:37 +06:00
parent c698d272a1
commit 05abf9ad3e
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
21 changed files with 263 additions and 141 deletions

View file

@ -241,14 +241,14 @@ export default async function(host, patternMatch, obj) {
if (r.error && r.critical) { if (r.error && r.critical) {
return createResponse("critical", { return createResponse("critical", {
code: r.error code: `error.api.${r.error}`,
}) })
} }
if (r.error) { if (r.error) {
return createResponse("error", { return createResponse("error", {
code: r.error, code: `error.api.${r.error}`,
context: response?.context context: r?.context
}) })
} }

View file

@ -30,22 +30,34 @@ function extractBestQuality(dashData) {
async function com_download(id) { async function com_download(id) {
let html = await fetch(`https://bilibili.com/video/${id}`, { let html = await fetch(`https://bilibili.com/video/${id}`, {
headers: { "user-agent": genericUserAgent } headers: {
}).then(r => r.text()).catch(() => {}); "user-agent": genericUserAgent
if (!html) return { error: 'ErrorCouldntFetch' }; }
})
.then(r => r.text())
.catch(() => {});
if (!html) {
return { error: "fetch.fail" }
}
if (!(html.includes('<script>window.__playinfo__=') && html.includes('"video_codecid"'))) { if (!(html.includes('<script>window.__playinfo__=') && html.includes('"video_codecid"'))) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
let streamData = JSON.parse(html.split('<script>window.__playinfo__=')[1].split('</script>')[0]); let streamData = JSON.parse(html.split('<script>window.__playinfo__=')[1].split('</script>')[0]);
if (streamData.data.timelength > env.durationLimit * 1000) { if (streamData.data.timelength > env.durationLimit * 1000) {
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
} }
const [ video, audio ] = extractBestQuality(streamData.data.dash); const [ video, audio ] = extractBestQuality(streamData.data.dash);
if (!video || !audio) { if (!video || !audio) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
return { return {
@ -66,7 +78,7 @@ async function tv_download(id) {
const { data } = await fetch(url).then(a => a.json()); const { data } = await fetch(url).then(a => a.json());
if (!data?.playurl?.video) { if (!data?.playurl?.video) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
const [ video, audio ] = extractBestQuality({ const [ video, audio ] = extractBestQuality({
@ -76,11 +88,16 @@ async function tv_download(id) {
}); });
if (!video || !audio) { if (!video || !audio) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
if (video.duration > env.durationLimit * 1000) { if (video.duration > env.durationLimit * 1000) {
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
} }
return { return {
@ -101,5 +118,5 @@ export default async function({ comId, tvId, comShortLink }) {
return tv_download(tvId); return tv_download(tvId);
} }
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.fail" };
} }

View file

@ -1,5 +1,5 @@
import HLSParser from 'hls-parser'; import HLSParser from "hls-parser";
import { env } from '../../config.js'; import { env } from "../../config.js";
let _token; let _token;
@ -31,7 +31,7 @@ const getToken = async () => {
export default async function({ id }) { export default async function({ id }) {
const token = await getToken(); const token = await getToken();
if (!token) return { error: 'ErrorSomethingWentWrong' }; if (!token) return { error: "fetch.fail" };
const req = await fetch('https://graphql.api.dailymotion.com/', const req = await fetch('https://graphql.api.dailymotion.com/',
{ {
@ -70,20 +70,25 @@ export default async function({ id }) {
const media = req?.data?.media; const media = req?.data?.media;
if (media?.__typename !== 'Video' || !media.hlsURL) { if (media?.__typename !== 'Video' || !media.hlsURL) {
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }
if (media.duration > env.durationLimit) { if (media.duration > env.durationLimit) {
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
} }
const manifest = await fetch(media.hlsURL).then(r => r.text()).catch(() => {}); const manifest = await fetch(media.hlsURL).then(r => r.text()).catch(() => {});
if (!manifest) return { error: 'ErrorSomethingWentWrong' }; if (!manifest) return { error: "fetch.fail" };
const bestQuality = HLSParser.parse(manifest).variants const bestQuality = HLSParser.parse(manifest).variants
.filter(v => v.codecs.includes('avc1')) .filter(v => v.codecs.includes('avc1'))
.reduce((a, b) => a.bandwidth > b.bandwidth ? a : b); .reduce((a, b) => a.bandwidth > b.bandwidth ? a : b);
if (!bestQuality) return { error: 'ErrorEmptyDownload' } if (!bestQuality) return { error: "fetch.empty" }
const fileMetadata = { const fileMetadata = {
title: media.title, title: media.title,

View file

@ -33,7 +33,8 @@ export default async function({ id, shareType, shortLink }) {
.then(r => r.text()) .then(r => r.text())
.catch(() => false); .catch(() => false);
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html && shortLink) return { error: "fetch.short_link" }
if (!html) return { error: "fetch.fail" };
const urls = []; const urls = [];
const hd = html.match('"browser_native_hd_url":(".*?")'); const hd = html.match('"browser_native_hd_url":(".*?")');
@ -43,7 +44,7 @@ export default async function({ id, shareType, shortLink }) {
if (sd?.[1]) urls.push(JSON.parse(sd[1])); if (sd?.[1]) urls.push(JSON.parse(sd[1]));
if (!urls.length) { if (!urls.length) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
const baseFilename = `facebook_${id || shortLink}`; const baseFilename = `facebook_${id || shortLink}`;

View file

@ -1,5 +1,5 @@
import { createStream } from "../../stream/manage.js";
import { genericUserAgent } from "../../config.js"; import { genericUserAgent } from "../../config.js";
import { createStream } from "../../stream/manage.js";
import { getCookie, updateCookie } from "../cookie/manager.js"; import { getCookie, updateCookie } from "../cookie/manager.js";
const commonHeaders = { const commonHeaders = {
@ -206,7 +206,7 @@ export default function(obj) {
.map(e => { .map(e => {
const type = e.video_versions ? "video" : "photo"; const type = e.video_versions ? "video" : "photo";
const imageUrl = e.image_versions2.candidates[0].url; const imageUrl = e.image_versions2.candidates[0].url;
let url = imageUrl; let url = imageUrl;
if (type === 'video') { if (type === 'video') {
const video = e.video_versions.reduce((a, b) => a.width * a.height < b.width * b.height ? b : a); const video = e.video_versions.reduce((a, b) => a.width * a.height < b.width * b.height ? b : a);
@ -246,7 +246,7 @@ export default function(obj) {
let data, result; let data, result;
try { try {
const cookie = getCookie('instagram'); const cookie = getCookie('instagram');
const bearer = getCookie('instagram_bearer'); const bearer = getCookie('instagram_bearer');
const token = bearer?.values()?.token; const token = bearer?.values()?.token;
@ -271,7 +271,7 @@ export default function(obj) {
if (!data && cookie) data = await requestGQL(id, cookie); if (!data && cookie) data = await requestGQL(id, cookie);
} catch {} } catch {}
if (!data) return { error: 'ErrorCouldntFetch' }; if (!data) return { error: "fetch.fail" };
if (data?.gql_data) { if (data?.gql_data) {
result = extractOldPost(data, id) result = extractOldPost(data, id)
@ -280,7 +280,7 @@ export default function(obj) {
} }
if (result) return result; if (result) return result;
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }
async function usernameToId(username, cookie) { async function usernameToId(username, cookie) {
@ -295,11 +295,16 @@ export default function(obj) {
async function getStory(username, id) { async function getStory(username, id) {
const cookie = getCookie('instagram'); const cookie = getCookie('instagram');
if (!cookie) return { error: 'ErrorUnsupported' }; if (!cookie) return {
error: "link.unsupported",
context: {
service: "instagram"
}
}
const userId = await usernameToId(username, cookie); const userId = await usernameToId(username, cookie);
if (!userId) return { error: 'ErrorEmptyDownload' }; if (!userId) return { error: "fetch.empty" };
const dtsgId = await findDtsgId(cookie); const dtsgId = await findDtsgId(cookie);
const url = new URL('https://www.instagram.com/api/graphql/'); const url = new URL('https://www.instagram.com/api/graphql/');
@ -320,8 +325,8 @@ export default function(obj) {
} catch {} } catch {}
const item = media.items.find(m => m.pk === id); const item = media.items.find(m => m.pk === id);
if (!item) return { error: 'ErrorEmptyDownload' }; if (!item) return { error: "fetch.empty" };
if (item.video_versions) { if (item.video_versions) {
const video = item.video_versions.reduce((a, b) => a.width * a.height < b.width * b.height ? b : a) const video = item.video_versions.reduce((a, b) => a.width * a.height < b.width * b.height ? b : a)
return { return {
@ -338,12 +343,17 @@ export default function(obj) {
} }
} }
return { error: 'ErrorUnsupported' }; return {
error: "link.unsupported",
context: {
service: "instagram"
}
}
} }
const { postId, storyId, username } = obj; const { postId, storyId, username } = obj;
if (postId) return getPost(postId); if (postId) return getPost(postId);
if (username && storyId) return getStory(username, storyId); if (username && storyId) return getStory(username, storyId);
return { error: 'ErrorUnsupported' } return { error: "fetch.fail" }
} }

View file

@ -23,7 +23,7 @@ export default async function({ id }) {
.then(r => r.status === 200 ? r.json() : false) .then(r => r.status === 200 ? r.json() : false)
.catch(() => {}); .catch(() => {});
if (!gql) return { error: 'ErrorEmptyDownload' }; if (!gql) return { error: "fetch.empty" };
const videoUrl = gql?.url; const videoUrl = gql?.url;
@ -35,5 +35,5 @@ export default async function({ id }) {
} }
} }
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }

View file

@ -19,26 +19,31 @@ export default async function(o) {
headers: { "user-agent": genericUserAgent } headers: { "user-agent": genericUserAgent }
}).then(r => r.text()).catch(() => {}); }).then(r => r.text()).catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html) return { error: "fetch.fail" };
let videoData = html.match(/<div data-module="OKVideo" .*? data-options="({.*?})"( .*?)>/) let videoData = html.match(/<div data-module="OKVideo" .*? data-options="({.*?})"( .*?)>/)
?.[1] ?.[1]
?.replaceAll("&quot;", '"'); ?.replaceAll("&quot;", '"');
if (!videoData) { if (!videoData) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
videoData = JSON.parse(JSON.parse(videoData).flashvars.metadata); videoData = JSON.parse(JSON.parse(videoData).flashvars.metadata);
if (videoData.provider !== "UPLOADED_ODKL") if (videoData.provider !== "UPLOADED_ODKL")
return { error: 'ErrorUnsupported' }; return { error: "link.unsupported" };
if (videoData.movie.is_live) if (videoData.movie.is_live)
return { error: 'ErrorLiveVideo' }; return { error: "content.video.live" };
if (videoData.movie.duration > env.durationLimit) if (videoData.movie.duration > env.durationLimit)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
let videos = videoData.videos.filter(v => !v.disallowed); let videos = videoData.videos.filter(v => !v.disallowed);
let bestVideo = videos.find(v => resolutions[v.name] === quality) || videos[videos.length - 1]; let bestVideo = videos.find(v => resolutions[v.name] === quality) || videos[videos.length - 1];
@ -61,5 +66,5 @@ export default async function(o) {
} }
} }
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }

View file

@ -12,13 +12,13 @@ export default async function(o) {
.catch(() => {}); .catch(() => {});
} }
if (id.includes("--")) id = id.split("--")[1]; if (id.includes("--")) id = id.split("--")[1];
if (!id) return { error: 'ErrorCouldntFetch' }; if (!id) return { error: "fetch.fail" };
let html = await fetch(`https://www.pinterest.com/pin/${id}/`, { let html = await fetch(`https://www.pinterest.com/pin/${id}/`, {
headers: { "user-agent": genericUserAgent } headers: { "user-agent": genericUserAgent }
}).then(r => r.text()).catch(() => {}); }).then(r => r.text()).catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html) return { error: "fetch.fail" };
let videoLink = [...html.matchAll(videoRegex)] let videoLink = [...html.matchAll(videoRegex)]
.map(([, link]) => link) .map(([, link]) => link)
@ -39,5 +39,5 @@ export default async function(o) {
isPhoto: true isPhoto: true
} }
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }

View file

@ -9,7 +9,7 @@ async function getAccessToken() {
* you can get these by making a reddit app and * you can get these by making a reddit app and
* authenticating an account against reddit's oauth2 api * authenticating an account against reddit's oauth2 api
* see: https://github.com/reddit-archive/reddit/wiki/OAuth2 * see: https://github.com/reddit-archive/reddit/wiki/OAuth2
* *
* any additional cookie fields are managed by this code and you * any additional cookie fields are managed by this code and you
* should not touch them unless you know what you're doing. **/ * should not touch them unless you know what you're doing. **/
const cookie = await getCookie('reddit'); const cookie = await getCookie('reddit');
@ -67,7 +67,9 @@ export default async function(obj) {
} }
).then(r => r.json()).catch(() => {}); ).then(r => r.json()).catch(() => {});
if (!data || !Array.isArray(data)) return { error: 'ErrorCouldntFetch' }; if (!data || !Array.isArray(data)) {
return { error: "fetch.fail" }
}
data = data[0]?.data?.children[0]?.data; data = data[0]?.data?.children[0]?.data;
@ -77,10 +79,15 @@ export default async function(obj) {
} }
if (!data.secure_media?.reddit_video) if (!data.secure_media?.reddit_video)
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
if (data.secure_media?.reddit_video?.duration > env.durationLimit) if (data.secure_media?.reddit_video?.duration > env.durationLimit)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
let audio = false, let audio = false,
video = data.secure_media?.reddit_video?.fallback_url?.split('?')[0], video = data.secure_media?.reddit_video?.fallback_url?.split('?')[0],

View file

@ -1,7 +1,7 @@
import HLS from 'hls-parser'; import HLS from "hls-parser";
import { env } from "../../config.js"; import { env } from "../../config.js";
import { cleanString } from '../../misc/utils.js'; import { cleanString } from "../../misc/utils.js";
async function requestJSON(url) { async function requestJSON(url) {
try { try {
@ -18,7 +18,7 @@ export default async function(obj) {
`https://rutube.ru/pangolin/api/web/yappy/yappypage/?client=wdp&videoId=${obj.yappyId}&page=1&page_size=15` `https://rutube.ru/pangolin/api/web/yappy/yappypage/?client=wdp&videoId=${obj.yappyId}&page=1&page_size=15`
) )
const yappyURL = yappy?.results?.find(r => r.id === obj.yappyId)?.link; const yappyURL = yappy?.results?.find(r => r.id === obj.yappyId)?.link;
if (!yappyURL) return { error: 'ErrorEmptyDownload' }; if (!yappyURL) return { error: "fetch.empty" };
return { return {
urls: yappyURL, urls: yappyURL,
@ -33,19 +33,24 @@ export default async function(obj) {
if (obj.key) requestURL.searchParams.set('p', obj.key); if (obj.key) requestURL.searchParams.set('p', obj.key);
const play = await requestJSON(requestURL); const play = await requestJSON(requestURL);
if (!play) return { error: 'ErrorCouldntFetch' }; if (!play) return { error: "fetch.fail" };
if (play.detail || !play.video_balancer) return { error: 'ErrorEmptyDownload' }; if (play.detail || !play.video_balancer) return { error: "fetch.empty" };
if (play.live_streams?.hls) return { error: 'ErrorLiveVideo' }; if (play.live_streams?.hls) return { error: "content.video.live" };
if (play.duration > env.durationLimit * 1000) if (play.duration > env.durationLimit * 1000)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
let m3u8 = await fetch(play.video_balancer.m3u8) let m3u8 = await fetch(play.video_balancer.m3u8)
.then(r => r.text()) .then(r => r.text())
.catch(() => {}); .catch(() => {});
if (!m3u8) return { error: 'ErrorCouldntFetch' }; if (!m3u8) return { error: "fetch.fail" };
m3u8 = HLS.parse(m3u8).variants; m3u8 = HLS.parse(m3u8).variants;

View file

@ -1,16 +1,16 @@
import { extract, normalizeURL } from "../url.js";
import { genericUserAgent } from "../../config.js"; import { genericUserAgent } from "../../config.js";
import { getRedirectingURL } from "../../misc/utils.js"; import { getRedirectingURL } from "../../misc/utils.js";
import { extract, normalizeURL } from "../url.js";
const SPOTLIGHT_VIDEO_REGEX = /<link data-react-helmet="true" rel="preload" href="(https:\/\/cf-st\.sc-cdn\.net\/d\/[\w.?=]+&amp;uc=\d+)" as="video"\/>/; const SPOTLIGHT_VIDEO_REGEX = /<link data-react-helmet="true" rel="preload" href="(https:\/\/cf-st\.sc-cdn\.net\/d\/[\w.?=]+&amp;uc=\d+)" as="video"\/>/;
const NEXT_DATA_REGEX = /<script id="__NEXT_DATA__" type="application\/json">({.+})<\/script><\/body><\/html>$/; const NEXT_DATA_REGEX = /<script id="__NEXT_DATA__" type="application\/json">({.+})<\/script><\/body><\/html>$/;
async function getSpotlight(id) { async function getSpotlight(id) {
const html = await fetch(`https://www.snapchat.com/spotlight/${id}`, { const html = await fetch(`https://www.snapchat.com/spotlight/${id}`, {
headers: { 'User-Agent': genericUserAgent } headers: { 'user-agent': genericUserAgent }
}).then((r) => r.text()).catch(() => null); }).then((r) => r.text()).catch(() => null);
if (!html) { if (!html) {
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.fail" };
} }
const videoURL = html.match(SPOTLIGHT_VIDEO_REGEX)?.[1]; const videoURL = html.match(SPOTLIGHT_VIDEO_REGEX)?.[1];
@ -24,11 +24,15 @@ async function getSpotlight(id) {
} }
async function getStory(username, storyId) { async function getStory(username, storyId) {
const html = await fetch(`https://www.snapchat.com/add/${username}${storyId ? `/${storyId}` : ''}`, { const html = await fetch(
headers: { 'User-Agent': genericUserAgent } `https://www.snapchat.com/add/${username}${storyId ? `/${storyId}` : ''}`,
}).then((r) => r.text()).catch(() => null); { headers: { 'user-agent': genericUserAgent } }
)
.then((r) => r.text())
.catch(() => null);
if (!html) { if (!html) {
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.fail" };
} }
const nextDataString = html.match(NEXT_DATA_REGEX)?.[1]; const nextDataString = html.match(NEXT_DATA_REGEX)?.[1];
@ -73,12 +77,12 @@ export default async function (obj) {
const link = await getRedirectingURL(`https://t.snapchat.com/${obj.shortLink}`); const link = await getRedirectingURL(`https://t.snapchat.com/${obj.shortLink}`);
if (!link?.startsWith('https://www.snapchat.com/')) { if (!link?.startsWith('https://www.snapchat.com/')) {
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.short_link" };
} }
const extractResult = extract(normalizeURL(link)); const extractResult = extract(normalizeURL(link));
if (extractResult?.host !== 'snapchat') { if (extractResult?.host !== 'snapchat') {
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.short_link" };
} }
params = extractResult.patternMatch; params = extractResult.patternMatch;
@ -92,5 +96,5 @@ export default async function (obj) {
if (result) return result; if (result) return result;
} }
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.fail" };
} }

View file

@ -39,7 +39,7 @@ async function findClientID() {
export default async function(obj) { export default async function(obj) {
let clientId = await findClientID(); let clientId = await findClientID();
if (!clientId) return { error: 'ErrorSoundCloudNoClientId' }; if (!clientId) return { error: "fetch.fail" };
let link; let link;
if (obj.url.hostname === 'on.soundcloud.com' && obj.shortLink) { if (obj.url.hostname === 'on.soundcloud.com' && obj.shortLink) {
@ -54,15 +54,16 @@ export default async function(obj) {
link = `https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}` link = `https://soundcloud.com/${obj.author}/${obj.song}${obj.accessKey ? `/s-${obj.accessKey}` : ''}`
} }
if (!link) return { error: 'ErrorCouldntFetch' }; if (!link && obj.shortLink) return { error: "fetch.short_link" };
if (!link) return { error: "link.unsupported" };
let json = await fetch(`https://api-v2.soundcloud.com/resolve?url=${link}&client_id=${clientId}`) let json = await fetch(`https://api-v2.soundcloud.com/resolve?url=${link}&client_id=${clientId}`)
.then(r => r.status === 200 ? r.json() : false) .then(r => r.status === 200 ? r.json() : false)
.catch(() => {}); .catch(() => {});
if (!json) return { error: 'ErrorCouldntFetch' }; if (!json) return { error: "fetch.fail" };
if (!json.media.transcodings) return { error: 'ErrorEmptyDownload' }; if (!json.media.transcodings) return { error: "fetch.empty" };
let bestAudio = "opus", let bestAudio = "opus",
selectedStream = json.media.transcodings.find(v => v.preset === "opus_0_0"), selectedStream = json.media.transcodings.find(v => v.preset === "opus_0_0"),
@ -78,15 +79,21 @@ export default async function(obj) {
let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`; let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
if (!fileUrl.startsWith("https://api-v2.soundcloud.com/media/soundcloud:tracks:")) if (!fileUrl.startsWith("https://api-v2.soundcloud.com/media/soundcloud:tracks:"))
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
if (json.duration > env.durationLimit * 1000) if (json.duration > env.durationLimit * 1000) {
return { error: ['ErrorLengthAudioConvert', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
}
let file = await fetch(fileUrl) let file = await fetch(fileUrl)
.then(async r => (await r.json()).url) .then(async r => (await r.json()).url)
.catch(() => {}); .catch(() => {});
if (!file) return { error: 'ErrorCouldntFetch' }; if (!file) return { error: "fetch.empty" };
let fileMetadata = { let fileMetadata = {
title: cleanString(json.title.trim()), title: cleanString(json.title.trim()),

View file

@ -3,9 +3,9 @@ export default async function(obj) {
.then(r => r.status === 200 ? r.json() : false) .then(r => r.status === 200 ? r.json() : false)
.catch(() => {}); .catch(() => {});
if (!video) return { error: 'ErrorEmptyDownload' }; if (!video) return { error: "fetch.empty" };
let best = video.files['mp4-mobile']; let best = video.files["mp4-mobile"];
if (video.files.mp4 && (obj.isAudioOnly || obj.quality === "max" || obj.quality >= 720)) { if (video.files.mp4 && (obj.isAudioOnly || obj.quality === "max" || obj.quality >= 720)) {
best = video.files.mp4; best = video.files.mp4;
} }
@ -18,5 +18,5 @@ export default async function(obj) {
title: video.title title: video.title
} }
} }
return { error: 'ErrorEmptyDownload' } return { error: "fetch.fail" }
} }

View file

@ -1,7 +1,8 @@
import Cookie from "../cookie/cookie.js";
import { extract } from "../url.js";
import { genericUserAgent } from "../../config.js"; import { genericUserAgent } from "../../config.js";
import { updateCookie } from "../cookie/manager.js"; import { updateCookie } from "../cookie/manager.js";
import { extract } from "../url.js";
import Cookie from "../cookie/cookie.js";
const shortDomain = "https://vt.tiktok.com/"; const shortDomain = "https://vt.tiktok.com/";
@ -17,7 +18,7 @@ export default async function(obj) {
} }
}).then(r => r.text()).catch(() => {}); }).then(r => r.text()).catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html) return { error: "fetch.fail" };
if (html.startsWith('<a href="https://')) { if (html.startsWith('<a href="https://')) {
const extractedURL = html.split('<a href="')[1].split('?')[0]; const extractedURL = html.split('<a href="')[1].split('?')[0];
@ -25,7 +26,7 @@ export default async function(obj) {
postId = patternMatch.postId postId = patternMatch.postId
} }
} }
if (!postId) return { error: 'ErrorCantGetID' }; if (!postId) return { error: "fetch.short_link" };
// should always be /video/, even for photos // should always be /video/, even for photos
const res = await fetch(`https://tiktok.com/@i/video/${postId}`, { const res = await fetch(`https://tiktok.com/@i/video/${postId}`, {
@ -46,7 +47,7 @@ export default async function(obj) {
const data = JSON.parse(json) const data = JSON.parse(json)
detail = data["__DEFAULT_SCOPE__"]["webapp.video-detail"]["itemInfo"]["itemStruct"] detail = data["__DEFAULT_SCOPE__"]["webapp.video-detail"]["itemInfo"]["itemStruct"]
} catch { } catch {
return { error: 'ErrorCouldntFetch' }; return { error: "fetch.fail" };
} }
let video, videoFilename, audioFilename, audio, images, let video, videoFilename, audioFilename, audio, images,

View file

@ -22,7 +22,12 @@ export default async function(input) {
let { subdomain } = psl.parse(input.url.hostname); let { subdomain } = psl.parse(input.url.hostname);
if (subdomain?.includes('.')) { if (subdomain?.includes('.')) {
return { error: ['ErrorBrokenLink', 'tumblr'] } return {
error: "link.unsupported",
context: {
service: "tumblr"
}
}
} else if (subdomain === 'www' || subdomain === 'at') { } else if (subdomain === 'www' || subdomain === 'at') {
subdomain = undefined subdomain = undefined
} }
@ -31,7 +36,7 @@ export default async function(input) {
const data = await request(domain, input.id); const data = await request(domain, input.id);
const element = data?.response?.timeline?.elements?.[0]; const element = data?.response?.timeline?.elements?.[0];
if (!element) return { error: 'ErrorEmptyDownload' }; if (!element) return { error: "fetch.empty" };
const contents = [ const contents = [
...element.content, ...element.content,
@ -66,5 +71,5 @@ export default async function(input) {
} }
} }
return { error: 'ErrorEmptyDownload' } return { error: "link.unsupported" }
} }

View file

@ -5,7 +5,7 @@ const gqlURL = "https://gql.twitch.tv/gql";
const clientIdHead = { "client-id": "kimne78kx3ncx6brgo4mv6wki5h1ko" }; const clientIdHead = { "client-id": "kimne78kx3ncx6brgo4mv6wki5h1ko" };
export default async function (obj) { export default async function (obj) {
let req_metadata = await fetch(gqlURL, { const req_metadata = await fetch(gqlURL, {
method: "POST", method: "POST",
headers: clientIdHead, headers: clientIdHead,
body: JSON.stringify({ body: JSON.stringify({
@ -30,16 +30,24 @@ export default async function (obj) {
}` }`
}) })
}).then(r => r.status === 200 ? r.json() : false).catch(() => {}); }).then(r => r.status === 200 ? r.json() : false).catch(() => {});
if (!req_metadata) return { error: 'ErrorCouldntFetch' };
let clipMetadata = req_metadata.data.clip; if (!req_metadata) return { error: "fetch.fail" };
if (clipMetadata.durationSeconds > env.durationLimit) const clipMetadata = req_metadata.data.clip;
return { error: ['ErrorLengthLimit', env.durationLimit / 60] };
if (!clipMetadata.videoQualities || !clipMetadata.broadcaster)
return { error: 'ErrorEmptyDownload' };
let req_token = await fetch(gqlURL, { if (clipMetadata.durationSeconds > env.durationLimit) {
return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
}
if (!clipMetadata.videoQualities || !clipMetadata.broadcaster) {
return { error: "fetch.empty" };
}
const req_token = await fetch(gqlURL, {
method: "POST", method: "POST",
headers: clientIdHead, headers: clientIdHead,
body: JSON.stringify([ body: JSON.stringify([
@ -58,10 +66,10 @@ export default async function (obj) {
]) ])
}).then(r => r.status === 200 ? r.json() : false).catch(() => {}); }).then(r => r.status === 200 ? r.json() : false).catch(() => {});
if (!req_token) return { error: 'ErrorCouldntFetch' }; if (!req_token) return { error: "fetch.fail" };
let formats = clipMetadata.videoQualities; const formats = clipMetadata.videoQualities;
let format = formats.find(f => f.quality === obj.quality) || formats[0]; const format = formats.find(f => f.quality === obj.quality) || formats[0];
return { return {
type: "proxy", type: "proxy",

View file

@ -105,7 +105,7 @@ export default async function({ id, index, toGif, dispatcher }) {
const cookie = await getCookie('twitter'); const cookie = await getCookie('twitter');
let guestToken = await getGuestToken(dispatcher); let guestToken = await getGuestToken(dispatcher);
if (!guestToken) return { error: 'ErrorCouldntFetch' }; if (!guestToken) return { error: "fetch.fail" };
let tweet = await requestTweet(dispatcher, id, guestToken); let tweet = await requestTweet(dispatcher, id, guestToken);
@ -123,18 +123,22 @@ export default async function({ id, index, toGif, dispatcher }) {
const reason = tweet?.data?.tweetResult?.result?.reason; const reason = tweet?.data?.tweetResult?.result?.reason;
switch(reason) { switch(reason) {
case "Protected": case "Protected":
return { error: 'ErrorTweetProtected' } return { error: "content.post.private" }
case "NsfwLoggedOut": case "NsfwLoggedOut":
if (cookie) { if (cookie) {
tweet = await requestTweet(dispatcher, id, guestToken, cookie); tweet = await requestTweet(dispatcher, id, guestToken, cookie);
tweet = await tweet.json(); tweet = await tweet.json();
tweetTypename = tweet?.data?.tweetResult?.result?.__typename; tweetTypename = tweet?.data?.tweetResult?.result?.__typename;
} else return { error: 'ErrorTweetNSFW' } } else return { error: "content.post.age" }
} }
} }
if (!tweetTypename) {
return { error: "link.invalid" }
}
if (!["Tweet", "TweetWithVisibilityResults"].includes(tweetTypename)) { if (!["Tweet", "TweetWithVisibilityResults"].includes(tweetTypename)) {
return { error: 'ErrorTweetUnavailable' } return { error: "content.post.unavailable" }
} }
let tweetResult = tweet.data.tweetResult.result, let tweetResult = tweet.data.tweetResult.result,
@ -156,7 +160,9 @@ export default async function({ id, index, toGif, dispatcher }) {
switch (media?.length) { switch (media?.length) {
case undefined: case undefined:
case 0: case 0:
return { error: 'ErrorNoVideosInTweet' }; return {
error: "fetch.empty"
}
case 1: case 1:
if (media[0].type === "photo") { if (media[0].type === "photo") {
return { return {

View file

@ -1,8 +1,8 @@
import HLS from "hls-parser";
import { env } from "../../config.js"; import { env } from "../../config.js";
import { cleanString, merge } from '../../misc/utils.js'; import { cleanString, merge } from '../../misc/utils.js';
import HLS from "hls-parser";
const resolutionMatch = { const resolutionMatch = {
"3840": 2160, "3840": 2160,
"2732": 1440, "2732": 1440,
@ -73,25 +73,30 @@ const getHLS = async (configURL, obj) => {
const api = await fetch(configURL) const api = await fetch(configURL)
.then(r => r.json()) .then(r => r.json())
.catch(() => {}); .catch(() => {});
if (!api) return { error: 'ErrorCouldntFetch' }; if (!api) return { error: "fetch.fail" };
if (api.video?.duration > env.durationLimit) { if (api.video?.duration > env.durationLimit) {
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
} }
const urlMasterHLS = api.request?.files?.hls?.cdns?.akfire_interconnect_quic?.url; const urlMasterHLS = api.request?.files?.hls?.cdns?.akfire_interconnect_quic?.url;
if (!urlMasterHLS) return { error: 'ErrorCouldntFetch' } if (!urlMasterHLS) return { error: "fetch.fail" };
const masterHLS = await fetch(urlMasterHLS) const masterHLS = await fetch(urlMasterHLS)
.then(r => r.text()) .then(r => r.text())
.catch(() => {}); .catch(() => {});
if (!masterHLS) return { error: 'ErrorCouldntFetch' }; if (!masterHLS) return { error: "fetch.fail" };
const variants = HLS.parse(masterHLS)?.variants?.sort( const variants = HLS.parse(masterHLS)?.variants?.sort(
(a, b) => Number(b.bandwidth) - Number(a.bandwidth) (a, b) => Number(b.bandwidth) - Number(a.bandwidth)
); );
if (!variants || variants.length === 0) return { error: 'ErrorEmptyDownload' }; if (!variants || variants.length === 0) return { error: "fetch.empty" };
let bestQuality; let bestQuality;
@ -116,7 +121,7 @@ const getHLS = async (configURL, obj) => {
expandLink(audioPath) expandLink(audioPath)
] ]
} else if (obj.isAudioOnly) { } else if (obj.isAudioOnly) {
return { error: 'ErrorEmptyDownload' }; return { error: "fetch.empty" };
} }
return { return {
@ -143,7 +148,7 @@ export default async function(obj) {
} }
if (!response) response = getDirectLink(info, quality); if (!response) response = getDirectLink(info, quality);
if (!response) response = { error: 'ErrorEmptyDownload' }; if (!response) response = { error: "fetch.empty" };
if (response.error) { if (response.error) {
return response; return response;

View file

@ -3,7 +3,7 @@ export default async function(obj) {
.then(r => r.json()) .then(r => r.json())
.catch(() => {}); .catch(() => {});
if (!post) return { error: 'ErrorEmptyDownload' }; if (!post) return { error: "fetch.empty" };
if (post.videoUrl) return { if (post.videoUrl) return {
urls: post.videoUrl.replace("http://", "https://"), urls: post.videoUrl.replace("http://", "https://"),
@ -11,5 +11,5 @@ export default async function(obj) {
audioFilename: `vine_${obj.id}_audio` audioFilename: `vine_${obj.id}_audio`
} }
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }

View file

@ -1,5 +1,5 @@
import { genericUserAgent, env } from "../../config.js";
import { cleanString } from "../../misc/utils.js"; import { cleanString } from "../../misc/utils.js";
import { genericUserAgent, env } from "../../config.js";
const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240"]; const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240"];
@ -7,22 +7,35 @@ export default async function(o) {
let html, url, quality = o.quality === "max" ? 2160 : o.quality; let html, url, quality = o.quality === "max" ? 2160 : o.quality;
html = await fetch(`https://vk.com/video${o.userId}_${o.videoId}`, { html = await fetch(`https://vk.com/video${o.userId}_${o.videoId}`, {
headers: { "user-agent": genericUserAgent } headers: {
}).then(r => r.arrayBuffer()).catch(() => {}); "user-agent": genericUserAgent
}
})
.then(r => r.arrayBuffer())
.catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html) return { error: "fetch.fail" };
// decode cyrillic from windows-1251 because vk still uses apis from prehistoric times // decode cyrillic from windows-1251 because vk still uses apis from prehistoric times
let decoder = new TextDecoder('windows-1251'); let decoder = new TextDecoder('windows-1251');
html = decoder.decode(html); html = decoder.decode(html);
if (!html.includes(`{"lang":`)) return { error: 'ErrorEmptyDownload' }; if (!html.includes(`{"lang":`)) return { error: "fetch.empty" };
let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]); let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
if (Number(js.mvData.is_active_live) !== 0) return { error: 'ErrorLiveVideo' }; if (Number(js.mvData.is_active_live) !== 0) {
if (js.mvData.duration > env.durationLimit) return { error: "content.video.live" };
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; }
if (js.mvData.duration > env.durationLimit) {
return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
}
for (let i in resolutions) { for (let i in resolutions) {
if (js.player.params[0][`url${resolutions[i]}`]) { if (js.player.params[0][`url${resolutions[i]}`]) {
@ -51,5 +64,5 @@ export default async function(o) {
extension: "mp4" extension: "mp4"
} }
} }
return { error: 'ErrorEmptyDownload' } return { error: "fetch.empty" }
} }

View file

@ -110,7 +110,7 @@ export default async function(o) {
); );
} catch(e) { } catch(e) {
if (e.message?.endsWith("decipher algorithm")) { if (e.message?.endsWith("decipher algorithm")) {
return { error: "ErrorYoutubeDecipher" } return { error: "youtube.decipher" }
} else throw e; } else throw e;
} }
@ -130,38 +130,56 @@ export default async function(o) {
try { try {
info = await yt.getBasicInfo(o.id, yt.session.logged_in ? 'ANDROID' : 'IOS'); info = await yt.getBasicInfo(o.id, yt.session.logged_in ? 'ANDROID' : 'IOS');
} catch(e) { } catch(e) {
if (e?.message === 'This video is unavailable') { if (e?.info?.reason === "This video is private") {
return { error: 'ErrorCouldntFetch' }; return { error: "content.video.private" };
} else if (e?.message === "This video is unavailable") {
return { error: "content.video.unavailable" };
} else { } else {
return { error: 'ErrorCantConnectToServiceAPI' }; return { error: "fetch.fail" };
} }
} }
if (!info) return { error: 'ErrorCantConnectToServiceAPI' }; if (!info) return { error: "fetch.fail" };
const playability = info.playability_status; const playability = info.playability_status;
const basicInfo = info.basic_info; const basicInfo = info.basic_info;
if (playability.status === 'LOGIN_REQUIRED') { if (playability.status === "LOGIN_REQUIRED") {
if (playability.reason.endsWith('bot')) { if (playability.reason.endsWith("bot")) {
return { error: 'ErrorYTLogin' } return { error: "youtube.login" }
} }
if (playability.reason.endsWith('age')) { if (playability.reason.endsWith("age")) {
return { error: 'ErrorYTAgeRestrict' } return { error: "content.video.age" }
}
if (playability?.error_screen?.reason?.text === "Private video") {
return { error: "content.video.private" }
} }
}
if (playability.status === "UNPLAYABLE" && playability.reason.endsWith('request limit.')) {
return { error: 'ErrorYTRateLimit' }
} }
if (playability.status !== 'OK') return { error: 'ErrorYTUnavailable' }; if (playability.status === "UNPLAYABLE") {
if (basicInfo.is_live) return { error: 'ErrorLiveVideo' }; if (playability?.reason?.endsWith("request limit.")) {
return { error: "fetch.rate" }
}
if (playability?.error_screen?.subreason?.text?.endsWith("in your country")) {
return { error: "content.video.region" }
}
if (playability?.error_screen?.reason?.text === "Private video") {
return { error: "content.video.private" }
}
}
if (playability.status !== "OK") {
return { error: "content.video.unavailable" };
}
if (basicInfo.is_live) {
return { error: "content.video.live" };
}
// return a critical error if returned video is "Video Not Available" // return a critical error if returned video is "Video Not Available"
// or a similar stub by youtube // or a similar stub by youtube
if (basicInfo.id !== o.id) { if (basicInfo.id !== o.id) {
return { return {
error: 'ErrorCantConnectToServiceAPI', error: "fetch.fail",
critical: true critical: true
} }
} }
@ -189,10 +207,15 @@ export default async function(o) {
if (bestQuality) bestQuality = qual(bestQuality); if (bestQuality) bestQuality = qual(bestQuality);
if ((!bestQuality && !o.isAudioOnly) || !hasAudio) if ((!bestQuality && !o.isAudioOnly) || !hasAudio)
return { error: 'ErrorYTTryOtherCodec' }; return { error: "youtube.codec" };
if (basicInfo.duration > env.durationLimit) if (basicInfo.duration > env.durationLimit)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] }; return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
const checkBestAudio = (i) => (i.has_audio && !i.has_video); const checkBestAudio = (i) => (i.has_audio && !i.has_video);
@ -286,5 +309,5 @@ export default async function(o) {
} }
} }
return { error: 'ErrorYTTryOtherCodec' } return { error: "fetch.fail" }
} }