feat: Douyin video reslove and download
This commit is contained in:
parent
578cd4b2af
commit
8d9d1d5867
3
bun.lock
3
bun.lock
@ -4,6 +4,7 @@
|
||||
"": {
|
||||
"name": "qq-bot",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.0.0",
|
||||
"express": "^5.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -64,6 +65,8 @@
|
||||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"dotenv": ["dotenv@17.0.0", "", {}, "sha512-A0BJ5lrpJVSfnMMXjmeO0xUnoxqsBHWCoqqTnGwGYVdnctqXXUEhJOO7LxmgxJon9tEZFGpe0xPRX0h2v3AANQ=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||
|
||||
200
index.ts
200
index.ts
@ -1,8 +1,10 @@
|
||||
import express from 'express';
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const PORT = process.env.PORT || 6100;
|
||||
|
||||
// 中间件:解析 JSON 请求体
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
@ -14,7 +16,7 @@ app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
app.use(express.raw({ type: '*/*', limit: '10mb' }));
|
||||
|
||||
// 自定义中间件:打印所有请求的详细信息
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
/* app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
@ -41,121 +43,139 @@ app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
console.log('='.repeat(80));
|
||||
|
||||
next();
|
||||
});
|
||||
}); */
|
||||
|
||||
// QQ机器人回显功能
|
||||
app.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
// 检查是否是消息类型的请求
|
||||
if (req.body && req.body.post_type === 'message') {
|
||||
if (!req.body || req.body.post_type != 'message') return
|
||||
const { target_id, raw_message, message_type, user_id } = req.body;
|
||||
|
||||
console.log(`\n[QQ机器人] 收到${message_type}消息,准备回显...`);
|
||||
console.log(`\n[QQ机器人] 收到${message_type}消息`);
|
||||
console.log(`发送者ID: ${user_id || target_id}`);
|
||||
console.log(`消息内容: ${raw_message}`);
|
||||
|
||||
// 构建回显消息体
|
||||
// Match Douyin URL
|
||||
// Like: https://v.douyin.com/YqgJL_phY_k/
|
||||
const douyinUrlPattern = /https?:\/\/v\.douyin\.com\/[a-zA-Z0-9_]+/;
|
||||
const douyinMatch = raw_message.match(douyinUrlPattern);
|
||||
|
||||
try {
|
||||
// 如果检测到抖音链接,调用解析API
|
||||
if (douyinMatch) {
|
||||
const douyinUrl = douyinMatch[0];
|
||||
console.log(`[抖音链接检测] 发现抖音链接: ${douyinUrl}`);
|
||||
|
||||
// 调用抖音视频解析API
|
||||
const apiUrl = `http://localhost:6101/api/hybrid/video_data?url=${encodeURIComponent(douyinUrl)}`;
|
||||
console.log(`[抖音解析] 调用API: ${apiUrl}`);
|
||||
|
||||
const apiResponse = await fetch(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
if (!apiResponse.ok)
|
||||
throw new Error(`抖音API调用失败: ${apiResponse.status} ${apiResponse.statusText}`);
|
||||
|
||||
let vD: any = await apiResponse.json();
|
||||
|
||||
if (!vD.data)
|
||||
throw new Error('抖音API返回数据格式错误');
|
||||
|
||||
vD = vD.data;
|
||||
|
||||
// 发送视频信息
|
||||
sendMsg(`[抖音下载] ${vD.author.nickname}: ${vD.caption}\n`, target_id);
|
||||
|
||||
// 下载视频
|
||||
const downloadUrl = `http://localhost:6101/api/download?url=${encodeURIComponent(douyinUrl)}&prefix=true&with_watermark=false`;
|
||||
console.log(`[抖音下载] 调用下载API: ${downloadUrl}`);
|
||||
|
||||
const downloadResponse = await fetch(downloadUrl);
|
||||
if (!downloadResponse.ok) {
|
||||
throw new Error(`视频下载失败: ${downloadResponse.status} ${downloadResponse.statusText}`);
|
||||
}
|
||||
|
||||
// 创建下载目录
|
||||
const downloadDir = process.env.DOWNLOAD_DIR || path.join(__dirname, 'downloads');
|
||||
if (!fs.existsSync(downloadDir)) {
|
||||
fs.mkdirSync(downloadDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
const timestamp = Date.now();
|
||||
// 清理文件名中的非法字符
|
||||
const cleanCaption = vD.caption.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50);
|
||||
const fileName = `${cleanCaption}_${timestamp}.mp4`;
|
||||
const filePath = path.join(downloadDir, fileName);
|
||||
|
||||
// 保存视频文件
|
||||
console.log(`[抖音下载] 保存视频到: ${filePath}`);
|
||||
const buffer = await downloadResponse.arrayBuffer();
|
||||
fs.writeFileSync(filePath, Buffer.from(buffer));
|
||||
|
||||
// 发送视频消息
|
||||
console.log(`[抖音发送] 发送视频文件`);
|
||||
await sendVideoMsg(filePath, target_id);
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
sendMsg(String(error), target_id);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function sendMsg(msg: string, target_id: string) {
|
||||
const replyMessage = {
|
||||
user_id: String(target_id || user_id),
|
||||
user_id: String(target_id),
|
||||
message: [
|
||||
{
|
||||
type: "text",
|
||||
data: {
|
||||
text: raw_message || "收到空消息"
|
||||
text: msg
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// 发送回显消息 - 使用请求来源的IP地址
|
||||
const clientIP = req.ip || req.connection.remoteAddress || req.socket.remoteAddress;
|
||||
// 清理IPv6映射的IPv4地址格式
|
||||
const cleanIP = clientIP?.replace(/^::ffff:/, '') || 'localhost';
|
||||
const replyUrl = `http://${cleanIP}:30000/send_private_msg`;
|
||||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||||
|
||||
console.log(`[QQ机器人] 发送回显消息到: ${replyUrl}`);
|
||||
console.log(`[QQ机器人] 回显内容:`, JSON.stringify(replyMessage, null, 2));
|
||||
|
||||
// 使用 fetch 发送回显消息
|
||||
const response = await fetch(replyUrl, {
|
||||
return fetch(replyUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(replyMessage)
|
||||
});
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
const responseData = await response.text();
|
||||
console.log(`[QQ机器人] 回显成功! 响应:`, responseData);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '消息已回显',
|
||||
echo_sent: true,
|
||||
original_message: raw_message,
|
||||
target_user: target_id || user_id
|
||||
});
|
||||
} else {
|
||||
console.error(`[QQ机器人] 回显失败! HTTP状态:`, response.status);
|
||||
const errorText = await response.text();
|
||||
console.error(`[QQ机器人] 错误响应:`, errorText);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '消息已接收,但回显失败',
|
||||
echo_sent: false,
|
||||
error: `HTTP ${response.status}: ${errorText}`,
|
||||
original_message: raw_message
|
||||
});
|
||||
function sendVideoMsg(filePath: string, target_id: string) {
|
||||
const videoMessage = {
|
||||
user_id: String(target_id),
|
||||
message: [
|
||||
{
|
||||
type: "video",
|
||||
data: {
|
||||
file: `file://${filePath}`
|
||||
}
|
||||
} else {
|
||||
// 非消息类型的请求,只记录不回显
|
||||
console.log(`\n[QQ机器人] 收到非消息类型请求: ${req.body?.post_type || '未知类型'}`);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '请求已接收(非消息类型,不回显)',
|
||||
post_type: req.body?.post_type || 'unknown'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`\n[QQ机器人] 处理请求时发生错误:`, error);
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '处理请求时发生错误',
|
||||
error: error instanceof Error ? error.message : '未知错误'
|
||||
});
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
/* // 捕获所有其他路径的处理器
|
||||
app.use((req: Request, res: Response) => {
|
||||
const responseData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
originalUrl: req.originalUrl,
|
||||
query: req.query,
|
||||
headers: req.headers,
|
||||
body: req.body,
|
||||
ip: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
message: '请求已接收并记录'
|
||||
};
|
||||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||||
|
||||
console.log(`\n[通用处理器] 向客户端返回确认信息`);
|
||||
|
||||
// 返回 JSON 响应
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: '请求已成功接收并记录到控制台',
|
||||
data: responseData
|
||||
return fetch(replyUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(videoMessage)
|
||||
});
|
||||
}); */
|
||||
}
|
||||
|
||||
// 错误处理中间件
|
||||
app.use((error: any, req: Request, res: Response, next: NextFunction) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.error(`\n[${timestamp}] 错误:`, error);
|
||||
@ -169,14 +189,6 @@ app.use((error: any, req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 HTTP 调试服务器已启动!`);
|
||||
console.log(`📡 监听端口: ${PORT}`);
|
||||
console.log(`🌐 访问地址: http://localhost:${PORT}`);
|
||||
console.log(`📝 所有请求都会在控制台显示详细信息`);
|
||||
console.log(`\n支持的测试方法:`);
|
||||
console.log(` GET: curl http://localhost:${PORT}/test`);
|
||||
console.log(` POST: curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' http://localhost:${PORT}/api/test`);
|
||||
console.log(` PUT: curl -X PUT -H "Content-Type: application/json" -d '{"data":"test"}' http://localhost:${PORT}/update`);
|
||||
console.log(` DELETE: curl -X DELETE http://localhost:${PORT}/delete/123`);
|
||||
console.log(`\n按 Ctrl+C 停止服务器\n`);
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
console.log(`With env:`, { DOWNLOAD_DIR: process.env.DOWNLOAD_DIR, PORT: process.env.PORT });
|
||||
});
|
||||
@ -15,6 +15,7 @@
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.0.0",
|
||||
"express": "^5.1.0"
|
||||
}
|
||||
}
|
||||
6
pm2.config.js
Normal file
6
pm2.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export const name = "QQBotServer";
|
||||
export const script = "index.ts";
|
||||
export const interpreter = "bun";
|
||||
export const env = {
|
||||
PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, // Add "~/.bun/bin/bun" to PATH
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user