196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
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 || 6100;
|
||
|
||
// 中间件:解析 JSON 请求体
|
||
app.use(express.json({ limit: '10mb' }));
|
||
|
||
// 中间件:解析 URL 编码的请求体
|
||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||
|
||
// 中间件:解析原始请求体
|
||
app.use(express.raw({ type: '*/*', limit: '10mb' }));
|
||
|
||
// 自定义中间件:打印所有请求的详细信息
|
||
/* app.use((req: Request, res: Response, next: NextFunction) => {
|
||
const timestamp = new Date().toISOString();
|
||
|
||
console.log('\n' + '='.repeat(80));
|
||
console.log(`[${timestamp}] 收到请求:`);
|
||
console.log(`方法: ${req.method}`);
|
||
console.log(`路径: ${req.path}`);
|
||
console.log(`完整URL: ${req.originalUrl}`);
|
||
console.log(`查询参数:`, req.query);
|
||
console.log(`请求头:`, req.headers);
|
||
|
||
// 打印请求体(如果存在)
|
||
if (req.body) {
|
||
if (Buffer.isBuffer(req.body)) {
|
||
console.log(`请求体 (原始):`, req.body.toString());
|
||
} else if (typeof req.body === 'object' && Object.keys(req.body).length > 0) {
|
||
console.log(`请求体 (JSON):`, req.body);
|
||
} else if (req.body) {
|
||
console.log(`请求体:`, req.body);
|
||
}
|
||
}
|
||
|
||
console.log(`客户端IP: ${req.ip}`);
|
||
console.log(`User-Agent: ${req.get('User-Agent')}`);
|
||
console.log('='.repeat(80));
|
||
|
||
next();
|
||
}); */
|
||
|
||
// QQ机器人回显功能
|
||
app.post('/', async (req: Request, res: Response) => {
|
||
// 检查是否是消息类型的请求
|
||
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(`发送者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) {
|
||
console.error(`[错误] 处理消息时发生错误:`, error);
|
||
|
||
sendMsg(String(error), target_id);
|
||
}
|
||
|
||
});
|
||
|
||
function sendMsg(msg: string, target_id: string) {
|
||
const replyMessage = {
|
||
user_id: String(target_id),
|
||
message: [
|
||
{
|
||
type: "text",
|
||
data: {
|
||
text: msg
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||
|
||
return fetch(replyUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(replyMessage)
|
||
});
|
||
}
|
||
|
||
function sendVideoMsg(filePath: string, target_id: string) {
|
||
const videoMessage = {
|
||
user_id: String(target_id),
|
||
message: [
|
||
{
|
||
type: "video",
|
||
data: {
|
||
file: `file://${filePath}`
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
const replyUrl = `http://localhost:30000/send_private_msg`;
|
||
|
||
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);
|
||
|
||
res.status(500).json({
|
||
success: false,
|
||
message: '服务器内部错误',
|
||
error: error.message
|
||
});
|
||
});
|
||
|
||
// 启动服务器
|
||
app.listen(PORT, () => {
|
||
console.log(`Server is running on http://localhost:${PORT}`);
|
||
console.log(`With env:`, { DOWNLOAD_DIR: process.env.DOWNLOAD_DIR, PORT: process.env.PORT });
|
||
}); |