mirror of
https://github.com/wukko/cobalt.git
synced 2024-11-15 12:50:01 +00:00
api: update error codes in services, add more error codes where needed
This commit is contained in:
parent
c698d272a1
commit
05abf9ad3e
21 changed files with 263 additions and 141 deletions
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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" };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}`;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
@ -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,10 +295,15 @@ 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);
|
||||||
|
|
||||||
|
@ -320,7 +325,7 @@ 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)
|
||||||
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(""", '"');
|
?.replaceAll(""", '"');
|
||||||
|
|
||||||
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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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.?=]+&uc=\d+)" as="video"\/>/;
|
const SPOTLIGHT_VIDEO_REGEX = /<link data-react-helmet="true" rel="preload" href="(https:\/\/cf-st\.sc-cdn\.net\/d\/[\w.?=]+&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" };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue