Add napcat-schema package for OpenAPI generation

Introduces the napcat-schema package with scripts and configuration to auto-generate OpenAPI schemas for NapCat OneBot 11 actions. Refactors action handler export in napcat-onebot to support schema extraction.
This commit is contained in:
手瓜一十雪 2026-01-25 17:12:25 +08:00
parent 20398af648
commit 8f1dc3fdde
6 changed files with 701 additions and 238 deletions

View File

@ -156,7 +156,7 @@ import { ReceiveOnlineFile } from './file/online/ReceiveOnlineFile';
import { RefuseOnlineFile } from './file/online/RefuseOnlineFile';
import { GetFilesetId } from './file/flash/GetFilesetIdByCode';
export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
export function getAllHandlers (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [
new CleanStreamTempFile(obContext, core),
new DownloadFileStream(obContext, core),
@ -324,7 +324,11 @@ export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatC
new DownloadFileset(obContext, core),
new GetFilesetId(obContext, core),
];
return actionHandlers;
}
export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = getAllHandlers(obContext, core);
type HandlerUnion = typeof actionHandlers[number];
type MapType = {
[H in HandlerUnion as H['actionName']]: H;

View File

@ -0,0 +1,85 @@
import { getAllHandlers } from '@/napcat-onebot/action/index';
import { AutoRegisterRouter } from '@/napcat-onebot/action/auto-register';
import { writeFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { TSchema } from '@sinclair/typebox';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export const actionSchemas: Record<string, { payload?: TSchema, return?: TSchema; }> = {};
export function initSchemas () {
const handlers = getAllHandlers(null as any, null as any);
handlers.forEach(handler => {
if (handler.actionName && (handler.actionName as string) !== 'unknown') {
actionSchemas[handler.actionName] = {
payload: handler.payloadSchema,
return: handler.returnSchema
};
}
});
AutoRegisterRouter.forEach((ActionClass) => {
const handler = new ActionClass(null as any, null as any);
if (handler.actionName && (handler.actionName as string) !== 'unknown') {
actionSchemas[handler.actionName] = {
payload: handler.payloadSchema,
return: handler.returnSchema
};
}
});
}
export function generateOpenAPI () {
try {
initSchemas();
} catch (e) {
console.warn('Init schemas partial failure (expected due to complex imports), proceeding with collected data...');
}
const openapi: any = {
openapi: '3.1.0',
info: {
title: 'NapCat OneBot 11 API',
description: 'Auto-generated OpenAPI schema for NapCat OneBot 11 actions',
version: '1.0.0'
},
paths: {}
};
for (const [actionName, schemas] of Object.entries(actionSchemas)) {
if (!schemas.payload) continue;
const path = `/${actionName}`;
const cleanPayload = JSON.parse(JSON.stringify(schemas.payload || { type: 'object', properties: {} }));
const cleanReturn = JSON.parse(JSON.stringify(schemas.return || { type: 'object', properties: {} }));
openapi.paths[path] = {
post: {
summary: actionName,
requestBody: {
content: {
'application/json': {
schema: cleanPayload
}
}
},
responses: {
'200': {
description: '成功',
content: {
'application/json': {
schema: cleanReturn
}
}
}
}
}
};
}
const outputPath = resolve(__dirname, 'openapi.json');
writeFileSync(outputPath, JSON.stringify(openapi, null, 2));
console.log(`OpenAPI schema generated at: ${outputPath}`);
}
generateOpenAPI();

View File

@ -0,0 +1,19 @@
{
"name": "napcat-schema",
"version": "1.0.0",
"private": true,
"type": "module",
"main": "index.ts",
"scripts": {
"generate:openapi": "node ./dist/schemas.mjs",
"build:schema": "vite build"
},
"dependencies": {
"@sinclair/typebox": "^0.34.38",
"napcat-onebot": "workspace:*"
},
"devDependencies": {
"tsx": "^4.7.1",
"vite": "^6.0.0"
}
}

View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"*.ts",
"**/*.ts",
],
"exclude": [
"node_modules",
"dist"
]
}

View File

@ -0,0 +1,46 @@
import { defineConfig } from 'vite';
import path, { resolve } from 'path';
import { builtinModules } from 'module';
import nodeResolve from '@rollup/plugin-node-resolve';
// 依赖排除
const external = [
'ws',
'express',
'electron'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
export default defineConfig({
resolve: {
conditions: ['node', 'default'],
alias: {
'@/napcat-core': resolve(__dirname, '../napcat-core'),
'@/napcat-common': resolve(__dirname, '../napcat-common'),
'@/napcat-onebot': resolve(__dirname, '../napcat-onebot'),
'@/napcat-pty': resolve(__dirname, '../napcat-pty'),
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'),
'@/image-size': resolve(__dirname, '../image-size'),
},
},
plugins: [
nodeResolve(),
],
build: {
target: 'esnext',
minify: false,
emptyOutDir: true,
outDir: 'dist',
lib: {
entry: path.resolve(__dirname, './index.ts'),
formats: ['es'],
fileName: () => 'schemas.mjs',
},
rollupOptions: {
external: [
...nodeModules,
...external
]
},
},
});

File diff suppressed because it is too large Load Diff