diff --git a/README.md b/README.md
index ff234a6..b4f2e8f 100644
--- a/README.md
+++ b/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
-🌸感谢以下框架的开源🌸:
-
-油管解析参考了:
-- [yt-dlp:A youtube-dl fork with additional features and fixes](https://github.com/yt-dlp/yt-dlp)
-
## ☕ 请我喝一杯瑞幸咖啡
如果你觉得插件能帮助到你增进好友关系,那么你可以在有条件的情况下[请我喝一杯瑞幸咖啡](https://afdian.net/a/zhiyu1998),这是我开源这个插件的最大动力!
感谢以下朋友的支持!(排名不分多少)
diff --git a/apps/tools.js b/apps/tools.js
index 4ceb468..1f775e7 100644
--- a/apps/tools.js
+++ b/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());
-
- 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;
+ // 判断海外
+ const isOversea = await this.isOverseasServer();
+ // 如果不是海外用户且没有梯子直接返回
+ if (!isOversea && await testProxy()) {
+ e.reply("检测到没有梯子,无法解析TikTok");
+ return false;
}
- // 格式化
- 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));
- }
- });
-
- // 寻找最佳的分辨率
- // 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('/') }`);
+ // 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]));
+
+ 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
+ },
+ });
+ }
+
+ 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) {
- logger.error(error.toString());
- e.reply("y2b下载失败");
- return;
+ console.error(error);
+ throw error; // Rethrow the error so it can be handled by the caller
}
+ return true;
}
// 米游社
diff --git a/config/version.yaml b/config/version.yaml
index 96a4d40..a34da81 100644
--- a/config/version.yaml
+++ b/config/version.yaml
@@ -1,5 +1,5 @@
- {
- version: 1.5.13,
+ version: 1.6.0,
data:
[
新增微视解析功能,
diff --git a/constants/tools.js b/constants/tools.js
index 12b14db..b042591 100644
--- a/constants/tools.js
+++ b/constants/tools.js
@@ -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
diff --git a/utils/common.js b/utils/common.js
index c214e75..4935ddb 100644
--- a/utils/common.js
+++ b/utils/common.js
@@ -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}
+ */
+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}秒`;
+}
\ No newline at end of file