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) {
return createResponse("critical", {
code: r.error
code: `error.api.${r.error}`,
})
}
if (r.error) {
return createResponse("error", {
code: r.error,
context: response?.context
code: `error.api.${r.error}`,
context: r?.context
})
}

View file

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

View file

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

View file

@ -33,7 +33,8 @@ export default async function({ id, shareType, shortLink }) {
.then(r => r.text())
.catch(() => false);
if (!html) return { error: 'ErrorCouldntFetch' };
if (!html && shortLink) return { error: "fetch.short_link" }
if (!html) return { error: "fetch.fail" };
const urls = [];
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 (!urls.length) {
return { error: 'ErrorEmptyDownload' };
return { error: "fetch.empty" };
}
const baseFilename = `facebook_${id || shortLink}`;

View file

@ -1,5 +1,5 @@
import { createStream } from "../../stream/manage.js";
import { genericUserAgent } from "../../config.js";
import { createStream } from "../../stream/manage.js";
import { getCookie, updateCookie } from "../cookie/manager.js";
const commonHeaders = {
@ -206,7 +206,7 @@ export default function(obj) {
.map(e => {
const type = e.video_versions ? "video" : "photo";
const imageUrl = e.image_versions2.candidates[0].url;
let url = imageUrl;
if (type === 'video') {
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;
try {
const cookie = getCookie('instagram');
const bearer = getCookie('instagram_bearer');
const token = bearer?.values()?.token;
@ -271,7 +271,7 @@ export default function(obj) {
if (!data && cookie) data = await requestGQL(id, cookie);
} catch {}
if (!data) return { error: 'ErrorCouldntFetch' };
if (!data) return { error: "fetch.fail" };
if (data?.gql_data) {
result = extractOldPost(data, id)
@ -280,7 +280,7 @@ export default function(obj) {
}
if (result) return result;
return { error: 'ErrorEmptyDownload' }
return { error: "fetch.empty" }
}
async function usernameToId(username, cookie) {
@ -295,11 +295,16 @@ export default function(obj) {
async function getStory(username, id) {
const cookie = getCookie('instagram');
if (!cookie) return { error: 'ErrorUnsupported' };
if (!cookie) return {
error: "link.unsupported",
context: {
service: "instagram"
}
}
const userId = await usernameToId(username, cookie);
if (!userId) return { error: 'ErrorEmptyDownload' };
if (!userId) return { error: "fetch.empty" };
const dtsgId = await findDtsgId(cookie);
const url = new URL('https://www.instagram.com/api/graphql/');
@ -320,8 +325,8 @@ export default function(obj) {
} catch {}
const item = media.items.find(m => m.pk === id);
if (!item) return { error: 'ErrorEmptyDownload' };
if (!item) return { error: "fetch.empty" };
if (item.video_versions) {
const video = item.video_versions.reduce((a, b) => a.width * a.height < b.width * b.height ? b : a)
return {
@ -338,12 +343,17 @@ export default function(obj) {
}
}
return { error: 'ErrorUnsupported' };
return {
error: "link.unsupported",
context: {
service: "instagram"
}
}
}
const { postId, storyId, username } = obj;
if (postId) return getPost(postId);
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)
.catch(() => {});
if (!gql) return { error: 'ErrorEmptyDownload' };
if (!gql) return { error: "fetch.empty" };
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 }
}).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="({.*?})"( .*?)>/)
?.[1]
?.replaceAll("&quot;", '"');
if (!videoData) {
return { error: 'ErrorEmptyDownload' };
return { error: "fetch.empty" };
}
videoData = JSON.parse(JSON.parse(videoData).flashvars.metadata);
if (videoData.provider !== "UPLOADED_ODKL")
return { error: 'ErrorUnsupported' };
return { error: "link.unsupported" };
if (videoData.movie.is_live)
return { error: 'ErrorLiveVideo' };
return { error: "content.video.live" };
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 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(() => {});
}
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}/`, {
headers: { "user-agent": genericUserAgent }
}).then(r => r.text()).catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' };
if (!html) return { error: "fetch.fail" };
let videoLink = [...html.matchAll(videoRegex)]
.map(([, link]) => link)
@ -39,5 +39,5 @@ export default async function(o) {
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
* authenticating an account against reddit's oauth2 api
* see: https://github.com/reddit-archive/reddit/wiki/OAuth2
*
*
* any additional cookie fields are managed by this code and you
* should not touch them unless you know what you're doing. **/
const cookie = await getCookie('reddit');
@ -67,7 +67,9 @@ export default async function(obj) {
}
).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;
@ -77,10 +79,15 @@ export default async function(obj) {
}
if (!data.secure_media?.reddit_video)
return { error: 'ErrorEmptyDownload' };
return { error: "fetch.empty" };
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,
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 { cleanString } from '../../misc/utils.js';
import { cleanString } from "../../misc/utils.js";
async function requestJSON(url) {
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`
)
const yappyURL = yappy?.results?.find(r => r.id === obj.yappyId)?.link;
if (!yappyURL) return { error: 'ErrorEmptyDownload' };
if (!yappyURL) return { error: "fetch.empty" };
return {
urls: yappyURL,
@ -33,19 +33,24 @@ export default async function(obj) {
if (obj.key) requestURL.searchParams.set('p', obj.key);
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.live_streams?.hls) return { error: 'ErrorLiveVideo' };
if (play.detail || !play.video_balancer) return { error: "fetch.empty" };
if (play.live_streams?.hls) return { error: "content.video.live" };
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)
.then(r => r.text())
.catch(() => {});
if (!m3u8) return { error: 'ErrorCouldntFetch' };
if (!m3u8) return { error: "fetch.fail" };
m3u8 = HLS.parse(m3u8).variants;

View file

@ -1,16 +1,16 @@
import { extract, normalizeURL } from "../url.js";
import { genericUserAgent } from "../../config.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 NEXT_DATA_REGEX = /<script id="__NEXT_DATA__" type="application\/json">({.+})<\/script><\/body><\/html>$/;
async function getSpotlight(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);
if (!html) {
return { error: 'ErrorCouldntFetch' };
return { error: "fetch.fail" };
}
const videoURL = html.match(SPOTLIGHT_VIDEO_REGEX)?.[1];
@ -24,11 +24,15 @@ async function getSpotlight(id) {
}
async function getStory(username, storyId) {
const html = await fetch(`https://www.snapchat.com/add/${username}${storyId ? `/${storyId}` : ''}`, {
headers: { 'User-Agent': genericUserAgent }
}).then((r) => r.text()).catch(() => null);
const html = await fetch(
`https://www.snapchat.com/add/${username}${storyId ? `/${storyId}` : ''}`,
{ headers: { 'user-agent': genericUserAgent } }
)
.then((r) => r.text())
.catch(() => null);
if (!html) {
return { error: 'ErrorCouldntFetch' };
return { error: "fetch.fail" };
}
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}`);
if (!link?.startsWith('https://www.snapchat.com/')) {
return { error: 'ErrorCouldntFetch' };
return { error: "fetch.short_link" };
}
const extractResult = extract(normalizeURL(link));
if (extractResult?.host !== 'snapchat') {
return { error: 'ErrorCouldntFetch' };
return { error: "fetch.short_link" };
}
params = extractResult.patternMatch;
@ -92,5 +96,5 @@ export default async function (obj) {
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) {
let clientId = await findClientID();
if (!clientId) return { error: 'ErrorSoundCloudNoClientId' };
if (!clientId) return { error: "fetch.fail" };
let link;
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}` : ''}`
}
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}`)
.then(r => r.status === 200 ? r.json() : false)
.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",
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}`;
if (!fileUrl.startsWith("https://api-v2.soundcloud.com/media/soundcloud:tracks:"))
return { error: 'ErrorEmptyDownload' };
return { error: "fetch.empty" };
if (json.duration > env.durationLimit * 1000)
return { error: ['ErrorLengthAudioConvert', env.durationLimit / 60] };
if (json.duration > env.durationLimit * 1000) {
return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
}
let file = await fetch(fileUrl)
.then(async r => (await r.json()).url)
.catch(() => {});
if (!file) return { error: 'ErrorCouldntFetch' };
if (!file) return { error: "fetch.empty" };
let fileMetadata = {
title: cleanString(json.title.trim()),

View file

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

View file

@ -22,7 +22,12 @@ export default async function(input) {
let { subdomain } = psl.parse(input.url.hostname);
if (subdomain?.includes('.')) {
return { error: ['ErrorBrokenLink', 'tumblr'] }
return {
error: "link.unsupported",
context: {
service: "tumblr"
}
}
} else if (subdomain === 'www' || subdomain === 'at') {
subdomain = undefined
}
@ -31,7 +36,7 @@ export default async function(input) {
const data = await request(domain, input.id);
const element = data?.response?.timeline?.elements?.[0];
if (!element) return { error: 'ErrorEmptyDownload' };
if (!element) return { error: "fetch.empty" };
const contents = [
...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" };
export default async function (obj) {
let req_metadata = await fetch(gqlURL, {
const req_metadata = await fetch(gqlURL, {
method: "POST",
headers: clientIdHead,
body: JSON.stringify({
@ -30,16 +30,24 @@ export default async function (obj) {
}`
})
}).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)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] };
if (!clipMetadata.videoQualities || !clipMetadata.broadcaster)
return { error: 'ErrorEmptyDownload' };
const clipMetadata = req_metadata.data.clip;
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",
headers: clientIdHead,
body: JSON.stringify([
@ -58,10 +66,10 @@ export default async function (obj) {
])
}).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;
let format = formats.find(f => f.quality === obj.quality) || formats[0];
const formats = clipMetadata.videoQualities;
const format = formats.find(f => f.quality === obj.quality) || formats[0];
return {
type: "proxy",

View file

@ -105,7 +105,7 @@ export default async function({ id, index, toGif, dispatcher }) {
const cookie = await getCookie('twitter');
let guestToken = await getGuestToken(dispatcher);
if (!guestToken) return { error: 'ErrorCouldntFetch' };
if (!guestToken) return { error: "fetch.fail" };
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;
switch(reason) {
case "Protected":
return { error: 'ErrorTweetProtected' }
return { error: "content.post.private" }
case "NsfwLoggedOut":
if (cookie) {
tweet = await requestTweet(dispatcher, id, guestToken, cookie);
tweet = await tweet.json();
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)) {
return { error: 'ErrorTweetUnavailable' }
return { error: "content.post.unavailable" }
}
let tweetResult = tweet.data.tweetResult.result,
@ -156,7 +160,9 @@ export default async function({ id, index, toGif, dispatcher }) {
switch (media?.length) {
case undefined:
case 0:
return { error: 'ErrorNoVideosInTweet' };
return {
error: "fetch.empty"
}
case 1:
if (media[0].type === "photo") {
return {

View file

@ -1,8 +1,8 @@
import HLS from "hls-parser";
import { env } from "../../config.js";
import { cleanString, merge } from '../../misc/utils.js';
import HLS from "hls-parser";
const resolutionMatch = {
"3840": 2160,
"2732": 1440,
@ -73,25 +73,30 @@ const getHLS = async (configURL, obj) => {
const api = await fetch(configURL)
.then(r => r.json())
.catch(() => {});
if (!api) return { error: 'ErrorCouldntFetch' };
if (!api) return { error: "fetch.fail" };
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;
if (!urlMasterHLS) return { error: 'ErrorCouldntFetch' }
if (!urlMasterHLS) return { error: "fetch.fail" };
const masterHLS = await fetch(urlMasterHLS)
.then(r => r.text())
.catch(() => {});
if (!masterHLS) return { error: 'ErrorCouldntFetch' };
if (!masterHLS) return { error: "fetch.fail" };
const variants = HLS.parse(masterHLS)?.variants?.sort(
(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;
@ -116,7 +121,7 @@ const getHLS = async (configURL, obj) => {
expandLink(audioPath)
]
} else if (obj.isAudioOnly) {
return { error: 'ErrorEmptyDownload' };
return { error: "fetch.empty" };
}
return {
@ -143,7 +148,7 @@ export default async function(obj) {
}
if (!response) response = getDirectLink(info, quality);
if (!response) response = { error: 'ErrorEmptyDownload' };
if (!response) response = { error: "fetch.empty" };
if (response.error) {
return response;

View file

@ -3,7 +3,7 @@ export default async function(obj) {
.then(r => r.json())
.catch(() => {});
if (!post) return { error: 'ErrorEmptyDownload' };
if (!post) return { error: "fetch.empty" };
if (post.videoUrl) return {
urls: post.videoUrl.replace("http://", "https://"),
@ -11,5 +11,5 @@ export default async function(obj) {
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 { genericUserAgent, env } from "../../config.js";
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;
html = await fetch(`https://vk.com/video${o.userId}_${o.videoId}`, {
headers: { "user-agent": genericUserAgent }
}).then(r => r.arrayBuffer()).catch(() => {});
headers: {
"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
let decoder = new TextDecoder('windows-1251');
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]);
if (Number(js.mvData.is_active_live) !== 0) return { error: 'ErrorLiveVideo' };
if (js.mvData.duration > env.durationLimit)
return { error: ['ErrorLengthLimit', env.durationLimit / 60] };
if (Number(js.mvData.is_active_live) !== 0) {
return { error: "content.video.live" };
}
if (js.mvData.duration > env.durationLimit) {
return {
error: "content.too_long",
context: {
limit: env.durationLimit / 60
}
}
}
for (let i in resolutions) {
if (js.player.params[0][`url${resolutions[i]}`]) {
@ -51,5 +64,5 @@ export default async function(o) {
extension: "mp4"
}
}
return { error: 'ErrorEmptyDownload' }
return { error: "fetch.empty" }
}

View file

@ -110,7 +110,7 @@ export default async function(o) {
);
} catch(e) {
if (e.message?.endsWith("decipher algorithm")) {
return { error: "ErrorYoutubeDecipher" }
return { error: "youtube.decipher" }
} else throw e;
}
@ -130,38 +130,56 @@ export default async function(o) {
try {
info = await yt.getBasicInfo(o.id, yt.session.logged_in ? 'ANDROID' : 'IOS');
} catch(e) {
if (e?.message === 'This video is unavailable') {
return { error: 'ErrorCouldntFetch' };
if (e?.info?.reason === "This video is private") {
return { error: "content.video.private" };
} else if (e?.message === "This video is unavailable") {
return { error: "content.video.unavailable" };
} 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 basicInfo = info.basic_info;
if (playability.status === 'LOGIN_REQUIRED') {
if (playability.reason.endsWith('bot')) {
return { error: 'ErrorYTLogin' }
if (playability.status === "LOGIN_REQUIRED") {
if (playability.reason.endsWith("bot")) {
return { error: "youtube.login" }
}
if (playability.reason.endsWith('age')) {
return { error: 'ErrorYTAgeRestrict' }
if (playability.reason.endsWith("age")) {
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 (basicInfo.is_live) return { error: 'ErrorLiveVideo' };
if (playability.status === "UNPLAYABLE") {
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"
// or a similar stub by youtube
if (basicInfo.id !== o.id) {
return {
error: 'ErrorCantConnectToServiceAPI',
error: "fetch.fail",
critical: true
}
}
@ -189,10 +207,15 @@ export default async function(o) {
if (bestQuality) bestQuality = qual(bestQuality);
if ((!bestQuality && !o.isAudioOnly) || !hasAudio)
return { error: 'ErrorYTTryOtherCodec' };
return { error: "youtube.codec" };
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);
@ -286,5 +309,5 @@ export default async function(o) {
}
}
return { error: 'ErrorYTTryOtherCodec' }
return { error: "fetch.fail" }
}