qq-bot/index.ts

140 lines
4.5 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';
import cors from "cors";
import multer from "multer";
import * as douyin from './douyin'
import { sendMediaMsg, sendMsg } from './lib/qq';
import * as llm from './llm'
import { testNetwork } from './lib/network';
const app = express();
const PORT = process.env.PORT || 6100;
const __dirname = path.dirname(new URL(import.meta.url).pathname);
app.use(cors());
// 仅解析明确类型;不要用 '*/*'
app.use(express.json({ limit: '10mb', type: ['application/json'] }));
app.use(express.urlencoded({ extended: true, limit: '10mb', type: ['application/x-www-form-urlencoded'] }));
// QQ机器人回显功能
app.post('/', async (req: Request, res: Response) => {
// 检查是否是消息类型的请求
if (!req.body || req.body.post_type != 'message') return
const msgData = req.body as MessagePayload
const text = msgData.message.filter(m => m.type === 'text').map(m => m.data.text).join("\n").trim();
const target_id = String(msgData.target_id);
console.log(`\n[QQ机器人] 收到消息:`);
// console.log(JSON.stringify(req.body, null, 0));
console.log(`消息内容: ${msgData.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 = text.match(douyinUrlPattern);
try {
// 如果检测到抖音链接调用解析API
if (douyinMatch) {
const douyinUrl = douyinMatch[0];
console.log(`[抖音链接检测] 发现抖音链接: ${douyinUrl}`);
sendMsg(`[抖音链接检测] 发现抖音链接: ${douyinUrl},启动 Chromium 中...`, target_id);
douyin.downloadDouyinMedia(douyinUrl, target_id);
return
}
if (text.startsWith('/reset')) {
llm.resetChat(target_id)
return
}
// 使用 LLM 回答
llm.chat(msgData.message, target_id);
} catch (error) {
console.error(`[错误] 处理消息时发生错误:`, error);
sendMsg(String(error), target_id);
}
});
function sanitizeDirname(input: string) {
// 基础清洗:去除路径分隔符、控制字符,并截断长度(防止奇怪标题)
const s = (input || "default")
.trim()
.replace(/[\\/]+/g, "_")
.replace(/[<>:"|?*\u0000-\u001F]/g, "_")
.slice(0, 32) || "default";
return s;
}
const storage = multer.diskStorage({
destination: (req, _file, cb) => {
const title = sanitizeDirname(req.query.title as string);
const baseDir = process.env.DOWNLOAD_DIR || path.join(__dirname, "downloads");
const dest = req.query.type == 'image' ? path.join(baseDir, title) : baseDir;
fs.mkdir(dest, { recursive: true }, (err) => cb(err, dest));
}, filename: (req, file, cb) => {
const ext = path.extname(file.originalname || "");
const title = sanitizeDirname(req.query.title as string);
cb(null, `${title}_${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`);
},
});
const upload = multer({
storage,
limits: { fileSize: 1024 * 1024 * 1024, fields: 50, files: 50 },
fileFilter: (_req, file, cb) => {
if (/^file_\d+$/i.test(file.fieldname)) cb(null, true);
else cb(new Error(`Unexpected file field: ${file.fieldname}`));
}
});
app.post("/upload", upload.any(), (req, res) => {
const files = (req.files as Express.Multer.File[]) || [];
const accepted = files.filter(f => /^file_\d+$/i.test(f.fieldname));
let meta = {
title: req.body.title,
type: req.query.type as 'video' | 'image',
target_id: req.query.target_id
};
console.log(`收到上传: ${accepted.length} 个文件`, files.map(f => f.path));
if (meta.target_id) {
const totalSize = accepted.reduce((sum, f) => sum + f.size, 0);
sendMsg(`[抖音下载] ${meta.title},已下载 ${accepted.length} 个文件,类型 ${meta.type},共 ${(totalSize / 1024 / 1024).toFixed(2)} MB上传中...`, meta.target_id as string);
accepted.forEach(f => {
sendMediaMsg(f.path, meta.target_id as string, meta.type);
})
}
res.json({ ok: true, files: accepted.length, meta });
});
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}`);
testNetwork();
});