@pickstar-2002/video-fetch-mcp
Version:
基于 MCP 协议的多平台视频下载服务端
247 lines • 8.66 kB
JavaScript
/**
* REST API 服务器实现
*/
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import YtDlpService from './ytdlp-service.js';
import { logger } from '../utils/logger.js';
import config from '../config.js';
import Joi from 'joi';
export class RestApiServer {
constructor() {
this.app = express();
this.ytdlpService = new YtDlpService();
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
/**
* 设置中间件
*/
setupMiddleware() {
// 安全头
if (config.api.enableSecurity) {
this.app.use(helmet());
}
// CORS
if (config.api.enableCors) {
this.app.use(cors({
origin: true,
credentials: true,
}));
}
// 解析 JSON
this.app.use(express.json({ limit: config.api.bodyLimit }));
this.app.use(express.urlencoded({ extended: true, limit: config.api.bodyLimit }));
// 请求日志
this.app.use((req, res, next) => {
logger.info(`${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.get('User-Agent'),
body: req.method === 'POST' ? req.body : undefined
});
next();
});
}
/**
* 设置路由
*/
setupRoutes() {
const router = express.Router();
// 健康检查
router.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: config.mcp.version
});
});
// 获取视频信息
router.post('/video/info', async (req, res, next) => {
try {
const schema = Joi.object({
url: Joi.string().uri().required()
});
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: `参数验证失败: ${error.message}`
});
}
const videoInfo = await this.ytdlpService.getVideoInfo(value.url);
return res.json({
success: true,
data: videoInfo
});
}
catch (error) {
return next(error);
}
});
// 开始下载视频
router.post('/video/download', async (req, res, next) => {
try {
const schema = Joi.object({
url: Joi.string().uri().required(),
outputPath: Joi.string().required(),
quality: Joi.string().valid('best', 'worst', 'bestvideo', 'bestaudio').default('best'),
outputTemplate: Joi.string().default('%(title)s.%(ext)s'),
extractAudio: Joi.boolean().default(false),
audioFormat: Joi.string().valid('mp3', 'aac', 'wav', 'flac').default('mp3'),
downloadSubtitles: Joi.boolean().default(false),
subtitleLangs: Joi.array().items(Joi.string()).default(['zh-CN', 'en'])
});
const { error, value } = schema.validate(req.body);
if (error) {
return res.status(400).json({
success: false,
error: `参数验证失败: ${error.message}`
});
}
const request = value;
const taskId = await this.ytdlpService.downloadVideo(request);
return res.json({
success: true,
data: {
taskId,
message: '下载任务已创建',
request
}
});
}
catch (error) {
return next(error);
}
});
// 获取任务状态
router.get('/task/:taskId', async (req, res, next) => {
try {
const { taskId } = req.params;
const task = this.ytdlpService.getTaskStatus(taskId);
if (!task) {
return res.status(404).json({
success: false,
error: '任务不存在'
});
}
return res.json({
success: true,
data: task
});
}
catch (error) {
return next(error);
}
});
// 取消任务
router.delete('/task/:taskId', async (req, res, next) => {
try {
const { taskId } = req.params;
const success = this.ytdlpService.cancelTask(taskId);
res.json({
success,
message: success ? '任务已取消' : '任务不存在或无法取消'
});
}
catch (error) {
next(error);
}
});
// 列出所有任务
router.get('/tasks', async (req, res, next) => {
try {
const tasks = this.ytdlpService.getAllTasks();
res.json({
success: true,
data: {
tasks,
count: tasks.length
}
});
}
catch (error) {
next(error);
}
});
// 获取支持的平台列表
router.get('/platforms', (req, res) => {
res.json({
success: true,
data: {
platforms: config.supportedPlatforms,
count: config.supportedPlatforms.length
}
});
});
// 应用路由
this.app.use(config.api.prefix, router);
// 根路径重定向到 API 文档
this.app.get('/', (req, res) => {
res.json({
name: config.mcp.name,
version: config.mcp.version,
description: config.mcp.description,
endpoints: {
health: `GET ${config.api.prefix}/health`,
videoInfo: `POST ${config.api.prefix}/video/info`,
downloadVideo: `POST ${config.api.prefix}/video/download`,
getTask: `GET ${config.api.prefix}/task/:taskId`,
cancelTask: `DELETE ${config.api.prefix}/task/:taskId`,
listTasks: `GET ${config.api.prefix}/tasks`,
platforms: `GET ${config.api.prefix}/platforms`
}
});
});
}
/**
* 设置错误处理
*/
setupErrorHandling() {
// 404 处理
this.app.use((req, res) => {
res.status(404).json({
success: false,
error: '接口不存在',
path: req.path
});
});
// 全局错误处理
this.app.use((error, req, res, next) => {
logger.error('API 错误:', error);
res.status(500).json({
success: false,
error: error.message || '内部服务器错误',
timestamp: new Date().toISOString()
});
});
}
/**
* 启动服务器
*/
async start() {
return new Promise((resolve) => {
const server = this.app.listen(config.port, () => {
logger.info(`REST API 服务器已启动,端口: ${config.port}`);
logger.info(`API 文档: http://localhost:${config.port}`);
logger.info(`健康检查: http://localhost:${config.port}${config.api.prefix}/health`);
// 定期清理过期任务
setInterval(() => {
this.ytdlpService.cleanupExpiredTasks();
}, 60000); // 每分钟清理一次
resolve();
});
// 优雅关闭
process.on('SIGINT', () => {
logger.info('正在关闭服务器...');
server.close(() => {
logger.info('服务器已关闭');
process.exit(0);
});
});
});
}
}
export default RestApiServer;
//# sourceMappingURL=rest-api-server.js.map