moved to new repo
This commit is contained in:
72
modules/services/all.json
Normal file
72
modules/services/all.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"bilibili": {
|
||||
"alias": "bilibili.com",
|
||||
"patterns": ["video/:id"],
|
||||
"quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"],
|
||||
"enabled": true
|
||||
},
|
||||
"reddit": {
|
||||
"patterns": ["r/:sub/comments/:id/:title"],
|
||||
"enabled": true
|
||||
},
|
||||
"twitter": {
|
||||
"patterns": [":user/status/:id"],
|
||||
"quality_match": ["1080", "720", "480", "360", "240", "144"],
|
||||
"enabled": true,
|
||||
"api": "api.twitter.com",
|
||||
"token": "AAAAAAAAAAAAAAAAAAAAAIK1zgAAAAAA2tUWuhGZ2JceoId5GwYWU5GspY4%3DUq7gzFoCZs1QfwGoVdvSac3IniczZEYXIcDyumCauIXpcAPorE",
|
||||
"apiURLs": {
|
||||
"activate": "1.1/guest/activate.json",
|
||||
"status_show": "1.1/statuses/show.json"
|
||||
}
|
||||
},
|
||||
"vk": {
|
||||
"patterns": ["video-:userId_:videoId"],
|
||||
"quality_match": {
|
||||
"2160": 7,
|
||||
"1440": 6,
|
||||
"1080": 5,
|
||||
"720": 3,
|
||||
"480": 2,
|
||||
"360": 1,
|
||||
"240": 0,
|
||||
"144": 4
|
||||
},
|
||||
"quality": {
|
||||
"1080": "hig",
|
||||
"720": "mid",
|
||||
"480": "low"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
"youtube": {
|
||||
"patterns": ["watch?v=:id"],
|
||||
"quality_match": ["2160", "1440", "1080", "720", "480", "360", "240", "144"],
|
||||
"quality": {
|
||||
"1080": "hig",
|
||||
"720": "mid",
|
||||
"480": "low"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
"youtube music": {
|
||||
"patterns": ["watch?v=:id"],
|
||||
"enabled": true
|
||||
},
|
||||
"tumblr": {
|
||||
"patterns": ["post/:id"],
|
||||
"enabled": false
|
||||
},
|
||||
"facebook": {
|
||||
"patterns": [":pageid/:type/:postid"],
|
||||
"enabled": false
|
||||
},
|
||||
"instagram": {
|
||||
"patterns": [":type/:id"],
|
||||
"enabled": false
|
||||
},
|
||||
"tiktok": {
|
||||
"patterns": [":pageid/:type/:postid", ":id"],
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
38
modules/services/bilibili.js
Normal file
38
modules/services/bilibili.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import got from "got";
|
||||
import loc from "../sub/loc.js";
|
||||
import { genericUserAgent, maxVideoDuration } from "../config.js";
|
||||
|
||||
export default async function(obj) {
|
||||
try {
|
||||
let html = await got.get(`https://bilibili.com/video/${obj.id}`, {
|
||||
headers: { "user-agent": genericUserAgent }
|
||||
});
|
||||
html.on('error', (err) => {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
});
|
||||
html = html.body;
|
||||
if (html.includes('<script>window.__playinfo__=') && html.includes('"video_codecid"')) {
|
||||
let streamData = JSON.parse(html.split('<script>window.__playinfo__=')[1].split('</script>')[0]);
|
||||
if (streamData.data.timelength <= maxVideoDuration) {
|
||||
let video = streamData["data"]["dash"]["video"].filter((v) => {
|
||||
if (!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/") && v["height"] != 4320) {
|
||||
return true;
|
||||
}
|
||||
}).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
|
||||
let audio = streamData["data"]["dash"]["audio"].filter((a) => {
|
||||
if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) {
|
||||
return true;
|
||||
}
|
||||
}).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
|
||||
return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], time: streamData.data.timelength, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` };
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
}
|
||||
} catch (e) {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
}
|
||||
}
|
||||
|
||||
17
modules/services/reddit.js
Normal file
17
modules/services/reddit.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import got from "got";
|
||||
import loc from "../sub/loc.js";
|
||||
import { genericUserAgent, maxVideoDuration } from "../config.js";
|
||||
|
||||
export default async function(obj) {
|
||||
try {
|
||||
let req = await got.get(`https://www.reddit.com/r/${obj.sub}/comments/${obj.id}/${obj.name}.json`, { headers: { "user-agent": genericUserAgent } });
|
||||
let data = (JSON.parse(req.body))[0]["data"]["children"][0]["data"];
|
||||
if ("reddit_video" in data["secure_media"] && data["secure_media"]["reddit_video"]["duration"] * 1000 < maxVideoDuration) {
|
||||
return { urls: [data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0], `${data["secure_media"]["reddit_video"]["fallback_url"].split('_')[0]}_audio.mp4`], filename: `reddit_${data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3]}.mp4` };
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'nothingToDownload') };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: loc("en", "apiError", "nothingToDownload") };
|
||||
}
|
||||
}
|
||||
57
modules/services/twitter.js
Normal file
57
modules/services/twitter.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import got from "got";
|
||||
import loc from "../sub/loc.js";
|
||||
import { services } from "../config.js";
|
||||
|
||||
const configSt = services.twitter;
|
||||
|
||||
async function fetchTweetInfo(obj) {
|
||||
let cantConnect = { error: loc('en', 'apiError', 'cantConnectToAPI', 'twitter') }
|
||||
try {
|
||||
let _headers = {
|
||||
"Authorization": `Bearer ${configSt.token}`,
|
||||
"Host": configSt.api,
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": 0
|
||||
};
|
||||
let req_act = await got.post(`https://${configSt.api}/${configSt.apiURLs.activate}`, {
|
||||
headers: _headers
|
||||
});
|
||||
req_act.on('error', (err) => {
|
||||
return cantConnect
|
||||
})
|
||||
_headers["x-guest-token"] = req_act.body["guest_token"];
|
||||
let req_status = await got.get(`https://${configSt.api}/${configSt.apiURLs.status_show}?id=${obj.id}&tweet_mode=extended`, {
|
||||
headers: _headers
|
||||
});
|
||||
req_status.on('error', (err) => {
|
||||
return cantConnect
|
||||
})
|
||||
return JSON.parse(req_status.body);
|
||||
} catch (err) {
|
||||
return { error: cantConnect };
|
||||
}
|
||||
}
|
||||
export default async function (obj) {
|
||||
let nothing = { error: loc('en', 'apiError', 'nothingToDownload') }
|
||||
try {
|
||||
let parsbod = await fetchTweetInfo(obj);
|
||||
if (!parsbod.error) {
|
||||
if (parsbod.hasOwnProperty("extended_entities") && parsbod["extended_entities"].hasOwnProperty("media")) {
|
||||
if (parsbod["extended_entities"]["media"][0]["type"] === "video" || parsbod["extended_entities"]["media"][0]["type"] === "animated_gif") {
|
||||
let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"]
|
||||
return variants.filter((v) => {
|
||||
if (v["content_type"] == "video/mp4") {
|
||||
return true
|
||||
}
|
||||
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"]
|
||||
} else {
|
||||
return nothing
|
||||
}
|
||||
} else {
|
||||
return nothing
|
||||
}
|
||||
} else return parsbod;
|
||||
} catch (err) {
|
||||
return { error: loc("en", "apiError", "youtubeBroke") };
|
||||
}
|
||||
}
|
||||
47
modules/services/vk.js
Normal file
47
modules/services/vk.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import got from "got";
|
||||
import { xml2json } from "xml-js";
|
||||
import loc from "../sub/loc.js";
|
||||
import { genericUserAgent, maxVideoDuration, services } from "../config.js";
|
||||
import selectQuality from "../stream/select-quality.js";
|
||||
|
||||
export default async function(obj) {
|
||||
try {
|
||||
let html = await got.get(`https://vk.com/video-${obj.userId}_${obj.videoId}`, { headers: { "user-agent": genericUserAgent } });
|
||||
html.on('error', (err) => {
|
||||
return false;
|
||||
});
|
||||
html = html.body;
|
||||
if (html.includes(`{"lang":`)) {
|
||||
let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
|
||||
if (js["mvData"]["is_active_live"] == '0') {
|
||||
if (js["mvData"]["duration"] <= maxVideoDuration / 1000) {
|
||||
let mpd = JSON.parse(xml2json(js["player"]["params"][0]["manifest"], { compact: true, spaces: 4 }));
|
||||
|
||||
let repr = mpd["MPD"]["Period"]["AdaptationSet"]["Representation"];
|
||||
if (!mpd["MPD"]["Period"]["AdaptationSet"]["Representation"]) {
|
||||
repr = mpd["MPD"]["Period"]["AdaptationSet"][0]["Representation"];
|
||||
}
|
||||
let attr = repr[repr.length - 1]["_attributes"];
|
||||
let selectedQuality = `url${attr["height"]}`;
|
||||
|
||||
let maxQuality = js["player"]["params"][0][selectedQuality].split('type=')[1].slice(0, 1)
|
||||
let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r;})[maxQuality])
|
||||
|
||||
if (selectedQuality in js["player"]["params"][0]) {
|
||||
return { url: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${js["player"]["params"][0][selectedQuality].split("id=")[1]}_${attr['width']}x${attr['height']}.mp4` };
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'nothingToDownload') };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'liveVideo') };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'nothingToDownload') };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
}
|
||||
}
|
||||
74
modules/services/youtube.js
Normal file
74
modules/services/youtube.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import ytdl from "ytdl-core";
|
||||
import loc from "../sub/loc.js";
|
||||
import { maxVideoDuration, quality as mq } from "../config.js";
|
||||
import selectQuality from "../stream/select-quality.js";
|
||||
|
||||
export default async function (obj) {
|
||||
try {
|
||||
let info = await ytdl.getInfo(obj.id);
|
||||
if (info) {
|
||||
info = info.formats;
|
||||
if (!info[0]["isLive"]) {
|
||||
if (obj.isAudioOnly) {
|
||||
obj.format = "webm"
|
||||
obj.quality = "max"
|
||||
}
|
||||
let selectedVideo, videoMatch = [], video = [], audio = info.filter((a) => {
|
||||
if (!a["isLive"] && !a["isHLS"] && !a["isDashMPD"] && a["hasAudio"] && !a["hasVideo"] && a["container"] == obj.format) {
|
||||
return true;
|
||||
}
|
||||
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
|
||||
if (!obj.isAudioOnly) {
|
||||
video = info.filter((a) => {
|
||||
if (!a["isLive"] && !a["isHLS"] && !a["isDashMPD"] && !a["hasAudio"] && a["hasVideo"] && a["container"] == obj.format && a["height"] != 4320) {
|
||||
if (obj.quality != "max" && mq[obj.quality] == a["height"]) {
|
||||
videoMatch.push(a)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
|
||||
selectedVideo = video[0]
|
||||
if (obj.quality != "max") {
|
||||
if (videoMatch.length > 0) {
|
||||
selectedVideo = videoMatch[0]
|
||||
} else {
|
||||
let ss = selectQuality("youtube", obj.quality, video[0]["height"])
|
||||
selectedVideo = video.filter((a) => {
|
||||
if (a["height"] == ss) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
selectedVideo = selectedVideo[0]
|
||||
}
|
||||
}
|
||||
if (obj.quality == "los") {
|
||||
selectedVideo = video[video.length - 1]
|
||||
}
|
||||
}
|
||||
if (audio[0]["approxDurationMs"] <= maxVideoDuration) {
|
||||
if (!obj.isAudioOnly && video.length > 0) {
|
||||
let filename = `youtube_${obj.id}_${selectedVideo["width"]}x${selectedVideo["height"]}.${obj.format}`;
|
||||
if (video.length > 0 && audio.length > 0) {
|
||||
return { type: "render", urls: [selectedVideo["url"], audio[0]["url"]], time: video[0]["approxDurationMs"], filename: filename };
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeBroke') };
|
||||
}
|
||||
} else if (audio.length > 0) {
|
||||
return { type: "render", isAudioOnly: true, urls: [audio[0]["url"]], filename: `youtube_${obj.id}_${audio[0]["audioBitrate"]}kbps.opus` };
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeBroke') };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeLimit', maxVideoDuration / 60000) };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'liveVideo') };
|
||||
}
|
||||
} else {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
}
|
||||
} catch (e) {
|
||||
return { error: loc('en', 'apiError', 'youtubeFetch') };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user