mirror of
https://github.com/Jerryplusy/rc-plugin.git
synced 2025-10-14 16:19:18 +00:00
🐞 fix: V1.3.3 修复bili音乐逻辑 (现在即可使用命令:bili音乐+链接,下载音乐啦!)
1. 将m4s发送转换为mp3发送格式 2. 提升了bili解析的扩展性 3. 整理了path获取的方式
This commit is contained in:
parent
9e44ced5f5
commit
2f794fd80b
107
apps/tools.js
107
apps/tools.js
@ -6,35 +6,54 @@ import { Buffer } from 'node:buffer';
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import tunnel from "tunnel";
|
import tunnel from "tunnel";
|
||||||
import HttpProxyAgent, { HttpsProxyAgent } from "https-proxy-agent";
|
import HttpProxyAgent from "https-proxy-agent";
|
||||||
import { mkdirIfNotExists, checkAndRemoveFile, deleteFolderRecursive } from "../utils/file.js";
|
import { checkAndRemoveFile, deleteFolderRecursive, mkdirIfNotExists } from "../utils/file.js";
|
||||||
import { downloadBFile, getAudioUrl, getDownloadUrl, mergeFileToMp4 } from "../utils/bilibili.js";
|
import {
|
||||||
import { parseUrl, parseM3u8, downloadM3u8Videos, mergeAcFileToMp4 } from "../utils/acfun.js";
|
downloadBFile,
|
||||||
|
getBiliAudio,
|
||||||
|
getDownloadUrl,
|
||||||
|
getDynamic,
|
||||||
|
getVideoInfo,
|
||||||
|
m4sToMp3,
|
||||||
|
mergeFileToMp4
|
||||||
|
} from "../utils/bilibili.js";
|
||||||
|
import { downloadM3u8Videos, mergeAcFileToMp4, parseM3u8, parseUrl } from "../utils/acfun.js";
|
||||||
import {
|
import {
|
||||||
transMap,
|
|
||||||
douyinTypeMap,
|
|
||||||
DIVIDING_LINE,
|
DIVIDING_LINE,
|
||||||
XHS_NO_WATERMARK_HEADER,
|
douyinTypeMap,
|
||||||
REDIS_YUNZAI_ISOVERSEA,
|
REDIS_YUNZAI_ISOVERSEA,
|
||||||
|
transMap,
|
||||||
TWITTER_BEARER_TOKEN,
|
TWITTER_BEARER_TOKEN,
|
||||||
|
XHS_NO_WATERMARK_HEADER,
|
||||||
} from "../constants/constant.js";
|
} from "../constants/constant.js";
|
||||||
import { containsChinese, formatBiliInfo, getIdVideo, secondsToTime } from "../utils/common.js";
|
import { containsChinese, formatBiliInfo, getIdVideo, secondsToTime } from "../utils/common.js";
|
||||||
import config from "../model/index.js";
|
import config from "../model/index.js";
|
||||||
import Translate from "../utils/trans-strategy.js";
|
import Translate from "../utils/trans-strategy.js";
|
||||||
import * as xBogus from "../utils/x-bogus.cjs";
|
import * as xBogus from "../utils/x-bogus.cjs";
|
||||||
import { getVideoInfo, getDynamic } from "../utils/biliInfo.js";
|
import { getBodianAudio, getBodianMusicInfo, getBodianMv } from "../utils/bodian.js";
|
||||||
import { getBodianAudio, getBodianMv, getBodianMusicInfo } from "../utils/bodian.js";
|
|
||||||
import { av2BV } from "../utils/bilibili-bv-av-convert.js";
|
import { av2BV } from "../utils/bilibili-bv-av-convert.js";
|
||||||
import querystring from "querystring";
|
import querystring from "querystring";
|
||||||
import TokenBucket from "../utils/token-bucket.js";
|
import TokenBucket from "../utils/token-bucket.js";
|
||||||
import { getWbi } from "../utils/biliWbi.js";
|
import { getWbi } from "../utils/biliWbi.js";
|
||||||
import { BILI_SUMMARY, DY_INFO, TIKTOK_INFO, TWITTER_TWEET_INFO, XHS_REQ_LINK } from "../constants/tools.js";
|
import { BILI_SUMMARY, DY_INFO, TIKTOK_INFO, TWITTER_TWEET_INFO, XHS_REQ_LINK, XHS_VIDEO } from "../constants/tools.js";
|
||||||
import { XHS_VIDEO } from "../constants/tools.js";
|
|
||||||
import child_process from 'node:child_process'
|
import child_process from 'node:child_process'
|
||||||
import { getAudio, getVideo } from "../utils/y2b.js";
|
import { getAudio, getVideo } from "../utils/y2b.js";
|
||||||
import { processTikTokUrl } from "../utils/tiktok.js";
|
import { processTikTokUrl } from "../utils/tiktok.js";
|
||||||
|
|
||||||
export class tools extends plugin {
|
export class tools extends plugin {
|
||||||
|
/**
|
||||||
|
* 构造安全的命令
|
||||||
|
* @type {{existsPromptKey: string, existsTransKey: string}}
|
||||||
|
*/
|
||||||
|
static Constants = {
|
||||||
|
existsTransKey: Object.keys(transMap).join("|"),
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 构造令牌桶,防止解析致使服务器宕机(默认限制5s调用一次)
|
||||||
|
* @type {TokenBucket}
|
||||||
|
*/
|
||||||
|
static #tokenBucket = new TokenBucket(1, 1, 5);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
name: "R插件工具和学习类",
|
name: "R插件工具和学习类",
|
||||||
@ -197,9 +216,7 @@ export class tools extends plugin {
|
|||||||
"http",
|
"http",
|
||||||
"https",
|
"https",
|
||||||
);
|
);
|
||||||
const path = `${ this.defaultPath }${
|
const path = `${ this.getCurDownloadPath(e) }/temp.mp4`;
|
||||||
this.e.group_id || this.e.user_id
|
|
||||||
}/temp.mp4`;
|
|
||||||
await this.downloadVideo(resUrl).then(() => {
|
await this.downloadVideo(resUrl).then(() => {
|
||||||
e.reply(segment.video(path));
|
e.reply(segment.video(path));
|
||||||
});
|
});
|
||||||
@ -257,7 +274,7 @@ export class tools extends plugin {
|
|||||||
this.downloadVideo(data.video.play_addr.url_list[0], !isOversea).then(video => {
|
this.downloadVideo(data.video.play_addr.url_list[0], !isOversea).then(video => {
|
||||||
e.reply(
|
e.reply(
|
||||||
segment.video(
|
segment.video(
|
||||||
`${ this.defaultPath }${ this.e.group_id || this.e.user_id }/temp.mp4`,
|
`${ this.getCurDownloadPath(e) }/temp.mp4`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -296,7 +313,7 @@ export class tools extends plugin {
|
|||||||
}
|
}
|
||||||
// 只提取音乐处理
|
// 只提取音乐处理
|
||||||
if (e.msg !== undefined && e.msg.includes("bili音乐")) {
|
if (e.msg !== undefined && e.msg.includes("bili音乐")) {
|
||||||
await this.biliMusic(url, e);
|
await this.biliMusic(e, url);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// 动态处理
|
// 动态处理
|
||||||
@ -346,7 +363,7 @@ export class tools extends plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建文件,如果不存在
|
// 创建文件,如果不存在
|
||||||
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/`;
|
const path = `${ this.getCurDownloadPath(e) }/`;
|
||||||
await mkdirIfNotExists(path);
|
await mkdirIfNotExists(path);
|
||||||
// 下载文件
|
// 下载文件
|
||||||
getDownloadUrl(url)
|
getDownloadUrl(url)
|
||||||
@ -367,9 +384,13 @@ export class tools extends plugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async biliMusic(url, e) {
|
// 下载哔哩哔哩音乐
|
||||||
const { audioUrl } = await getAudioUrl(url);
|
async biliMusic(e, url) {
|
||||||
e.reply(segment.record(audioUrl))
|
const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1];
|
||||||
|
getBiliAudio(videoId, "").then(async audioUrl => {
|
||||||
|
const path = this.getCurDownloadPath(e);
|
||||||
|
e.reply(segment.record(await m4sToMp3(audioUrl, path)));
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,6 +420,8 @@ export class tools extends plugin {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 小蓝鸟解析:停止更新
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 哔哩哔哩总结
|
* 哔哩哔哩总结
|
||||||
* @author zhiyu1998
|
* @author zhiyu1998
|
||||||
@ -492,7 +515,6 @@ export class tools extends plugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 小蓝鸟解析:停止更新
|
|
||||||
// 例子:https://twitter.com/chonkyanimalx/status/1595834168000204800
|
// 例子:https://twitter.com/chonkyanimalx/status/1595834168000204800
|
||||||
async twitter(e) {
|
async twitter(e) {
|
||||||
// 配置参数及解析
|
// 配置参数及解析
|
||||||
@ -512,14 +534,14 @@ export class tools extends plugin {
|
|||||||
await fetch(TWITTER_TWEET_INFO.replace("{}", id), {
|
await fetch(TWITTER_TWEET_INFO.replace("{}", id), {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "v2TweetLookupJS",
|
"User-Agent": "v2TweetLookupJS",
|
||||||
"authorization": `Bearer ${Buffer.from(TWITTER_BEARER_TOKEN, "base64").toString()}`
|
"authorization": `Bearer ${ Buffer.from(TWITTER_BEARER_TOKEN, "base64").toString() }`
|
||||||
},
|
},
|
||||||
...params,
|
...params,
|
||||||
agent: !isOversea ? '' : new HttpProxyAgent(this.myProxy),
|
agent: !isOversea ? '' : new HttpProxyAgent(this.myProxy),
|
||||||
}).then(async resp => {
|
}).then(async resp => {
|
||||||
logger.info(resp)
|
logger.info(resp)
|
||||||
e.reply(`识别:小蓝鸟学习版,${ resp.data.text }`);
|
e.reply(`识别:小蓝鸟学习版,${ resp.data.text }`);
|
||||||
const downloadPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
|
const downloadPath = `${ this.getCurDownloadPath(e) }`;
|
||||||
// 创建文件夹(如果没有过这个群)
|
// 创建文件夹(如果没有过这个群)
|
||||||
if (!fs.existsSync(downloadPath)) {
|
if (!fs.existsSync(downloadPath)) {
|
||||||
mkdirsSync(downloadPath);
|
mkdirsSync(downloadPath);
|
||||||
@ -569,7 +591,7 @@ export class tools extends plugin {
|
|||||||
|
|
||||||
// acfun解析
|
// acfun解析
|
||||||
async acfun(e) {
|
async acfun(e) {
|
||||||
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/temp/`;
|
const path = `${ this.getCurDownloadPath(e) }/temp/`;
|
||||||
await mkdirIfNotExists(path);
|
await mkdirIfNotExists(path);
|
||||||
|
|
||||||
let inputMsg = e.msg;
|
let inputMsg = e.msg;
|
||||||
@ -619,9 +641,9 @@ export class tools extends plugin {
|
|||||||
} else {
|
} else {
|
||||||
id = /explore\/(\w+)/.exec(msgUrl)?.[1] || /discovery\/item\/(\w+)/.exec(msgUrl)?.[1];
|
id = /explore\/(\w+)/.exec(msgUrl)?.[1] || /discovery\/item\/(\w+)/.exec(msgUrl)?.[1];
|
||||||
}
|
}
|
||||||
const downloadPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
|
const downloadPath = `${ this.getCurDownloadPath(e) }`;
|
||||||
// 获取信息
|
// 获取信息
|
||||||
fetch(`${XHS_REQ_LINK}${ id }`, {
|
fetch(`${ XHS_REQ_LINK }${ id }`, {
|
||||||
headers: XHS_NO_WATERMARK_HEADER,
|
headers: XHS_NO_WATERMARK_HEADER,
|
||||||
}).then(async resp => {
|
}).then(async resp => {
|
||||||
const xhsHtml = await resp.text();
|
const xhsHtml = await resp.text();
|
||||||
@ -641,7 +663,7 @@ export class tools extends plugin {
|
|||||||
this.downloadVideo(xhsVideoUrl).then(path => {
|
this.downloadVideo(xhsVideoUrl).then(path => {
|
||||||
if (path === undefined) {
|
if (path === undefined) {
|
||||||
// 创建文件,如果不存在
|
// 创建文件,如果不存在
|
||||||
path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/`;
|
path = `${ this.getCurDownloadPath(e) }/`;
|
||||||
}
|
}
|
||||||
e.reply(segment.video(path + "/temp.mp4"));
|
e.reply(segment.video(path + "/temp.mp4"));
|
||||||
});
|
});
|
||||||
@ -730,7 +752,7 @@ export class tools extends plugin {
|
|||||||
const API = `https://imginn.com/${ suffix }`;
|
const API = `https://imginn.com/${ suffix }`;
|
||||||
// logger.info(API);
|
// logger.info(API);
|
||||||
let imgPromise = [];
|
let imgPromise = [];
|
||||||
const downloadPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
|
const downloadPath = `${ this.getCurDownloadPath(e) }`;
|
||||||
// 判断是否是海外服务器
|
// 判断是否是海外服务器
|
||||||
const isOversea = await this.isOverseasServer();
|
const isOversea = await this.isOverseasServer();
|
||||||
// 简单封装图片下载
|
// 简单封装图片下载
|
||||||
@ -813,7 +835,7 @@ export class tools extends plugin {
|
|||||||
segment.image(albumPic120),
|
segment.image(albumPic120),
|
||||||
]);
|
]);
|
||||||
if (e.msg.includes("musicId")) {
|
if (e.msg.includes("musicId")) {
|
||||||
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
|
const path = `${ this.getCurDownloadPath(e) }`;
|
||||||
await getBodianAudio(id, path).then(_ => {
|
await getBodianAudio(id, path).then(_ => {
|
||||||
Bot.acquireGfs(e.group_id).upload(
|
Bot.acquireGfs(e.group_id).upload(
|
||||||
fs.readFileSync(path + "/temp.mp3"),
|
fs.readFileSync(path + "/temp.mp3"),
|
||||||
@ -968,12 +990,12 @@ export class tools extends plugin {
|
|||||||
const format = `${ bestVideo.id }x${ bestAudio.id }`
|
const format = `${ bestVideo.id }x${ bestAudio.id }`
|
||||||
// 下载地址格式化
|
// 下载地址格式化
|
||||||
const path = `${ v }${ p ? `/p${ p }` : '' }`;
|
const path = `${ v }${ p ? `/p${ p }` : '' }`;
|
||||||
const fullpath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/${ path }`;
|
const fullpath = `${ this.getCurDownloadPath(e) }/${ path }`;
|
||||||
// 创建下载文件夹
|
// 创建下载文件夹
|
||||||
await mkdirIfNotExists(fullpath);
|
await mkdirIfNotExists(fullpath);
|
||||||
// yt-dlp下载
|
// yt-dlp下载
|
||||||
let cmd = //`cd '${__dirname}' && (cd tmp > /dev/null || (mkdir tmp && cd tmp)) &&` +
|
let cmd = //`cd '${__dirname}' && (cd tmp > /dev/null || (mkdir tmp && cd tmp)) &&` +
|
||||||
`yt-dlp ${ this.y2bCk !== undefined ? `--cookies ${ this.y2bCk }` : '' } ${url} -f ${ format.replace('x', '+') } ` +
|
`yt-dlp ${ this.y2bCk !== undefined ? `--cookies ${ this.y2bCk }` : '' } ${ url } -f ${ format.replace('x', '+') } ` +
|
||||||
`-o '${ fullpath }/${ v }.%(ext)s' ${ isProxy ? `--proxy ${ this.proxyAddr }:${ this.proxyPort }` : '' } -k --write-info-json`;
|
`-o '${ fullpath }/${ v }.%(ext)s' ${ isProxy ? `--proxy ${ this.proxyAddr }:${ this.proxyPort }` : '' } -k --write-info-json`;
|
||||||
logger.mark(cmd)
|
logger.mark(cmd)
|
||||||
try {
|
try {
|
||||||
@ -1099,6 +1121,15 @@ export class tools extends plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前发送人/群的下载路径
|
||||||
|
* @param e Yunzai 机器人事件
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getCurDownloadPath(e) {
|
||||||
|
return `${ this.defaultPath }${ e.group_id || e.user_id }`
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取视频下载位置
|
* 提取视频下载位置
|
||||||
* @returns {{groupPath: string, target: string}}
|
* @returns {{groupPath: string, target: string}}
|
||||||
@ -1208,18 +1239,4 @@ export class tools extends plugin {
|
|||||||
logger.warn(`解析被限制使用`);
|
logger.warn(`解析被限制使用`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造安全的命令
|
|
||||||
* @type {{existsPromptKey: string, existsTransKey: string}}
|
|
||||||
*/
|
|
||||||
static Constants = {
|
|
||||||
existsTransKey: Object.keys(transMap).join("|"),
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造令牌桶,防止解析致使服务器宕机(默认限制5s调用一次)
|
|
||||||
* @type {TokenBucket}
|
|
||||||
*/
|
|
||||||
static #tokenBucket = new TokenBucket(1, 1, 5);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
- {
|
- {
|
||||||
version: 1.3.2,
|
version: 1.3.3,
|
||||||
data:
|
data:
|
||||||
[
|
[
|
||||||
新增<span class="cmd">油管解析</span>功能,
|
新增<span class="cmd">油管解析</span>功能,
|
||||||
|
@ -5,6 +5,27 @@
|
|||||||
*/
|
*/
|
||||||
export const BILI_SUMMARY = "https://api.bilibili.com/x/web-interface/view/conclusion/get"
|
export const BILI_SUMMARY = "https://api.bilibili.com/x/web-interface/view/conclusion/get"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频流URL
|
||||||
|
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const BILI_PLAY_STREAM = "https://api.bilibili.com/x/player/playurl?cid={cid}&bvid={bvid}&qn=64&fnval=16"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态信息
|
||||||
|
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/dynamic/content.md
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const BILI_DYNAMIC = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id={}"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BVID -> CID
|
||||||
|
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/33bde6f6afcac2ff8c6f7069f08ce84065a6cff6/docs/video/info.md?plain=1#L4352
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const BILI_BVID_TO_CID = "https://api.bilibili.com/x/player/pagelist?bvid={bvid}&jsonp=jsonp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 视频基本信息API
|
* 视频基本信息API
|
||||||
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/info.md
|
* https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/info.md
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import fetch from "node-fetch";
|
|
||||||
import axios from "axios";
|
|
||||||
import { BILI_VIDEO_INFO } from "../constants/tools.js";
|
|
||||||
|
|
||||||
async function getVideoInfo(url) {
|
|
||||||
// const baseVideoInfo = "http://api.bilibili.com/x/web-interface/view";
|
|
||||||
const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1];
|
|
||||||
// 获取视频信息,然后发送
|
|
||||||
return fetch(`${BILI_VIDEO_INFO}?bvid=${videoId}`)
|
|
||||||
.then(async resp => {
|
|
||||||
const respJson = await resp.json();
|
|
||||||
const respData = respJson.data;
|
|
||||||
return {
|
|
||||||
title: respData.title,
|
|
||||||
pic: respData.pic,
|
|
||||||
desc: respData.desc,
|
|
||||||
duration: respData.duration,
|
|
||||||
dynamic: respJson.data.dynamic,
|
|
||||||
stat: respData.stat,
|
|
||||||
bvid: respData.bvid,
|
|
||||||
aid: respData.aid,
|
|
||||||
cid: respData.pages?.[0].cid,
|
|
||||||
owner: respData.owner,
|
|
||||||
pages: respData?.pages,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDynamic(dynamicId) {
|
|
||||||
const dynamicApi = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=${dynamicId}`
|
|
||||||
return axios.get(dynamicApi, {
|
|
||||||
headers: {
|
|
||||||
'User-Agent':
|
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
|
||||||
'referer': 'https://www.bilibili.com',
|
|
||||||
}
|
|
||||||
}).then(resp => {
|
|
||||||
const dynamicData = resp.data.data.card
|
|
||||||
const card = JSON.parse(dynamicData.card)
|
|
||||||
const dynamicOrigin = card.item
|
|
||||||
const dynamicDesc = dynamicOrigin.description
|
|
||||||
|
|
||||||
const pictures = dynamicOrigin.pictures
|
|
||||||
let dynamicSrc = []
|
|
||||||
for (let pic of pictures) {
|
|
||||||
const img_src = pic.img_src
|
|
||||||
dynamicSrc.push(img_src)
|
|
||||||
}
|
|
||||||
// console.log(dynamic_src)
|
|
||||||
return {
|
|
||||||
dynamicSrc,
|
|
||||||
dynamicDesc
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getVideoInfo, getDynamic };
|
|
@ -2,8 +2,17 @@ import fs from "node:fs";
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import child_process from 'node:child_process'
|
import child_process from 'node:child_process'
|
||||||
import util from "util";
|
import util from "util";
|
||||||
|
import { BILI_BVID_TO_CID, BILI_DYNAMIC, BILI_PLAY_STREAM, BILI_VIDEO_INFO } from "../constants/tools.js";
|
||||||
|
import { mkdirIfNotExists } from "./file.js";
|
||||||
|
|
||||||
async function downloadBFile (url, fullFileName, progressCallback) {
|
/**
|
||||||
|
* 下载单个bili文件
|
||||||
|
* @param url
|
||||||
|
* @param fullFileName
|
||||||
|
* @param progressCallback
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export async function downloadBFile(url, fullFileName, progressCallback) {
|
||||||
return axios
|
return axios
|
||||||
.get(url, {
|
.get(url, {
|
||||||
responseType: 'stream',
|
responseType: 'stream',
|
||||||
@ -35,7 +44,12 @@ async function downloadBFile (url, fullFileName, progressCallback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDownloadUrl (url) {
|
/**
|
||||||
|
* 获取下载链接
|
||||||
|
* @param url
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export async function getDownloadUrl(url) {
|
||||||
return axios
|
return axios
|
||||||
.get(url, {
|
.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -65,34 +79,15 @@ async function getDownloadUrl (url) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAudioUrl (url) {
|
/**
|
||||||
return axios
|
* 合并视频和音频
|
||||||
.get(url, {
|
* @param vFullFileName
|
||||||
headers: {
|
* @param aFullFileName
|
||||||
'User-Agent':
|
* @param outputFileName
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
* @param shouldDelete
|
||||||
referer: 'https://www.bilibili.com',
|
* @returns {Promise<{outputFileName}>}
|
||||||
},
|
*/
|
||||||
})
|
export async function mergeFileToMp4(vFullFileName, aFullFileName, outputFileName, shouldDelete = true) {
|
||||||
.then(({ data }) => {
|
|
||||||
const info = JSON.parse(
|
|
||||||
data.match(/<script>window\.__playinfo__=({.*})<\/script><script>/)?.[1],
|
|
||||||
);
|
|
||||||
// 获取音频
|
|
||||||
const audioUrl =
|
|
||||||
info?.data?.dash?.audio?.[0]?.baseUrl ?? info?.data?.dash?.audio?.[0]?.backupUrl?.[0];
|
|
||||||
const title = data.match(/title="(.*?)"/)?.[1]?.replaceAll?.(/\\|\/|:|\*|\?|"|<|>|\|/g, '');
|
|
||||||
|
|
||||||
|
|
||||||
if (audioUrl) {
|
|
||||||
return { audioUrl, title };
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject('获取下载地址失败');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mergeFileToMp4 (vFullFileName, aFullFileName, outputFileName, shouldDelete = true) {
|
|
||||||
// 判断当前环境
|
// 判断当前环境
|
||||||
let env;
|
let env;
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
@ -122,4 +117,140 @@ async function mergeFileToMp4 (vFullFileName, aFullFileName, outputFileName, sho
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { downloadBFile, getDownloadUrl, getAudioUrl, mergeFileToMp4 }
|
/**
|
||||||
|
* 下载m4s文件,通过ffmpeg转换成mp3
|
||||||
|
* @param m4sUrl
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function m4sToMp3(m4sUrl, path) {
|
||||||
|
return axios
|
||||||
|
.get(m4sUrl, {
|
||||||
|
responseType: 'stream',
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
||||||
|
referer: 'https://www.bilibili.com',
|
||||||
|
},
|
||||||
|
}).then(async res => {
|
||||||
|
// 如果没有目录就创建一个
|
||||||
|
await mkdirIfNotExists(path)
|
||||||
|
// 补充保存文件名
|
||||||
|
path += "/temp.m4s";
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
}
|
||||||
|
// 开始下载
|
||||||
|
const fileStream = fs.createWriteStream(path);
|
||||||
|
res.data.pipe(fileStream);
|
||||||
|
// 下载完成
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fileStream.on("finish", () => {
|
||||||
|
fileStream.close(() => {
|
||||||
|
const transformCmd = `ffmpeg -i ${ path } ${ path.replace(".m4s", ".mp3") } -y -loglevel quiet`;
|
||||||
|
child_process.execSync(transformCmd)
|
||||||
|
logger.mark("bili: mp3下载完成")
|
||||||
|
resolve(path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fileStream.on("error", err => {
|
||||||
|
fs.unlink(path, () => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 哔哩哔哩音乐下载
|
||||||
|
* @param bvid BVID
|
||||||
|
* @param cid (选项)CID
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export async function getBiliAudio(bvid, cid) {
|
||||||
|
// 转换cid
|
||||||
|
if (!cid)
|
||||||
|
cid = await fetchCID(bvid).catch((err) => console.log(err))
|
||||||
|
|
||||||
|
// 返回一个fetch的promise
|
||||||
|
return (new Promise((resolve, reject) => {
|
||||||
|
fetch(BILI_PLAY_STREAM.replace("{bvid}", bvid).replace("{cid}", cid))
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => resolve(json.data.dash.audio[0].baseUrl));
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bvid转换成cid
|
||||||
|
* @param bvid
|
||||||
|
* @returns {Promise<*>}
|
||||||
|
*/
|
||||||
|
export const fetchCID = async (bvid) => {
|
||||||
|
//console.log('Data.js Calling fetchCID:' + URL_BVID_TO_CID.replace("{bvid}", bvid))
|
||||||
|
const res = await fetch(BILI_BVID_TO_CID.replace("{bvid}", bvid))
|
||||||
|
const json = await res.json()
|
||||||
|
const cid = json.data[0].cid
|
||||||
|
return cid
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频信息
|
||||||
|
* @param url
|
||||||
|
* @returns {Promise<{duration: *, owner: *, bvid: *, stat: *, pages: *, dynamic: *, pic: *, title: *, aid: *, desc: *, cid: *}>}
|
||||||
|
*/
|
||||||
|
export async function getVideoInfo(url) {
|
||||||
|
// const baseVideoInfo = "http://api.bilibili.com/x/web-interface/view";
|
||||||
|
const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1];
|
||||||
|
// 获取视频信息,然后发送
|
||||||
|
return fetch(`${ BILI_VIDEO_INFO }?bvid=${ videoId }`)
|
||||||
|
.then(async resp => {
|
||||||
|
const respJson = await resp.json();
|
||||||
|
const respData = respJson.data;
|
||||||
|
return {
|
||||||
|
title: respData.title,
|
||||||
|
pic: respData.pic,
|
||||||
|
desc: respData.desc,
|
||||||
|
duration: respData.duration,
|
||||||
|
dynamic: respJson.data.dynamic,
|
||||||
|
stat: respData.stat,
|
||||||
|
bvid: respData.bvid,
|
||||||
|
aid: respData.aid,
|
||||||
|
cid: respData.pages?.[0].cid,
|
||||||
|
owner: respData.owner,
|
||||||
|
pages: respData?.pages,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取动态
|
||||||
|
* @param dynamicId
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
export async function getDynamic(dynamicId) {
|
||||||
|
const dynamicApi = BILI_DYNAMIC.replace("{}", dynamicId);
|
||||||
|
return axios.get(dynamicApi, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
||||||
|
'referer': 'https://www.bilibili.com',
|
||||||
|
}
|
||||||
|
}).then(resp => {
|
||||||
|
const dynamicData = resp.data.data.card
|
||||||
|
const card = JSON.parse(dynamicData.card)
|
||||||
|
const dynamicOrigin = card.item
|
||||||
|
const dynamicDesc = dynamicOrigin.description
|
||||||
|
|
||||||
|
const pictures = dynamicOrigin.pictures
|
||||||
|
let dynamicSrc = []
|
||||||
|
for (let pic of pictures) {
|
||||||
|
const img_src = pic.img_src
|
||||||
|
dynamicSrc.push(img_src)
|
||||||
|
}
|
||||||
|
// console.log(dynamic_src)
|
||||||
|
return {
|
||||||
|
dynamicSrc,
|
||||||
|
dynamicDesc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user