mirror of
https://github.com/Jerryplusy/rc-plugin.git
synced 2025-10-14 08:09:19 +00:00
✨ feat: V1.6.0 油管解析重构 & 修复tiktok
1. 加入梯子检测,提升R插件的健壮性 2. 油管解析大重构,稳定性大大提高 3. 修复tiktok出现的问题
This commit is contained in:
parent
1791f82bf1
commit
ad6b29cf82
17
README.md
17
README.md
@ -63,18 +63,6 @@ sudo apt-get install ffmpeg
|
||||
# 其他linux参考(群友推荐):https://gitee.com/baihu433/ffmpeg
|
||||
# Windows 参考:https://www.jianshu.com/p/5015a477de3c
|
||||
````
|
||||
`油管解析`需要 `yt-dlp` 的依赖才能完成解析(三选一):
|
||||
```shell
|
||||
# 三选一
|
||||
# ubuntu (国内 or 国外,且安装了snap)
|
||||
snap install yt-dlp
|
||||
# debian 海外
|
||||
curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o ~/.local/bin/yt-dlp
|
||||
chmod a+rx ~/.local/bin/yt-dlp
|
||||
# debian 国内
|
||||
curl -L https://ghproxy.net/https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o ~/.local/bin/yt-dlp
|
||||
chmod a+rx ~/.local/bin/yt-dlp
|
||||
```
|
||||
|
||||
4. 【可选】小程序解析适配了:
|
||||
* 喵崽:[Yoimiya / Miao-Yunzai](https://gitee.com/yoimiya-kokomi/Miao-Yunzai)
|
||||
@ -186,11 +174,6 @@ git clone -b 1.5.1 https://gitee.com/kyrzy0416/rconsole-plugin.git
|
||||
<img src="https://contrib.rocks/image?repo=zhiyu1998/rconsole-plugin&max=1000" />
|
||||
</a>
|
||||
|
||||
🌸感谢以下框架的开源🌸:
|
||||
|
||||
油管解析参考了:
|
||||
- [yt-dlp:A youtube-dl fork with additional features and fixes](https://github.com/yt-dlp/yt-dlp)
|
||||
|
||||
## ☕ 请我喝一杯瑞幸咖啡
|
||||
如果你觉得插件能帮助到你增进好友关系,那么你可以在有条件的情况下[请我喝一杯瑞幸咖啡](https://afdian.net/a/zhiyu1998),这是我开源这个插件的最大动力!
|
||||
感谢以下朋友的支持!(排名不分多少)
|
||||
|
184
apps/tools.js
184
apps/tools.js
@ -31,9 +31,9 @@ import {
|
||||
containsChinese,
|
||||
downloadImg,
|
||||
downloadMp3,
|
||||
formatBiliInfo,
|
||||
formatBiliInfo, formatSeconds,
|
||||
getIdVideo,
|
||||
secondsToTime, truncateString
|
||||
secondsToTime, testProxy, truncateString
|
||||
} from "../utils/common.js";
|
||||
import config from "../model/index.js";
|
||||
import Translate from "../utils/trans-strategy.js";
|
||||
@ -284,25 +284,46 @@ export class tools extends plugin {
|
||||
async tiktok(e) {
|
||||
// 判断海外
|
||||
const isOversea = await this.isOverseasServer();
|
||||
// 如果不是海外用户且没有梯子直接返回
|
||||
if (!isOversea && await testProxy()) {
|
||||
e.reply("检测到没有梯子,无法解析TikTok");
|
||||
return false;
|
||||
}
|
||||
// 处理链接
|
||||
let url = await processTikTokUrl(e.msg.trim(), isOversea);
|
||||
// 处理ID
|
||||
let tiktokVideoId = await getIdVideo(url);
|
||||
tiktokVideoId = tiktokVideoId.replace(/\//g, "");
|
||||
// API链接
|
||||
const API_URL = TIKTOK_INFO.replace("{}", tiktokVideoId);
|
||||
await fetch(API_URL, {
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
|
||||
"Content-Type": "application/json",
|
||||
"Accept-Encoding": "gzip,deflate,compress",
|
||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
|
||||
},
|
||||
// redirect: "follow",
|
||||
follow: 10,
|
||||
timeout: 10000,
|
||||
agent: isOversea ? '' : new HttpProxyAgent(this.myProxy),
|
||||
}
|
||||
// 如果不是海外,则使用代理
|
||||
if (!isOversea) {
|
||||
config.httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: new HttpProxyAgent(this.myProxy),
|
||||
});
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
"iid": "7318518857994389254",
|
||||
"device_id": "7318517321748022790",
|
||||
"channel": "googleplay",
|
||||
"app_name": "musical_ly",
|
||||
"version_code": "300904",
|
||||
"device_platform": "android",
|
||||
"device_type": "ASUS_Z01QD",
|
||||
"os_version": "9",
|
||||
"aweme_id": tiktokVideoId
|
||||
})
|
||||
console.log(`${TIKTOK_INFO}?${params.toString()}`)
|
||||
await fetch(`${TIKTOK_INFO}?${params.toString()}`, config)
|
||||
.then(async resp => {
|
||||
const respJson = await resp.json();
|
||||
const data = respJson.aweme_list[0];
|
||||
@ -584,13 +605,21 @@ export class tools extends plugin {
|
||||
|
||||
// 使用现有api解析小蓝鸟
|
||||
async twitter_x(e) {
|
||||
// 判断海外
|
||||
const isOversea = await this.isOverseasServer();
|
||||
// 如果不是海外用户且没有梯子直接返回
|
||||
if (!isOversea && await testProxy()) {
|
||||
e.reply("检测到没有梯子,无法解析TikTok");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配置参数及解析
|
||||
const reg = /https?:\/\/x.com\/[0-9-a-zA-Z_]{1,20}\/status\/([0-9]*)/;
|
||||
const twitterUrl = reg.exec(e.msg)[0];
|
||||
// 提取视频
|
||||
const videoUrl = GENERAL_REQ_LINK.link.replace("{}", twitterUrl);
|
||||
e.reply("识别:小蓝鸟");
|
||||
axios.get(videoUrl, {
|
||||
const config = {
|
||||
headers: {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
@ -606,7 +635,17 @@ export class tools extends plugin {
|
||||
|
||||
},
|
||||
timeout: 10000 // 设置超时时间
|
||||
}).then(resp => {
|
||||
}
|
||||
// 如果不是海外,则使用代理
|
||||
if (!isOversea) {
|
||||
config.httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: this.proxyAddr,
|
||||
port: this.proxyPort
|
||||
},
|
||||
});
|
||||
}
|
||||
axios.get(videoUrl, config).then(resp => {
|
||||
const url = resp.data.data?.url;
|
||||
if (url && (url.endsWith(".jpg") || url.endsWith(".png"))) {
|
||||
e.reply(segment.image(url));
|
||||
@ -1027,94 +1066,55 @@ export class tools extends plugin {
|
||||
async y2b(e) {
|
||||
const urlRex = /(?:https?:\/\/)?(www\.)?youtube\.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
|
||||
let url = urlRex.exec(e.msg)[0];
|
||||
// 获取url查询参数
|
||||
const query = querystring.parse(url.split("?")[1]);
|
||||
let p = query?.p || '0';
|
||||
let v = query?.v || url.match(/shorts\/([A-Za-z0-9_-]+)/)[1];
|
||||
// 判断是否是海外服务器,默认为false
|
||||
const isProxy = !(await this.isOverseasServer());
|
||||
// 判断海外
|
||||
const isOversea = await this.isOverseasServer();
|
||||
// 如果不是海外用户且没有梯子直接返回
|
||||
if (!isOversea && await testProxy()) {
|
||||
e.reply("检测到没有梯子,无法解析TikTok");
|
||||
return false;
|
||||
}
|
||||
|
||||
let audios = [], videos = [];
|
||||
let bestAudio = {}, bestVideo = {};
|
||||
|
||||
let rs = { title: '', thumbnail: '', formats: [] };
|
||||
try {
|
||||
let cmd = `yt-dlp --print-json --skip-download ${ this.y2bCk !== undefined ? `--cookies ${ this.y2bCk }` : '' } '${ url }' ${ isProxy ? `--proxy ${ this.proxyAddr }:${ this.proxyPort }` : '' } 2> /dev/null`
|
||||
logger.mark('解析视频, 命令:', cmd);
|
||||
rs = child_process.execSync(cmd).toString();
|
||||
try {
|
||||
rs = JSON.parse(rs);
|
||||
} catch (error) {
|
||||
let cmd = `yt-dlp --print-json --skip-download ${ this.y2bCk !== undefined ? `--cookies ${ this.y2bCk }` : '' } '${ url }?p=1' ${ isProxy ? `--proxy ${ this.proxyAddr }:${ this.proxyPort }` : '' } 2> /dev/null`;
|
||||
logger.mark('尝试分P, 命令:', cmd);
|
||||
rs = child_process.execSync(cmd).toString();
|
||||
rs = JSON.parse(rs);
|
||||
p = '1';
|
||||
// url = `${msg.url}?p=1`;
|
||||
}
|
||||
if (!containsChinese(rs.title)) {
|
||||
// 启用翻译引擎翻译不是中文的标题
|
||||
const transedTitle = await this.translateEngine.translate(rs.title, '中');
|
||||
// const transedDescription = await this.translateEngine.translate(rs.description, '中');
|
||||
e.reply(`识别:油管,
|
||||
${ rs.title.trim() }\n
|
||||
${ DIVIDING_LINE.replace("{}", "R插件翻译引擎服务") }\n
|
||||
${ transedTitle }\n
|
||||
${ rs.description }
|
||||
`);
|
||||
} else {
|
||||
e.reply(`识别:油管,${ rs.title }`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error.toString());
|
||||
e.reply("解析失败")
|
||||
return;
|
||||
// Perform the HTTP GET request
|
||||
const formData = {
|
||||
"link": url,
|
||||
"from": "ytbsaver"
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(formData).forEach(key => params.append(key, formData[key]));
|
||||
|
||||
// 格式化
|
||||
rs.formats.forEach(it => {
|
||||
let length = (it.filesize_approx ? '≈' : '') + ((it.filesize || it.filesize_approx || 0) / 1024 / 1024).toFixed(2);
|
||||
if (it.audio_ext != 'none') {
|
||||
audios.push(getAudio(it.format_id, it.ext, (it.abr || 0).toFixed(0), it.format_note || it.format || '', length));
|
||||
} else if (it.video_ext != 'none') {
|
||||
videos.push(getVideo(it.format_id, it.ext, it.resolution, it.height, (it.vbr || 0).toFixed(0), it.format_note || it.format || '', length));
|
||||
const config = {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36",
|
||||
"origin": "https://www.ytbsaver.com"
|
||||
},
|
||||
}
|
||||
// 如果不是海外,则使用代理
|
||||
if (!isOversea) {
|
||||
config.httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: this.proxyAddr,
|
||||
port: this.proxyPort
|
||||
},
|
||||
});
|
||||
|
||||
// 寻找最佳的分辨率
|
||||
// bestAudio = Array.from(audios).sort((a, b) => a.rate - b.rate)[audios.length - 1];
|
||||
// bestVideo = Array.from(videos).sort((a, b) => a.rate - b.rate)[videos.length - 1];
|
||||
|
||||
// 较为有性能的分辨率
|
||||
bestVideo = Array.from(videos).find(item => item.scale.includes("720") || item.scale.includes("360"));
|
||||
bestAudio = Array.from(audios).find(item => item.format === 'm4a');
|
||||
// logger.mark({
|
||||
// bestVideo,
|
||||
// bestAudio
|
||||
// })
|
||||
|
||||
// 格式化yt-dlp的请求
|
||||
const format = `${ bestVideo.id }x${ bestAudio.id }`
|
||||
// 下载地址格式化
|
||||
const path = `${ v }${ p ? `/p${ p }` : '' }`;
|
||||
const fullpath = `${ this.getCurDownloadPath(e) }/${ path }`;
|
||||
// 创建下载文件夹
|
||||
await mkdirIfNotExists(fullpath);
|
||||
// yt-dlp下载
|
||||
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', '+') } ` +
|
||||
`-o '${ fullpath }/${ v }.%(ext)s' ${ isProxy ? `--proxy ${ this.proxyAddr }:${ this.proxyPort }` : '' } -k --write-info-json`;
|
||||
logger.mark(cmd)
|
||||
try {
|
||||
await child_process.execSync(cmd);
|
||||
e.reply(segment.video(`${ fullpath }/${ v }.mp4`))
|
||||
// 清理文件
|
||||
await deleteFolderRecursive(`${ fullpath.split('\/').slice(0, -2).join('/') }`);
|
||||
} catch (error) {
|
||||
logger.error(error.toString());
|
||||
e.reply("y2b下载失败");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await axios.post("https://api.ytbvideoly.com/api/thirdvideo/parse", params.toString(), config);
|
||||
|
||||
const {title, /*thumbnail,*/ duration, formats} = response.data.data;
|
||||
e.reply(`识别:油管,${title}\n时长:${formatSeconds(duration)}`);
|
||||
if (formats.length > 0) {
|
||||
// 大概率是720p
|
||||
const videoUrl = formats?.[formats.length - 1].url;
|
||||
this.downloadVideo(videoUrl).then(path => {
|
||||
e.reply(segment.video(path + "/temp.mp4"));
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error; // Rethrow the error so it can be handled by the caller
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 米游社
|
||||
|
@ -1,5 +1,5 @@
|
||||
- {
|
||||
version: 1.5.13,
|
||||
version: 1.6.0,
|
||||
data:
|
||||
[
|
||||
新增<span class="cmd">微视解析</span>功能,
|
||||
|
@ -63,7 +63,7 @@ export const DY_INFO = "https://www.douyin.com/aweme/v1/web/aweme/detail/?device
|
||||
* Tiktok API
|
||||
* @type {string}
|
||||
*/
|
||||
export const TIKTOK_INFO = "https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/feed/?aweme_id={}"
|
||||
export const TIKTOK_INFO = "https://api22-normal-c-alisg.tiktokv.com/aweme/v1/feed/"
|
||||
|
||||
/**
|
||||
* X API
|
||||
|
@ -1,6 +1,7 @@
|
||||
import schedule from "node-schedule";
|
||||
import common from "../../../lib/common/common.js";
|
||||
import axios from "axios";
|
||||
import tunnel from "tunnel";
|
||||
import fs from "node:fs";
|
||||
import fetch from "node-fetch";
|
||||
import { mkdirIfNotExists } from "./file.js";
|
||||
@ -299,3 +300,38 @@ export function truncateString(inputString, maxLength = 50) {
|
||||
return truncatedString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试当前是否存在🪜
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
export async function testProxy() {
|
||||
// 配置代理服务器
|
||||
const proxyOptions = {
|
||||
host: '127.0.0.1',
|
||||
port: 7890,
|
||||
// 如果你的代理服务器需要认证
|
||||
// auth: 'username:password', // 取消注释并提供实际的用户名和密码
|
||||
};
|
||||
|
||||
// 创建一个代理隧道
|
||||
const httpsAgent = tunnel.httpsOverHttp({
|
||||
proxy: proxyOptions
|
||||
});
|
||||
|
||||
try {
|
||||
// 通过代理服务器发起请求
|
||||
await axios.get('https://google.com.hk', { httpsAgent });
|
||||
logger.mark('[R插件][梯子测试模块] 检测到梯子');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('[R插件][梯子测试模块] 检测不到梯子');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatSeconds(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}分${remainingSeconds}秒`;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user