feat: 新增哔哩哔哩官方AI总结、哔哩哔哩音乐提取

1. 新增哔哩哔哩音乐提取,使用“bili音乐+链接”即可提取视频中的音乐
2. 重构部分代码
3. 更换tiktok API
4. 更换视频总结方式GPT为官方的视频摘要(免费使用)
5. 删除GPT相关内容
This commit is contained in:
zhiyu 2024-01-26 12:49:11 +08:00
parent 444830003c
commit d0e6e6e5bd
13 changed files with 274 additions and 427 deletions

View File

@ -61,7 +61,7 @@ sudo apt-get install ffmpeg
<img src="./img/example6.webp" alt="小程序解析" width="50%" height="50%" />
5. 【可选】对哔哩哔哩解析进行总结:需要填写accessToken和哔哩哔哩的SESSDATA
5. 【可选】对哔哩哔哩解析进行总结需要填写哔哩哔哩的SESSDATA
<img src="./img/example7.webp" alt="小程序解析" width="50%" height="50%" />
@ -70,10 +70,28 @@ sudo apt-get install ffmpeg
- 锅巴设置
## 🐤 Q&A
### bilibili问题
> 哔哩哔哩的SESSDATA
> 进入哔哩哔哩网站 -- 打开F12开发者选项 -- 应用 -- 找到Cookie -- 找到SESSDATA -- 复制 -- 粘贴到plugins/rconsole-plugin/config/tools.yaml or 锅巴
> [图文教程【群友推荐!】](https://www.bilibili.com/read/cv12349604)
### 🎵 douyin问题
由于douyin的解析变化莫测现版本需要填入自己的cookie具体步骤如下
1. 打开`https://www.douyin.com/` 扫码登入自己的账号
2. F12进入控制台或者下载一个[Cookie-Editor](https://www.crxsoso.com/webstore/detail/hlkenndednhfkekhgcdicdfddnkalmdm)
3. 如果是F12就将以下参数填入到`tools.yaml - douyinCookie`,或者使用锅巴
> odin_tt=xxx;sessionid_ss=xxx;ttwid=xxx;passport_csrf_token=xxx;msToken=xxx;
3. 如果是`Cookie-Editor`就直接到插件复制到`tools.yaml - douyinCookie`,或者锅巴
具体图示,找以下这几个:
- odin_tt
- sessionid_ss
- ttwid
- passport_csrf_token
- msToken
![douyin_cookie](./img/douyin_cookie.webp)
## 🤺 R插件交流群
扫码不行就575663150
@ -88,22 +106,10 @@ sudo apt-get install ffmpeg
`proxyAddr: '127.0.0.1' # 魔法地址`
`proxyPort: '7890' # 魔法端口`
## 🎵 douyin_cookie问题
由于douyin的解析变化莫测现版本需要填入自己的cookie具体步骤如下
1. 打开`https://www.douyin.com/` 扫码登入自己的账号
2. F12进入控制台或者下载一个[Cookie-Editor](https://www.crxsoso.com/webstore/detail/hlkenndednhfkekhgcdicdfddnkalmdm)
3. 如果是F12就将以下参数填入到`tools.yaml - douyinCookie`,或者使用锅巴
> odin_tt=xxx;sessionid_ss=xxx;ttwid=xxx;passport_csrf_token=xxx;msToken=xxx;
> 海外服务器示例:
`proxyAddr: '127.0.0.1' # 魔法地址`
`proxyPort: '80' # 魔法端口`
3. 如果是`Cookie-Editor`就直接到插件复制到`tools.yaml - douyinCookie`,或者锅巴
具体图示,找以下这几个:
- odin_tt
- sessionid_ss
- ttwid
- passport_csrf_token
- msToken
![douyin_cookie](./img/douyin_cookie.webp)
## 📦 业务
![help](./img/help.webp)

View File

@ -6,7 +6,7 @@ import puppeteer from "../../../lib/puppeteer/puppeteer.js";
// http库
import axios from "axios";
// 常量
import { CAT_LIMIT } from "../utils/constant.js";
import { CAT_LIMIT } from "../constants/constant.js";
// 书库
import { getZHelper, getYiBook, getZBook } from "../utils/books.js";
// 工具类

View File

@ -7,10 +7,15 @@ import _ from "lodash";
import tunnel from "tunnel";
import HttpProxyAgent from "https-proxy-agent";
import { mkdirIfNotExists, checkAndRemoveFile, deleteFolderRecursive } from "../utils/file.js";
import { downloadBFile, getDownloadUrl, mergeFileToMp4 } from "../utils/bilibili.js";
import { downloadBFile, getAudioUrl, getDownloadUrl, mergeFileToMp4 } from "../utils/bilibili.js";
import { parseUrl, parseM3u8, downloadM3u8Videos, mergeAcFileToMp4 } from "../utils/acfun.js";
import { transMap, douyinTypeMap, XHS_CK, TEN_THOUSAND } from "../utils/constant.js";
import { getIdVideo } from "../utils/common.js";
import {
transMap,
douyinTypeMap,
XHS_CK,
RESTRICTION_DESCRIPTION,
} from "../constants/constant.js";
import { dataProcessing, formatBiliInfo, getIdVideo, secondsToTime } from "../utils/common.js";
import config from "../model/index.js";
import Translate from "../utils/trans-strategy.js";
import * as xBogus from "../utils/x-bogus.cjs";
@ -21,6 +26,8 @@ import { ChatGPTBrowserClient, ChatGPTClient } from "@waylaidwanderer/chatgpt-ap
import { av2BV } from "../utils/bilibili-bv-av-convert.js";
import querystring from "querystring";
import TokenBucket from "../utils/token-bucket.js";
import { getWbi } from "../utils/biliWbi.js";
import { BILI_SUMMARY } from "../constants/bili.js";
export class tools extends plugin {
constructor() {
@ -31,7 +38,7 @@ export class tools extends plugin {
priority: 300,
rule: [
{
reg: `^(翻|trans)[${tools.Constants.existsTransKey}]`,
reg: `^(翻|trans)[${ tools.Constants.existsTransKey }]`,
fnc: "trans",
},
{
@ -92,30 +99,13 @@ export class tools extends plugin {
// 代理接口
this.proxyAddr = this.toolsConfig.proxyAddr;
this.proxyPort = this.toolsConfig.proxyPort;
this.myProxy = `http://${this.proxyAddr}:${this.proxyPort}`;
this.myProxy = `http://${ this.proxyAddr }:${ this.proxyPort }`;
// 加载哔哩哔哩配置
this.biliSessData = this.toolsConfig.biliSessData;
// 加载哔哩哔哩的限制时长
this.biliDuration = this.toolsConfig.biliDuration;
// 加载抖音Cookie
this.douyinCookie = this.toolsConfig.douyinCookie;
// 加载gpt配置accessToken、apiKey、模型
this.openaiAccessToken = this.toolsConfig.openaiAccessToken;
this.openaiApiKey = this.toolsConfig.openaiApiKey;
this.openaiModel = this.toolsConfig.openaiModel;
// 加载gpt客户端默认加载sk如果填了AccessToken就用AccessToken
this.chatGptClient = this.openaiAccessToken === '' ? new ChatGPTClient(this.openaiApiKey, {
modelOptions: {
model: this.openaiModel,
temperature: 0,
},
proxy: this.myProxy,
debug: false,
}) : new ChatGPTBrowserClient({
reverseProxyUrl: "https://bypass.churchless.tech/api/conversation",
accessToken: this.openaiAccessToken,
model: this.openaiModel,
})
}
// 翻译插件
@ -164,15 +154,15 @@ export class tools extends plugin {
Referer: "https://www.douyin.com/",
cookie: this.douyinCookie,
};
const dyApi = `https://www.douyin.com/aweme/v1/web/aweme/detail/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=${douId}&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1344&screen_height=756&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=118.0&browser_online=true&engine_name=Gecko&engine_version=109.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=&platform=PC&webid=7284189800734082615&msToken=B1N9FM825TkvFbayDsDvZxM8r5suLrsfQbC93TciS0O9Iii8iJpAPd__FM2rpLUJi5xtMencSXLeNn8xmOS9q7bP0CUsrt9oVTL08YXLPRzZm0dHKLc9PGRlyEk=`;
const dyApi = `https://www.douyin.com/aweme/v1/web/aweme/detail/?device_platform=webapp&aid=6383&channel=channel_pc_web&aweme_id=${ douId }&pc_client_type=1&version_code=190500&version_name=19.5.0&cookie_enabled=true&screen_width=1344&screen_height=756&browser_language=zh-CN&browser_platform=Win32&browser_name=Firefox&browser_version=118.0&browser_online=true&engine_name=Gecko&engine_version=109.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=&platform=PC&webid=7284189800734082615&msToken=B1N9FM825TkvFbayDsDvZxM8r5suLrsfQbC93TciS0O9Iii8iJpAPd__FM2rpLUJi5xtMencSXLeNn8xmOS9q7bP0CUsrt9oVTL08YXLPRzZm0dHKLc9PGRlyEk=`;
// xg参数
const xbParam = xBogus.sign(
new URLSearchParams(new URL(dyApi).search).toString(),
headers["User-Agent"],
);
// const param = resp.data.result[0].paramsencode;
const resDyApi = `${dyApi}&X-Bogus=${xbParam}`;
headers['Referer'] = `https://www.douyin.com/video/${douId}`
const resDyApi = `${ dyApi }&X-Bogus=${ xbParam }`;
headers['Referer'] = `https://www.douyin.com/video/${ douId }`
axios
.get(resDyApi, {
headers,
@ -182,9 +172,9 @@ export class tools extends plugin {
e.reply("解析失败,请重试!");
return;
}
console.log(resp.data)
// console.log(resp.data)
const item = resp.data.aweme_detail;
e.reply(`识别:抖音, ${item.desc}`);
e.reply(`识别:抖音, ${ item.desc }`);
const urlTypeCode = item.aweme_type;
const urlType = douyinTypeMap[urlTypeCode];
if (urlType === "video") {
@ -192,7 +182,7 @@ export class tools extends plugin {
"http",
"https",
);
const path = `${this.defaultPath}${
const path = `${ this.defaultPath }${
this.e.group_id || this.e.user_id
}/temp.mp4`;
await this.downloadVideo(resUrl).then(() => {
@ -253,10 +243,10 @@ export class tools extends plugin {
} else {
url = urlRex.exec(url)[0];
}
let idVideo = await getIdVideo(url);
idVideo = idVideo.replace(/\//g, "");
let tiktokVideoId = await getIdVideo(url);
tiktokVideoId = tiktokVideoId.replace(/\//g, "");
// API链接
const API_URL = `https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id=${idVideo}&version_code=262&app_name=musical_ly&channel=App&device_id=null&os_version=14.4.2&device_platform=iphone&device_type=iPhone9`;
const API_URL = `https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id=${ tiktokVideoId }`;
await axios
.get(API_URL, {
@ -277,11 +267,11 @@ export class tools extends plugin {
})
.then(resp => {
const data = resp.data.aweme_list[0];
e.reply(`识别tiktok, ${data.desc}`);
e.reply(`识别tiktok, ${ data.desc }`);
this.downloadVideo(data.video.play_addr.url_list[0], true).then(video => {
e.reply(
segment.video(
`${this.defaultPath}${this.e.group_id || this.e.user_id}/temp.mp4`,
`${ this.defaultPath }${ this.e.group_id || this.e.user_id }/temp.mp4`,
),
);
});
@ -295,6 +285,7 @@ export class tools extends plugin {
this.biliCore(e);
});
}
async biliCore(e) {
const urlRex = /(?:https?:\/\/)?www\.bilibili\.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g;
@ -317,81 +308,68 @@ export class tools extends plugin {
if (matched) {
url = url.replace(matched[0], av2BV(Number(matched[2])));
}
// 动态
// 动态处理
if (url.includes("t.bilibili.com")) {
// 去除多余参数
if (url.includes("?")) {
url = url.substring(0, url.indexOf("?"));
}
const dynamicId = /[^/]+(?!.*\/)/.exec(url)[0];
getDynamic(dynamicId).then(async resp => {
if (resp.dynamicSrc.length > 0) {
e.reply(`识别:哔哩哔哩动态, ${resp.dynamicDesc}`);
let dynamicSrcMsg = [];
resp.dynamicSrc.forEach(item => {
dynamicSrcMsg.push({
message: segment.image(item),
nickname: e.sender.card || e.user_id,
user_id: e.user_id,
});
});
await this.reply(await Bot.makeForwardMsg(dynamicSrcMsg));
} else {
e.reply(`识别:哔哩哔哩动态, 但是失败!`);
}
});
url = this.biliDynamic(url, e);
return true;
}
// 视频信息获取例子http://api.bilibili.com/x/web-interface/view?bvid=BV1hY411m7cB
// 请求视频信息
const videoInfo = await getVideoInfo(url);
const { title, pic, desc, duration, dynamic, stat, aid, cid, pages } = videoInfo;
const { title, pic, desc, duration, dynamic, stat, bvid, aid, cid, owner, pages } = videoInfo;
// 视频信息
let { view, danmaku, reply, favorite, coin, share, like } = stat;
// 数据处理
const dataProcessing = data => {
return Number(data) >= TEN_THOUSAND ? (data / TEN_THOUSAND).toFixed(1) + "万" : data;
};
// 限制时长 & 考虑分页视频情况
const query = querystring.parse(url);
const curPage = query?.p || 0;
const curDuration = pages?.[curPage]?.duration || duration;
const isLimitDuration = curDuration > this.biliDuration
// 构造一个可扩展的Map
const dataProcessMap = {
"点赞": like,
"硬币": coin,
"收藏": favorite,
"分享": share,
"总播放量": view,
"弹幕数量": danmaku,
"评论": reply
};
// 格式化数据
const combineContent =
`\n点赞:${dataProcessing(like)} | 硬币:${dataProcessing(
coin,
)} | 收藏${dataProcessing(favorite)} | 分享${dataProcessing(share)}\n` +
`总播放量:${dataProcessing(view)} | 弹幕数量:${dataProcessing(
danmaku,
)} | 评论${dataProcessing(reply)}\n` +
`简介:${desc}`;
let biliInfo = [`识别:哔哩哔哩:${title}`, combineContent]
const combineContent = `\n${ formatBiliInfo(dataProcessMap) }\n简介:${ desc }`;
let biliInfo = [`识别:哔哩哔哩:${ title }`, combineContent]
// 只提取音乐处理
if (e.msg.includes("bili音乐")) {
return await this.biliMusic(url, e, biliInfo);
}
// 不提取音乐,正常处理
if (isLimitDuration) {
// 加入图片
biliInfo.unshift(segment.image(pic))
// 限制视频解析
const durationInMinutes = (curDuration / 60).toFixed(0);
biliInfo.push(`\n-----------------------限制说明-----------------------\n当前视频时长约:${durationInMinutes}分钟,\n大于管理员设置的最大时长 ${this.biliDuration / 60} 分钟!`)
biliInfo.push(`${RESTRICTION_DESCRIPTION}\n当前视频时长约:${ durationInMinutes }分钟,\n大于管理员设置的最大时长 ${ this.biliDuration / 60 } 分钟!`)
e.reply(biliInfo);
// 总结
const summary = await this.getBiliSummary(videoInfo);
const summary = await this.getBiliSummary(bvid, cid, owner.mid);
summary && e.reply(summary);
return true;
} else {
// 总结
const summary = await this.getBiliSummary(bvid, cid, owner.mid);
summary && biliInfo.push(`\n${summary}`)
//
e.reply(biliInfo);
}
// 创建文件,如果不存在
const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}/`;
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/`;
await mkdirIfNotExists(path);
// 下载文件
getDownloadUrl(url)
.then(data => {
this.downBili(`${path}temp`, data.videoUrl, data.audioUrl)
this.downBili(`${ path }temp`, data.videoUrl, data.audioUrl)
.then(_ => {
e.reply(segment.video(`${path}temp.mp4`));
e.reply(segment.video(`${ path }temp.mp4`));
})
.catch(err => {
logger.error(err);
@ -402,17 +380,95 @@ export class tools extends plugin {
logger.error(err);
e.reply("解析失败,请重试一下");
});
// 总结
const summary = await this.getBiliSummary(videoInfo);
summary && e.reply(summary);
return true;
}
async biliMusic(url, e, biliInfo) {
const { audioUrl, title } = await getAudioUrl(url);
e.reply(biliInfo)
e.reply(segment.record(audioUrl))
return true
}
// 发送哔哩哔哩动态的算法
biliDynamic(url, e) {
// 去除多余参数
if (url.includes("?")) {
url = url.substring(0, url.indexOf("?"));
}
const dynamicId = /[^/]+(?!.*\/)/.exec(url)[0];
getDynamic(dynamicId).then(async resp => {
if (resp.dynamicSrc.length > 0) {
e.reply(`识别:哔哩哔哩动态, ${ resp.dynamicDesc }`);
let dynamicSrcMsg = [];
resp.dynamicSrc.forEach(item => {
dynamicSrcMsg.push({
message: segment.image(item),
nickname: e.sender.card || e.user_id,
user_id: e.user_id,
});
});
await this.reply(await Bot.makeForwardMsg(dynamicSrcMsg));
} else {
e.reply(`识别:哔哩哔哩动态, 但是失败!`);
}
});
return url;
}
/**
* 哔哩哔哩总结
* @author zhiyu1998
* @param bvid 稿件
* @param cid 视频 cid
* @param up_mid UP主 mid
* @return {Promise<void>}
*/
async getBiliSummary(bvid, cid, up_mid) {
// 这个有点用,但不多
let wbi = "wts=1701546363&w_rid=1073871926b3ccd99bd790f0162af634"
if (!_.isEmpty(this.biliSessData)) {
wbi = await getWbi({ bvid, cid, up_mid }, this.biliSessData);
}
// 构造API
const summaryUrl = `${ BILI_SUMMARY }?${ wbi }`;
logger.info(summaryUrl)
// 构造结果https://api.bilibili.com/x/web-interface/view/conclusion/get?bvid=BV1L94y1H7CV&cid=1335073288&up_mid=297242063&wts=1701546363&w_rid=1073871926b3ccd99bd790f0162af634
return axios.get(summaryUrl)
.then(resp => {
const data = resp.data.data?.model_result;
// logger.info(data)
const summary = data?.summary;
const outline = data?.outline;
let resReply = "";
// 总体总结
if (summary) {
resReply = `摘要:${ summary }\n`
}
// 分段总结
if (outline) {
const specificTimeSummary = outline.map(item => {
const smallTitle = item.title;
const keyPoint = item?.part_outline;
// 时间点的总结
const specificContent = keyPoint.map(point => {
const { timestamp, content } = point
const specificTime = secondsToTime(timestamp)
return `${ specificTime } ${ content }\n`;
}).join("");
return `- ${ smallTitle }\n${ specificContent }\n`;
});
resReply += specificTimeSummary.join("");
}
return resReply;
})
}
// 百科
async wiki(e) {
const key = e.msg.replace(/#|百科|wiki/g, "").trim();
const url = `https://xiaoapi.cn/API/bk.php?m=json&type=sg&msg=${encodeURI(key)}`;
const bdUrl = `https://xiaoapi.cn/API/bk.php?m=json&type=bd&msg=${encodeURI(key)}`;
const url = `https://xiaoapi.cn/API/bk.php?m=json&type=sg&msg=${ encodeURI(key) }`;
const bdUrl = `https://xiaoapi.cn/API/bk.php?m=json&type=bd&msg=${ encodeURI(key) }`;
const bkRes = await Promise.all([
axios
.get(bdUrl, {
@ -440,8 +496,8 @@ export class tools extends plugin {
return res.map(item => {
return {
message: `
解释${_.get(item, "msg")}\n
详情${_.get(item, "more")}\n
解释${ _.get(item, "msg") }\n
详情${ _.get(item, "more") }\n
`,
nickname: e.sender.card || e.user_id,
user_id: e.user_id,
@ -473,8 +529,8 @@ export class tools extends plugin {
expansions: ["entities.mentions.username", "attachments.media_keys"],
})
.then(async resp => {
e.reply(`识别:小蓝鸟学习版,${resp.data.text}`);
const downloadPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
e.reply(`识别:小蓝鸟学习版,${ resp.data.text }`);
const downloadPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
// 创建文件夹(如果没有过这个群)
if (!fs.existsSync(downloadPath)) {
mkdirsSync(downloadPath);
@ -489,7 +545,7 @@ export class tools extends plugin {
// 视频
await this.downloadVideo(resp.includes.media[0].variants[0].url, true).then(
_ => {
e.reply(segment.video(`${downloadPath}/temp.mp4`));
e.reply(segment.video(`${ downloadPath }/temp.mp4`));
},
);
}
@ -524,21 +580,21 @@ export class tools extends plugin {
// acfun解析
async acfun(e) {
const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}/temp/`;
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }/temp/`;
await mkdirIfNotExists(path);
let inputMsg = e.msg;
// 适配手机分享https://m.acfun.cn/v/?ac=32838812&sid=d2b0991bd6ad9c09
if (inputMsg.includes("m.acfun.cn")) {
inputMsg = `https://www.acfun.cn/v/ac${/ac=([^&?]*)/.exec(inputMsg)[1]}`;
inputMsg = `https://www.acfun.cn/v/ac${ /ac=([^&?]*)/.exec(inputMsg)[1] }`;
}
parseUrl(inputMsg).then(res => {
e.reply(`识别:猴山,${res.videoName}`);
e.reply(`识别:猴山,${ res.videoName }`);
parseM3u8(res.urlM3u8s[res.urlM3u8s.length - 1]).then(res2 => {
downloadM3u8Videos(res2.m3u8FullUrls, path).then(_ => {
mergeAcFileToMp4(res2.tsNames, path, `${path}out.mp4`).then(_ => {
e.reply(segment.video(`${path}out.mp4`));
mergeAcFileToMp4(res2.tsNames, path, `${ path }out.mp4`).then(_ => {
e.reply(segment.video(`${ path }out.mp4`));
});
});
});
@ -550,9 +606,9 @@ export class tools extends plugin {
async redbook(e) {
// 正则说明匹配手机链接、匹配小程序、匹配PC链接
let msgUrl =
/(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec(
e.msg,
)?.[0]
/(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec(
e.msg,
)?.[0]
|| /(http:|https:)\/\/www\.xiaohongshu\.com\/discovery\/item\/(\w+)/.exec(
e.message[0].data,
)?.[0]
@ -574,9 +630,9 @@ export class tools extends plugin {
} else {
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.defaultPath }${ this.e.group_id || this.e.user_id }`;
// 获取信息
fetch(`https://www.xiaohongshu.com/discovery/item/${id}`, {
fetch(`https://www.xiaohongshu.com/discovery/item/${ id }`, {
headers: {
"user-agent":
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/110.0.0.0",
@ -589,7 +645,7 @@ export class tools extends plugin {
const res = JSON.parse(resJson.match(reg)[1]);
const noteData = res.noteData.data.noteData;
const { title, desc, type } = noteData;
e.reply(`识别:小红书, ${title}\n${desc}`);
e.reply(`识别:小红书, ${ title }\n${ desc }`);
let imgPromise = [];
if (type === "video") {
const url = noteData.video.url;
@ -661,9 +717,9 @@ export class tools extends plugin {
}
const rTempFileLen = await deleteFolderRecursive(this.toolsConfig.defaultPath)
e.reply(
`数据统计:\n`+
`- 当前清理了${dataDirectory}下总计:${dataClearFileLen} 个垃圾文件\n`+
`- 当前清理了${ this.toolsConfig.defaultPath}下文件夹:${rTempFileLen} 个群的所有临时文件`
`数据统计:\n` +
`- 当前清理了${ dataDirectory }下总计:${ dataClearFileLen } 个垃圾文件\n` +
`- 当前清理了${ this.toolsConfig.defaultPath }下文件夹:${ rTempFileLen } 个群的所有临时文件`
);
} catch (err) {
logger.error(err);
@ -677,10 +733,10 @@ export class tools extends plugin {
if (suffix.startsWith("reel")) {
suffix = suffix.replace("reel/", "p/");
}
const API = `https://imginn.com/${suffix}`;
logger.info(API);
const API = `https://imginn.com/${ suffix }`;
// logger.info(API);
let imgPromise = [];
const downloadPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
const downloadPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
// 简单封装图片下载
const downloadImg = (url, destination) => {
return new Promise((resolve, reject) => {
@ -708,13 +764,13 @@ export class tools extends plugin {
const desc = html.match(/(?<=content=").*?(?=\")/g)?.[2];
const images = html.match(/<div class=\"swiper-slide.*?\">/g);
if (!_.isNull(images)) {
e.reply(`识别Insta${desc || "暂无描述"}\n`);
e.reply(`识别Insta${ desc || "暂无描述" }\n`);
images.map((item, index) => {
const imgUrl = /(?<=data-src=").*?(?=")/
.exec(item)[0]
.replace(/#38/g, "")
.replace(/;/g, "");
imgPromise.push(downloadImg(imgUrl, `${downloadPath}/${index}.jpg`));
imgPromise.push(downloadImg(imgUrl, `${ downloadPath }/${ index }.jpg`));
});
}
// TODO 视频会出bug暂时不做
@ -755,18 +811,18 @@ export class tools extends plugin {
/(?=mvId).*?(?=&)/.exec(e.msg.trim())?.[0].replace("mvId=", "");
const { name, album, artist, albumPic120, categorys } = await getBodianMusicInfo(id);
e.reply([
`识别:波点音乐,${name}-${album}-${artist}\n标签:${categorys
`识别:波点音乐,${ name }-${ album }-${ artist }\n标签:${ categorys
.map(item => item.name)
.join(" | ")}`,
.join(" | ") }`,
segment.image(albumPic120),
]);
if (e.msg.includes("musicId")) {
const path = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
await getBodianAudio(id, path).then(_ => {
Bot.acquireGfs(e.group_id).upload(
fs.readFileSync(path + "/temp.mp3"),
"/",
`${name}-${album}-${artist}.mp3`,
`${ name }-${ album }-${ artist }.mp3`,
);
});
} else if (e.msg.includes("mvId")) {
@ -800,7 +856,7 @@ export class tools extends plugin {
return
}
// 提取视频
const videoUrl = `https://www.kuaishou.com/short-video/${video_id}`;
const videoUrl = `https://www.kuaishou.com/short-video/${ video_id }`;
// 发送GET请求
const response = await axios.get(videoUrl, {
@ -870,32 +926,10 @@ export class tools extends plugin {
),
),
]).then(data => {
return mergeFileToMp4(data[0].fullFileName, data[1].fullFileName, `${title}.mp4`);
return mergeFileToMp4(data[0].fullFileName, data[1].fullFileName, `${ title }.mp4`);
});
}
/**
* 哔哩哔哩总结
* @returns Promise{string}
* @param videoInfo
*/
async getBiliSummary(videoInfo) {
if (this.biliSessData && this.openaiAccessToken) {
try {
const prompt = await getBiliGptInputText(videoInfo, this.biliSessData);
const response = await this.chatGptClient.sendMessage(prompt);
// 暂时不设计上下文
return response.response
} catch (err) {
logger.error("总结失败,可能是没有弹幕或者网络问题!\n", err);
return ""
}
} else {
return ""
}
}
/**
* 下载一张网络图片(自动以url的最后一个为名字)
* @param img
@ -908,7 +942,7 @@ export class tools extends plugin {
if (fileName === "") {
fileName = img.split("/").pop();
}
const filepath = `${dir}/${fileName}`;
const filepath = `${ dir }/${ fileName }`;
await mkdirIfNotExists(dir)
const writer = fs.createWriteStream(filepath);
const axiosConfig = {
@ -976,8 +1010,8 @@ export class tools extends plugin {
* @returns {{groupPath: string, target: string}}
*/
getGroupPathAndTarget() {
const groupPath = `${this.defaultPath}${this.e.group_id || this.e.user_id}`;
const target = `${groupPath}/temp.mp4`;
const groupPath = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`;
const target = `${ groupPath }/temp.mp4`;
return { groupPath, target };
}
@ -1012,7 +1046,7 @@ export class tools extends plugin {
await checkAndRemoveFile(target);
const res = await axios.get(url, axiosConfig);
logger.mark(`开始下载: ${url}`);
logger.mark(`开始下载: ${ url }`);
const writer = fs.createWriteStream(target);
res.data.pipe(writer);

View File

@ -38,6 +38,9 @@
- icon: bilibili
title: "bilibili/b23"
desc: 哔哩哔哩分享实时下载
- icon: bilimusic
title: "bili音乐+链接"
desc: 哔哩哔哩音乐分享实时下载
- icon: 推特
title: "小蓝鸟"
desc: 推特学习版分享实时下载

View File

@ -8,8 +8,4 @@ translateSecret: '' # 百度翻译密匙
biliSessData: '' # 哔哩哔哩的SESSDATA
biliDuration: 480 # 哔哩哔哩限制的最大视频时长默认8分钟单位
openaiAccessToken: '' # 通过获取https://chat.openai.com/api/auth/session
openaiApiKey: '' # sk...
openaiModel: 'gpt-3.5-turbo' # 目前gpt-3.5-turbo效果比较好廉价适合群友
douyinCookie: '' # douyin's cookie, 格式odin_tt=xxx;sessionid_ss=xxx;ttwid=xxx;passport_csrf_token=xxx;msToken=xxx;

View File

@ -1,11 +1,11 @@
- {
version: 1.1.3,
version: 1.2.0,
data:
[
新增<span class="cmd">哔哩哔哩官方AI总结</span>功能,
新增<span class="cmd">哔哩哔哩音乐提取</span>功能,
新增<span class="cmd">快手解析</span>功能,
新增<span class="cmd">竹白百科</span>功能,
重构<span class="cmd">翻译</span>功能,
适配<span class="cmd">锅巴</span>插件,方便查看和修改配置,
支持<span class="cmd">锅巴</span>插件,方便查看和修改配置,
添加<span class="cmd">#R帮助</span>获取插件帮助,
添加<span class="cmd">#R版本</span>获取插件版本,
],

View File

@ -79,7 +79,7 @@ export function supportGuoba() {
field: "tools.biliSessData",
label: "哔哩哔哩SESSDATA",
bottomHelpMessage:
"如何获取具体参考我的文档说明https://gitee.com/kyrzy0416/rconsole-plugin",
"如何获取具体参考我的文档说明https://gitee.com/kyrzy0416/rconsole-plugin#Q&A",
component: "Input",
required: false,
componentProps: {
@ -97,17 +97,6 @@ export function supportGuoba() {
placeholder: "请输入哔哩哔哩的视频最大限制时长默认15分钟",
},
},
{
field: "tools.openaiAccessToken",
label: "OpenAI的AccessToken",
bottomHelpMessage:
"ey....先登录https://chat.openai.com/再复制里面的accessTokenhttps://chat.openai.com/api/auth/session",
component: "Input",
required: false,
componentProps: {
placeholder: "请输入OpenAI的AccessTokeney.....",
},
},
{
field: "tools.douyinCookie",
label: "抖音的Cookie",

View File

@ -1,11 +1,12 @@
import fetch from "node-fetch";
import axios from "axios";
import { BILI_VIDEO_INFO } from "../constants/bili.js";
async function getVideoInfo(url) {
const baseVideoInfo = "http://api.bilibili.com/x/web-interface/view";
// const baseVideoInfo = "http://api.bilibili.com/x/web-interface/view";
const videoId = /video\/[^\?\/ ]+/.exec(url)[0].split("/")[1];
// 获取视频信息,然后发送
return fetch(`${baseVideoInfo}?bvid=${videoId}`)
return fetch(`${BILI_VIDEO_INFO}?bvid=${videoId}`)
.then(async resp => {
const respJson = await resp.json();
const respData = respJson.data;
@ -16,8 +17,10 @@ async function getVideoInfo(url) {
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,
};
});

View File

@ -1,208 +0,0 @@
/**
* 获取gpt提取视频信息的文字
* @param videoInfo
* @param biliSessData
* @param shouldShowTimestamp 是否在每段字幕前面加入时间标识
* @returns {Promise<string>}
*/
export async function getBiliGptInputText(videoInfo, biliSessData, shouldShowTimestamp = false) {
const headers = {
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
Host: "api.bilibili.com",
Cookie: `SESSDATA=${biliSessData}`,
};
const commonConfig = {
method: "GET",
cache: "no-cache",
headers,
referrerPolicy: "no-referrer",
};
const { title, desc, dynamic, aid, cid } = videoInfo;
// https://api.bilibili.com/x/player/v2?aid=438937138&cid=1066979272
const resp = await fetch(
`https://api.bilibili.com/x/player/v2?aid=${aid}&cid=${cid}`,
commonConfig,
);
const subtitles = (await resp.json()).data.subtitle.subtitles;
const subtitlesUrl = subtitles?.subtitle_url?.startsWith("//")
? `https:${subtitles?.subtitle_url}`
: subtitles?.subtitle_url;
let inputText = "";
logger.mark(subtitlesUrl);
if (subtitlesUrl !== undefined) {
const res = await fetch(subtitlesUrl);
const subtitlesData = (await res.json()).body;
const subtitleTimestamp = reduceBilibiliSubtitleTimestamp(
subtitlesData,
shouldShowTimestamp,
);
inputText = getSmallSizeTranscripts(subtitleTimestamp, subtitleTimestamp);
} else {
inputText = `${desc} ${dynamic}`;
}
const videoConfig = {
showEmoji: true,
};
return shouldShowTimestamp
? getUserSubtitleWithTimestampPrompt(title, inputText, videoConfig)
: getUserSubtitlePrompt(title, inputText, videoConfig);
}
// 以下拼接算法来自https://github.com/JimmyLv/BibiGPT
function reduceBilibiliSubtitleTimestamp(subtitles = [], shouldShowTimestamp) {
return reduceSubtitleTimestamp(
subtitles,
i => i.from,
i => i.content,
shouldShowTimestamp,
);
}
function reduceSubtitleTimestamp(subtitles, getStart, getText, shouldShowTimestamp) {
// 把字幕数组总共分成 20 组
const TOTAL_GROUP_COUNT = 30;
// 如果字幕不够多就每7句话合并一下
const MINIMUM_COUNT_ONE_GROUP = 7;
const eachGroupCount =
subtitles.length > TOTAL_GROUP_COUNT
? subtitles.length / TOTAL_GROUP_COUNT
: MINIMUM_COUNT_ONE_GROUP;
return subtitles.reduce((accumulator, current, index) => {
// 计算当前元素在哪一组
const groupIndex = Math.floor(index / MINIMUM_COUNT_ONE_GROUP);
// 如果是当前组的第一个元素,初始化这一组的字符串
if (!accumulator[groupIndex]) {
accumulator[groupIndex] = {
// 5.88 -> 5.9
// text: current.start.toFixed() + ": ",
index: groupIndex,
s: getStart(current),
text: shouldShowTimestamp ? getStart(current) + " - " : "",
};
}
// 将当前元素添加到当前组的字符串末尾
accumulator[groupIndex].text = accumulator[groupIndex].text + getText(current) + " ";
return accumulator;
}, []);
}
function getSmallSizeTranscripts(newTextData, oldTextData, byteLimit = 6200) {
const text = newTextData
.sort((a, b) => a.index - b.index)
.map(t => t.text)
.join(" ");
const byteLength = getByteLength(text);
if (byteLength > byteLimit) {
const filtedData = filterHalfRandomly(newTextData);
return getSmallSizeTranscripts(filtedData, oldTextData, byteLimit);
}
let resultData = newTextData.slice();
let resultText = text;
let lastByteLength = byteLength;
for (let i = 0; i < oldTextData.length; i++) {
const obj = oldTextData[i];
if (itemInIt(newTextData, obj.text)) {
continue;
}
const nextTextByteLength = getByteLength(obj.text);
const isOverLimit = lastByteLength + nextTextByteLength > byteLimit;
if (isOverLimit) {
const overRate = (lastByteLength + nextTextByteLength - byteLimit) / nextTextByteLength;
const chunkedText = obj.text.substring(0, Math.floor(obj.text.length * overRate));
resultData.push({ text: chunkedText, index: obj.index });
} else {
resultData.push(obj);
}
resultText = resultData
.sort((a, b) => a.index - b.index)
.map(t => t.text)
.join(" ");
lastByteLength = getByteLength(resultText);
}
return resultText;
}
function filterHalfRandomly(arr) {
const filteredArr = [];
const halfLength = Math.floor(arr.length / 2);
const indicesToFilter = new Set();
// 随机生成要过滤掉的元素的下标
while (indicesToFilter.size < halfLength) {
const index = Math.floor(Math.random() * arr.length);
if (!indicesToFilter.has(index)) {
indicesToFilter.add(index);
}
}
// 过滤掉要过滤的元素
for (let i = 0; i < arr.length; i++) {
if (!indicesToFilter.has(i)) {
filteredArr.push(arr[i]);
}
}
return filteredArr;
}
function getByteLength(text) {
return unescape(encodeURIComponent(text)).length;
}
function itemInIt(textData, text) {
return textData.find(t => t.text === text) !== undefined;
}
function getUserSubtitlePrompt(title, transcript, videoConfig) {
const videoTitle = title?.replace(/\n+/g, " ").trim();
const videoTranscript = limitTranscriptByteLength(transcript).replace(/\n+/g, " ").trim();
const language = "zh-CN";
const sentenceCount = videoConfig.sentenceNumber || 7;
const emojiTemplateText = videoConfig.showEmoji ? "[Emoji] " : "";
const emojiDescriptionText = videoConfig.showEmoji
? "Choose an appropriate emoji for each bullet point. "
: "";
const shouldShowAsOutline = Number(videoConfig.outlineLevel) > 1;
const wordsCount = videoConfig.detailLevel ? (Number(videoConfig.detailLevel) / 100) * 2 : 15;
const outlineTemplateText = shouldShowAsOutline ? `\n - Child points` : "";
const outlineDescriptionText = shouldShowAsOutline
? `Use the outline list, which can have a hierarchical structure of up to ${videoConfig.outlineLevel} levels. `
: "";
const prompt = `Your output should use the following template:\n## Summary\n## Highlights\n- ${emojiTemplateText}Bulletpoint${outlineTemplateText}\n\nYour task is to summarise the text I have given you in up to ${sentenceCount} concise bullet points, starting with a short highlight, each bullet point is at least ${wordsCount} words. ${outlineDescriptionText}${emojiDescriptionText}Use the text above: {{Title}} {{Transcript}}.\n\nReply in ${language} Language.`;
return `Title: "${videoTitle}"\nTranscript: "${videoTranscript}"\n\nInstructions: ${prompt}`;
}
export function getUserSubtitleWithTimestampPrompt(title, transcript, videoConfig) {
const videoTitle = title?.replace(/\n+/g, " ").trim();
const videoTranscript = limitTranscriptByteLength(transcript).replace(/\n+/g, " ").trim();
const language = "zh-CN";
const sentenceCount = videoConfig.sentenceNumber || 7;
const emojiTemplateText = videoConfig.showEmoji ? "[Emoji] " : "";
const wordsCount = videoConfig.detailLevel ? (Number(videoConfig.detailLevel) / 100) * 2 : 15;
const promptWithTimestamp = `Act as the author and provide exactly ${sentenceCount} bullet points for the text transcript given in the format [seconds] - [text] \nMake sure that:\n - Please start by summarizing the whole video in one short sentence\n - Then, please summarize with each bullet_point is at least ${wordsCount} words\n - each bullet_point start with \"- \" or a number or a bullet point symbol\n - each bullet_point should has the start timestamp, use this template: - seconds - ${emojiTemplateText}[bullet_point]\n - there may be typos in the subtitles, please correct them\n - Reply all in ${language} Language.`;
const videoTranscripts = limitTranscriptByteLength(JSON.stringify(videoTranscript));
return `Title: ${videoTitle}\nTranscript: ${videoTranscripts}\n\nInstructions: ${promptWithTimestamp}`;
}
function limitTranscriptByteLength(str, byteLimit = 6200) {
const utf8str = unescape(encodeURIComponent(str));
const byteLength = utf8str.length;
if (byteLength > byteLimit) {
const ratio = byteLimit / byteLength;
const newStr = str.substring(0, Math.floor(str.length * ratio));
return newStr;
}
return str;
}

View File

@ -65,6 +65,33 @@ async function getDownloadUrl (url) {
});
}
async function getAudioUrl (url) {
return axios
.get(url, {
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(({ 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;
@ -95,4 +122,4 @@ async function mergeFileToMp4 (vFullFileName, aFullFileName, outputFileName, sho
}
}
export { downloadBFile, getDownloadUrl, mergeFileToMp4 }
export { downloadBFile, getDownloadUrl, getAudioUrl, mergeFileToMp4 }

View File

@ -4,6 +4,7 @@ import axios from "axios";
import fs from "node:fs";
import fetch from "node-fetch";
import { mkdirIfNotExists } from "./file.js";
import {TEN_THOUSAND} from "../constants/constant.js";
/**
* 请求模板
@ -163,4 +164,41 @@ async function downloadMp3(mp3Url, path, redirect = "manual") {
});
}
export { jFeatch, autoTask, retry, getIdVideo, generateRandomStr, downloadMp3 };
/**
* 千位数的数据处理
* @param data
* @return {string|*}
*/
const dataProcessing = data => {
return Number(data) >= TEN_THOUSAND ? (data / TEN_THOUSAND).toFixed(1) + "万" : data;
};
/**
* 哔哩哔哩解析的数据处理
* @param data
* @return {string}
*/
function formatBiliInfo(data) {
return Object.keys(data).map(key => `${key}${dataProcessing(data[key])}`).join(' | ');
}
/**
* 数字转换成具体时间
* @param seconds
* @return {string}
*/
function secondsToTime(seconds) {
const pad = (num, size) => num.toString().padStart(size, '0');
let hours = Math.floor(seconds / 3600);
let minutes = Math.floor((seconds % 3600) / 60);
let secs = seconds % 60;
// 如果你只需要分钟和秒钟,你可以返回下面这行:
// return `${pad(minutes, 2)}:${pad(secs, 2)}`;
// 完整的 HH:MM:SS 格式
return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(secs, 2)}`;
}
export { jFeatch, autoTask, retry, getIdVideo, generateRandomStr, downloadMp3, dataProcessing, formatBiliInfo, secondsToTime };

View File

@ -1,41 +0,0 @@
/**
* 用于百度翻译的常量控制
*
* @type {{: string, : string, : string, : string}}
*/
export const transMap = { : "zh", : "jp", : "wyw", : "en", : "ru", : "kr" };
/**
* 用于腾讯交互式翻译的常量控制
*
* @type {{: string, : string, : string, : string, : string}}
*/
export const tencentTransMap = { : "zh", : "ja", : "ko", : "en", : "ru" };
/**
* 用于腾讯交互式翻译的常量控制
*
* @type {{: string, : string, : string, : string, : string}}
*/
export const googleTransMap = { : "zh-CN", : "jp", : "ko", : "en", : "ru" };
/**
* 以下为抖音/TikTok类型代码
*
* @type {{"0": string, "55": string, "2": string, "68": string, "58": string, "4": string, "61": string, "51": string, "150": string}}
*/
export const douyinTypeMap = {
2: "image",
4: "video",
68: "image",
0: "video",
51: "video",
55: "video",
58: "video",
61: "video",
150: "image",
};
export const TEN_THOUSAND = 10000;
export const CAT_LIMIT = 10;
export const XHS_CK = 'eGhzVHJhY2tlcklkPTczODhhYmY2LTI0MDgtNGU5YS04MTUyLTE0MGVhOGY1MTQ5ZjsgeGhzVHJhY2tlcklkLnNpZz1UcGUxTkNaX3B3UkFYdG01SVJmVEs0SWUxM0xBaGZuNmNZU2N4Vi1JYWxFOyBhMT0xODY2ZDkwMDM0NmI2NmppcjMzcGpxZ2MwM3JvcG1mczAydXMxdWNoeDEwMDAwMTM1MDUzOyB3ZWJJZD1mMTNkOGJkYjhiZGM3ZGE0MzY0NjA4NWJjYzQ1MDQ1YTsgZ2lkPXlZS0tmajg4SzA4MnlZS0tmajg4cUo3UzRLREtLVjNGcXFVVjd4Q0FrUzhxRk15OGxVNmlNeTg4OHlxMjgycThmMlk0UzAySjsgZ2lkLnNpZ249YlpzcFFzSUxEUmN5akZLQmN2L1FMWVhkU3lvPTsgd2ViX3Nlc3Npb249MDMwMDM3YTRjMDQyYjE1ZTVjMTg4OTUwOGIyNDRhZDExM2UwNTM7IHhoc1RyYWNrZXI9dXJsPW5vdGVEZXRhaWwmeGhzc2hhcmU9V2VpeGluU2Vzc2lvbjsgeGhzVHJhY2tlci5zaWc9YzdmcDVRclk2SGNvVERhUzluX2N3Z2RCRHh2MFZmWnpSU1NTcnlzbG5lQTsgZXh0cmFfZXhwX2lkcz1oNV8yMzAyMDExX29yaWdpbixoNV8xMjA4X2NsdCxoNV8xMTMwX2NsdCxpb3Nfd3hfbGF1bmNoX29wZW5fYXBwX2V4cCxoNV92aWRlb191aV9leHAzLHd4X2xhdW5jaF9vcGVuX2FwcF9kdXJhdGlvbl9vcmlnaW4scXVlc19jbHQyOyBleHRyYV9leHBfaWRzLnNpZz1DVUdrR3NYT3lBZmpVSXkyVGo3SjN4YmRNakFfSnpoR1JkYWd6cVlkbmJnOyB3ZWJCdWlsZD0xLjEuMjE7IHhzZWNhcHBpZD14aHMtcGMtd2ViOyB3ZWJzZWN0aWdhPTU5ZDNlZjFlNjBjNGFhMzdhN2RmM2MyMzQ2N2JkNDZkN2YxZGEwYjE5MThjZjMzNWVlN2YyZTllNTJhYzA0Y2Y7IHNlY19wb2lzb25faWQ9MTI0OTE1NWQtOWU5ZS00MzkyLTg2NTgtNTA1Yzc0YTUzMTM1'

View File

@ -1,4 +1,4 @@
import {transMap, tencentTransMap, googleTransMap} from "./constant.js";
import {transMap, tencentTransMap, googleTransMap} from "../constants/constant.js";
import md5 from "md5";
import fetch from "node-fetch";
import HttpProxyAgent from "https-proxy-agent";