mirror of
https://github.com/imputnet/cobalt.git
synced 2024-12-26 17:46:06 +00:00
api/vk: use proper api, add support for more links, refactor
also added support for video access keys
This commit is contained in:
parent
5ffc0c6161
commit
f696335278
|
@ -77,8 +77,9 @@ export default async function({ host, patternMatch, params }) {
|
|||
|
||||
case "vk":
|
||||
r = await vk({
|
||||
userId: patternMatch.userId,
|
||||
ownerId: patternMatch.ownerId,
|
||||
videoId: patternMatch.videoId,
|
||||
accessKey: patternMatch.accessKey,
|
||||
quality: params.videoQuality
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -154,9 +154,14 @@ export const services = {
|
|||
},
|
||||
vk: {
|
||||
patterns: [
|
||||
"video:userId_:videoId",
|
||||
"clip:userId_:videoId",
|
||||
"clips:duplicate?z=clip:userId_:videoId"
|
||||
"video:ownerId_:videoId",
|
||||
"clip:ownerId_:videoId",
|
||||
"clips:duplicate?z=clip:ownerId_:videoId",
|
||||
"videos:duplicate?z=video:ownerId_:videoId",
|
||||
"video:ownerId_:videoId_:accessKey",
|
||||
"clip:ownerId_:videoId_:accessKey",
|
||||
"clips:duplicate?z=clip:ownerId_:videoId_:accessKey",
|
||||
"videos:duplicate?z=video:ownerId_:videoId_:accessKey"
|
||||
],
|
||||
subdomains: ["m"],
|
||||
altDomains: ["vkvideo.ru", "vk.ru"],
|
||||
|
|
|
@ -56,7 +56,8 @@ export const testers = {
|
|||
&& (!pattern.password || pattern.password.length < 16),
|
||||
|
||||
"vk": pattern =>
|
||||
pattern.userId?.length <= 10 && pattern.videoId?.length <= 10,
|
||||
(pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10) ||
|
||||
(pattern.ownerId?.length <= 10 && pattern.videoId?.length <= 10 && pattern.videoId?.accessKey <= 18),
|
||||
|
||||
"youtube": pattern =>
|
||||
pattern.id?.length <= 11,
|
||||
|
|
|
@ -1,62 +1,144 @@
|
|||
import { genericUserAgent, env } from "../../config.js";
|
||||
import { env } from "../../config.js";
|
||||
|
||||
const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240"];
|
||||
const resolutions = ["2160", "1440", "1080", "720", "480", "360", "240", "144"];
|
||||
|
||||
export default async function(o) {
|
||||
let html, url, quality = o.quality === "max" ? 2160 : o.quality;
|
||||
const oauthUrl = "https://oauth.vk.com/oauth/get_anonym_token";
|
||||
const apiUrl = "https://api.vk.com/method";
|
||||
|
||||
html = await fetch(`https://vk.com/video${o.userId}_${o.videoId}`, {
|
||||
headers: {
|
||||
"user-agent": genericUserAgent
|
||||
}
|
||||
})
|
||||
.then(r => r.arrayBuffer())
|
||||
.catch(() => {});
|
||||
const clientId = "51552953";
|
||||
const clientSecret = "qgr0yWwXCrsxA1jnRtRX";
|
||||
|
||||
if (!html) return { error: "fetch.fail" };
|
||||
// used in stream/shared.js for accessing media files
|
||||
export const vkClientAgent = "com.vk.vkvideo.prod/822 (iPhone, iOS 16.7.7, iPhone10,4, Scale/2.0) SAK/1.119";
|
||||
|
||||
// decode cyrillic from windows-1251 because vk still uses apis from prehistoric times
|
||||
let decoder = new TextDecoder('windows-1251');
|
||||
html = decoder.decode(html);
|
||||
const cachedToken = {
|
||||
token: "",
|
||||
expiry: 0,
|
||||
device_id: "",
|
||||
};
|
||||
|
||||
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: "content.video.live" };
|
||||
const getToken = async () => {
|
||||
if (cachedToken.expiry - 10 > Math.floor(new Date().getTime() / 1000)) {
|
||||
return cachedToken.token;
|
||||
}
|
||||
|
||||
if (js.mvData.duration > env.durationLimit) {
|
||||
const randomDeviceId = crypto.randomUUID().toUpperCase();
|
||||
|
||||
const anonymOauth = new URL(oauthUrl);
|
||||
anonymOauth.searchParams.set("client_id", clientId);
|
||||
anonymOauth.searchParams.set("client_secret", clientSecret);
|
||||
anonymOauth.searchParams.set("device_id", randomDeviceId);
|
||||
|
||||
const oauthResponse = await fetch(anonymOauth.toString(), {
|
||||
headers: {
|
||||
"user-agent": vkClientAgent,
|
||||
}
|
||||
}).then(r => {
|
||||
if (r.status === 200) {
|
||||
return r.json();
|
||||
}
|
||||
});
|
||||
|
||||
if (!oauthResponse) return;
|
||||
|
||||
if (oauthResponse?.token && oauthResponse?.expired_at && typeof oauthResponse?.expired_at === "number") {
|
||||
cachedToken.token = oauthResponse.token;
|
||||
cachedToken.expiry = oauthResponse.expired_at;
|
||||
cachedToken.device_id = randomDeviceId;
|
||||
}
|
||||
|
||||
if (!cachedToken.token) return;
|
||||
|
||||
return cachedToken.token;
|
||||
}
|
||||
|
||||
const getVideo = async (ownerId, videoId, accessKey) => {
|
||||
const video = await fetch(`${apiUrl}/video.get`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"user-agent": vkClientAgent,
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
anonymous_token: cachedToken.token,
|
||||
device_id: cachedToken.device_id,
|
||||
lang: "en",
|
||||
v: "5.244",
|
||||
videos: `${ownerId}_${videoId}${accessKey ? `_${accessKey}` : ''}`
|
||||
}).toString()
|
||||
})
|
||||
.then(r => {
|
||||
if (r.status === 200) {
|
||||
return r.json();
|
||||
}
|
||||
});
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
export default async function ({ ownerId, videoId, accessKey, quality }) {
|
||||
const token = await getToken();
|
||||
if (!token) return { error: "fetch.fail" };
|
||||
|
||||
const videoGet = await getVideo(ownerId, videoId, accessKey);
|
||||
|
||||
if (!videoGet || !videoGet.response || videoGet.response.items.length !== 1) {
|
||||
return { error: "fetch.empty" };
|
||||
}
|
||||
|
||||
const video = videoGet.response.items[0];
|
||||
|
||||
if (video.restriction) {
|
||||
const title = video.restriction.title;
|
||||
if (title.endsWith("country") || title.endsWith("region.")) {
|
||||
return { error: "content.video.region" };
|
||||
}
|
||||
if (title === "Processing video") {
|
||||
return { error: "fetch.empty" };
|
||||
}
|
||||
return { error: "content.video.unavailable" };
|
||||
}
|
||||
|
||||
if (!video.files || !video.duration) {
|
||||
return { error: "fetch.fail" };
|
||||
}
|
||||
|
||||
if (video.duration > env.durationLimit) {
|
||||
return { error: "content.too_long" };
|
||||
}
|
||||
|
||||
const userQuality = quality === "max" ? 2160 : quality;
|
||||
let pickedQuality;
|
||||
|
||||
for (let i in resolutions) {
|
||||
if (js.player.params[0][`url${resolutions[i]}`]) {
|
||||
quality = resolutions[i];
|
||||
if (video.files[`mp4_${resolutions[i]}`]) {
|
||||
pickedQuality = resolutions[i];
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Number(quality) > Number(o.quality)) quality = o.quality;
|
||||
|
||||
url = js.player.params[0][`url${quality}`];
|
||||
|
||||
let fileMetadata = {
|
||||
title: js.player.params[0].md_title.trim(),
|
||||
author: js.player.params[0].md_author.trim(),
|
||||
if (Number(pickedQuality) > Number(userQuality)) {
|
||||
pickedQuality = userQuality;
|
||||
}
|
||||
|
||||
if (url) return {
|
||||
const url = video.files[`mp4_${pickedQuality}`];
|
||||
|
||||
if (!url) return { error: "fetch.fail" };
|
||||
|
||||
const fileMetadata = {
|
||||
title: video.title.trim(),
|
||||
}
|
||||
|
||||
return {
|
||||
urls: url,
|
||||
fileMetadata,
|
||||
filenameAttributes: {
|
||||
service: "vk",
|
||||
id: `${o.userId}_${o.videoId}`,
|
||||
id: `${ownerId}_${videoId}${accessKey ? `_${accessKey}` : ''}`,
|
||||
title: fileMetadata.title,
|
||||
author: fileMetadata.author,
|
||||
resolution: `${quality}p`,
|
||||
qualityLabel: `${quality}p`,
|
||||
resolution: `${pickedQuality}p`,
|
||||
qualityLabel: `${pickedQuality}p`,
|
||||
extension: "mp4"
|
||||
}
|
||||
}
|
||||
return { error: "fetch.empty" }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { genericUserAgent } from "../config.js";
|
||||
import { vkClientAgent } from "../processing/services/vk.js";
|
||||
|
||||
const defaultHeaders = {
|
||||
'user-agent': genericUserAgent
|
||||
|
@ -13,6 +14,9 @@ const serviceHeaders = {
|
|||
origin: 'https://www.youtube.com',
|
||||
referer: 'https://www.youtube.com',
|
||||
DNT: '?1'
|
||||
},
|
||||
vk: {
|
||||
'user-agent': vkClientAgent
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue