qq-bot/index.ts
2025-08-30 22:46:31 +08:00

196 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 });
});