Compare commits

..

40 Commits

Author SHA1 Message Date
手瓜一十雪
afb6ef421a Add Passkey (WebAuthn) authentication support
Introduces Passkey (WebAuthn) registration and authentication to both backend and frontend. Backend adds new API endpoints, middleware exceptions, and a PasskeyHelper for credential management using @simplewebauthn/server. Frontend integrates @simplewebauthn/browser, updates login and config pages for Passkey registration and login flows, and adds related UI and controller methods.
2025-11-22 16:00:32 +08:00
手瓜一十雪
173a165c4b Add latest version check and update prompt to UI
Introduces backend and frontend logic to fetch the latest NapCat version tag from multiple sources, exposes a new API endpoint, and adds a UI prompt to notify users of new versions with an update button. Also includes minor code style improvements in dialog context.
2025-11-22 13:52:49 +08:00
手瓜一十雪
d525f9b03d Refactor and standardize share and message history APIs
Standardized field names (e.g., 'reverseOrder' to 'reverse_order', 'phoneNumber' to 'phone_number') and added new action names and classes for sharing contacts and group cards (SendArkShare, SendGroupArkShare). Deprecated old action names, updated API schemas and routes, and ensured backward compatibility for legacy fields. Updated frontend API definitions to match backend changes.
2025-11-22 13:14:46 +08:00
zeus-x99
f2ba789cc0 fix(webui-backend): 仅在启用 WebUI 时检测/更新默认密码 (#1387)
在初始化 WebUI 时,先判断  并直接返回,确保禁用状态下不再检测或更新默认密码 token。
2025-11-20 14:38:59 +08:00
手瓜一十雪
2cdc9bdc09 Add backend and frontend support for NapCat auto-update
Introduces backend API and router for updating NapCat, including update logic and pending update application on startup. Adds frontend integration with update button and request handling. Refactors system info component to remove legacy new version tip. Updates types and runtime to track working environment for update selection. Implements lazy loading for pty in unixTerminal to avoid early initialization.
2025-11-19 21:05:08 +08:00
手瓜一十雪
c123b34d5f Update ffmpeg addon binary for Darwin ARM64
Replaces the ffmpegAddon.darwin.arm64.node binary with a new version, likely to include bug fixes or performance improvements for ARM64 macOS systems.
2025-11-18 20:16:03 +08:00
手瓜一十雪
d25b43ebf2 Update ffmpeg native binaries for Linux
Replaces ffmpegAddon.linux.arm64.node and ffmpegAddon.linux.x64.node with new versions. This may include bug fixes, performance improvements, or compatibility updates for native ffmpeg functionality.
2025-11-18 17:36:09 +08:00
时瑾
8fe4a9e6ac 更新 Bug 反馈模板,增强对不当内容的说明和合规性要求 2025-11-16 15:55:25 +08:00
手瓜一十雪
09da80aad5 Remove unused dependencies from package.json
Deleted the 'dependencies' section from napcat-qrcode/package.json, removing references to napcat-core, napcat-common, napcat-onebot, and napcat-webui-backend. This streamlines the package configuration and may indicate these dependencies are no longer required.
2025-11-16 11:02:21 +08:00
手瓜一十雪
3d3f718fd5 Refactor interfaces and decouple backend dependencies
Introduced new interface definitions in napcat-common for logging, status, and subscription. Refactored napcat-webui-backend to use these interfaces, decoupling it from napcat-core and napcat-onebot. Moved OneBot config schema to backend and updated imports. Updated framework and shell to pass subscriptions to InitWebUi. Improved type safety and modularity across backend and shared packages.
2025-11-16 10:58:30 +08:00
手瓜一十雪
6068abdec0 Update VSCode settings for formatting and file nesting
Enhanced .vscode/settings.json with file nesting patterns, formatting preferences, auto-save behavior, and import specifier options for JavaScript and TypeScript. Removed old debug source map overrides to streamline workspace configuration.
2025-11-15 17:17:24 +08:00
手瓜一十雪
3957d7af5a Use environment variables for secret keys in dev and backend
Set fixed secret keys for JWT and WebUI in development environment via environment variables. Updated backend to use NAPCAT_WEBUI_SECRET_KEY and NAPCAT_WEBUI_JWT_SECRET_KEY from environment if available, improving configurability and security.
2025-11-15 17:00:52 +08:00
手瓜一十雪
a2837974fe Add VSCode launch and TailwindCSS config files
Added .vscode/launch.json for Node.js debugging and .vscode/tailwindcss.json for TailwindCSS directive support in VSCode. These files improve development workflow and editor integration.
2025-11-15 16:51:46 +08:00
手瓜一十雪
6f8edfe570 清理重复的 Vitest configuration file
Deleted vitest.config.ts, which contained test environment and path alias settings. This may indicate a change in testing strategy or migration away from Vitest.
2025-11-15 16:42:50 +08:00
手瓜一十雪
0b655db4dd Add test step to build workflow
Inserts 'pnpm test' into both build jobs in the GitHub Actions workflow to ensure tests are run during CI before building artifacts.
2025-11-15 16:25:06 +08:00
手瓜一十雪
d800466a30 Move inversify and reflect-metadata to napcat-core
Transferred 'inversify' and 'reflect-metadata' dependencies from the root package.json to packages/napcat-core/package.json to better scope dependencies and improve project organization.
2025-11-15 16:23:03 +08:00
手瓜一十雪
fa80441e36 Add ESLint config and update code style
Introduced a new eslint.config.js using neostandard and added related devDependencies. Updated codebase for consistent formatting, spacing, and function declarations. Minor refactoring and cleanup across multiple files to improve readability and maintain code style compliance.
2025-11-15 16:21:59 +08:00
手瓜一十雪
1990761ad6 Update main entry and tsconfig for JS support
Changed package.json main and exports to use index.js instead of index.cjs. Updated tsconfig.json to allow JavaScript files and expanded include patterns to support both JS and TS files.
2025-11-15 16:09:26 +08:00
手瓜一十雪
ef63812391 Add napcat-test package and Vitest setup
Introduces the napcat-test package with initial SHA-1 stream tests, configuration files, and scripts for running tests. Updates root package.json to include test commands and Vitest dependencies, and adds Vitest configuration at the root and package level for test environment setup.
2025-11-15 16:05:09 +08:00
手瓜一十雪
0f033b0ac8 Remove performance monitor module
Deleted performance-monitor.ts from napcat-common, removing the function statistics and reporting utility. This may indicate a refactor or replacement of performance monitoring functionality.
2025-11-15 15:13:56 +08:00
手瓜一十雪
9fdef3cde9 Revert "Update default model in release workflow"
This reverts commit d32ccc6eb5.
2025-11-15 14:56:34 +08:00
手瓜一十雪
20e8643193 Fix import paths for require_dlopen module
Updated import statements in prebuild-loader.ts and windowsPtyAgent.ts to use relative path './index' for require_dlopen. This resolves incorrect module resolution issues.
2025-11-15 14:43:08 +08:00
手瓜一十雪
8645ed4d9d Update tsconfig to include typeRoots and format paths
Added 'typeRoots' to specify custom type definitions directory and reformatted the 'paths' property for better readability. This improves TypeScript type resolution and project maintainability.
2025-11-15 14:40:06 +08:00
手瓜一十雪
c0b9817ff5 Add type checking to build workflow
Incorporates 'pnpm run typecheck' into the build steps for both frontend and shell jobs to ensure type safety during CI builds.
2025-11-15 14:01:11 +08:00
手瓜一十雪
b147e57c1c Refactor TypeScript configs to use shared base
Introduced tsconfig.base.json for shared TypeScript configuration and updated all package tsconfig.json files to extend from it, reducing duplication and improving maintainability. Also updated typecheck script in package.json and fixed import in prebuild-loader.ts.
2025-11-15 14:00:27 +08:00
手瓜一十雪
ad4a108781 feat: 大规模去耦合
Moved various helper, event, and utility files from napcat-common to napcat-core/helper for better modularity and separation of concerns. Updated imports across packages to reflect new file locations. Removed unused dependencies from napcat-common and added them to napcat-core where needed. Also consolidated type definitions and cleaned up tsconfig settings for improved compatibility.
2025-11-15 13:36:33 +08:00
手瓜一十雪
df824d77ae feat: 所有的类型检查 2025-11-15 12:57:19 +08:00
手瓜一十雪
19888d52dc Remove unused test utility files
Deleted test.ts and test2.ts from utils as they are no longer needed for the project.
2025-11-15 12:02:07 +08:00
手瓜一十雪
4dc8b3ed3b Refactor FileApi usage to obContext in OneBot
Replaced references to `core.apis.FileApi` with `obContext.apis.FileApi` across actions and message API to ensure consistent context usage. Added FileApi to the ApiListType and initialized it in NapCatOneBot11Adapter. This improves maintainability and context handling for file-related operations.
2025-11-15 11:20:01 +08:00
手瓜一十雪
8df54d5cd3 feat: 去core耦合onebot
Moved file element creation methods (for file, picture, video, and ptt) from napcat-core/apis/file.ts to a new OneBotFileApi class in napcat-onebot/api/file.ts. Updated package.json dependencies to remove unused packages and fix workspace references. This improves separation of concerns and modularity between core and onebot-specific logic.
2025-11-15 11:17:57 +08:00
手瓜一十雪
aa982b3071 Improve version folder selection in loadNapCat.cjs
Enhanced logic to handle multiple or missing version folders by selecting the most recently modified folder if more than one exists, and providing clearer error messages. Also updated .vscode/settings.json to add source map path overrides for additional packages.
2025-11-15 10:58:33 +08:00
手瓜一十雪
8e71dec63a Bind TypedEventEmitter to DI container and update usage
Added TypedEventEmitter to the dependency injection container and exposed it in ServiceBase. Updated OlPushService to use the injected event emitter instead of directly importing appEvent. Also performed minor code style improvements for consistency.
2025-11-15 10:48:59 +08:00
手瓜一十雪
31bb1e5dee Add emoji like event handling to core and onebot
Introduces a typed event emitter for app events in napcat-core, specifically for emoji like events in groups. OlPushService now emits 'event:emoji_like' when a group reaction is detected. napcat-onebot listens for this event and emits corresponding OneBot events. Refactors and adds missing type definitions and improves method formatting for consistency.
2025-11-15 10:45:02 +08:00
手瓜一十雪
75e1e8dd79 Improve error handling in release workflow
Enhances the OpenRouter API call step by adding error handling for both curl and jq failures. If the API call or response parsing fails, the workflow now falls back to using a default release note template.
2025-11-14 23:00:20 +08:00
手瓜一十雪
d32ccc6eb5 Update default model in release workflow
Changed the OPENROUTER_MODEL environment variable from 'kimi-k2-0905-turbo' to 'glm-4.6-turbo' in the release workflow configuration.
2025-11-14 22:45:17 +08:00
手瓜一十雪
7b3e94d568 Add raw response output to release workflow
Added echo statements to display the raw API response in the release workflow for improved debugging and visibility.
2025-11-14 22:35:58 +08:00
手瓜一十雪
5cfe479044 Refactor createListenerFunction type and usage
Simplifies the generic type signature of createListenerFunction and updates the way createEventFunction is called, removing unnecessary type parameters and using a direct function call with TypeScript ignore for type checking.
2025-11-14 22:24:50 +08:00
手瓜一十雪
f04ffa5dc6 Add service handler registration and DI support
Introduces dependency injection via Inversify and reflect-metadata, adds a service handler registry for packet handling, and updates core initialization to auto-register and bind service handlers. Also updates Vite configs and auto-include logic to support protocol service files.
2025-11-14 22:20:33 +08:00
手瓜一十雪
a2a73ce2dd Add dev build script and improve Vite config
Introduces a 'build:shell:dev' script for development builds and updates napcat-shell's Vite config to conditionally enable source maps in development mode. This enhances build flexibility for development and production environments.
2025-11-14 21:25:29 +08:00
手瓜一十雪
66d02eeb6a Enable source maps and improve debugging support
This commit enables source maps in napcat-shell's Vite config for better debugging, adds source map path overrides to VSCode settings, and updates nodeTest.ps1 to launch with the Node.js inspector. The autoIncludeTSPlugin transform now returns a source map for improved breakpoint support in VSCode. Also adds a sources.txt file listing project and dependency sources.
2025-11-14 21:21:49 +08:00
159 changed files with 2905 additions and 1748 deletions

View File

@@ -1,6 +1,6 @@
name: Bug 反馈 name: Bug 反馈
description: 报告可能的 NapCat 异常行为 description: 报告可能的 NapCat 异常行为
title: '[BUG] ' title: "[BUG] "
labels: bug labels: bug
body: body:
- type: markdown - type: markdown
@@ -10,6 +10,10 @@ body:
在提交新的 Bug 反馈前,请确保您: 在提交新的 Bug 反馈前,请确保您:
* 已经搜索了现有的 issues并且没有找到可以解决您问题的方法 * 已经搜索了现有的 issues并且没有找到可以解决您问题的方法
* 不与现有的某一 issue 重复 * 不与现有的某一 issue 重复
* **不接受因发送不当内容而导致的问题报告**
- 包括但不限于:多媒体发送失败、转发消息失败、消息被拦截等因 18+ 内容、违规内容或触发风控的问题
- 提交 issue 前,请确认您发送的多媒体内容、链接、文本等均为正常合规内容,不会触发平台风控机制
- 因违规内容导致的问题,一律不予受理
- type: input - type: input
id: system-version id: system-version
attributes: attributes:

View File

@@ -21,6 +21,8 @@ jobs:
run: | run: |
npm i -g pnpm npm i -g pnpm
pnpm i pnpm i
pnpm run typecheck || exit 1
pnpm test || exit 1
pnpm --filter napcat-webui-frontend run build || exit 1 pnpm --filter napcat-webui-frontend run build || exit 1
pnpm run build:framework pnpm run build:framework
mv packages/napcat-framework/dist framework-dist mv packages/napcat-framework/dist framework-dist
@@ -45,6 +47,8 @@ jobs:
run: | run: |
npm i -g pnpm npm i -g pnpm
pnpm i pnpm i
pnpm run typecheck || exit 1
pnpm test || exit 1
pnpm --filter napcat-webui-frontend run build || exit 1 pnpm --filter napcat-webui-frontend run build || exit 1
pnpm run build:shell pnpm run build:shell
mv packages/napcat-shell/dist shell-dist mv packages/napcat-shell/dist shell-dist

View File

@@ -232,24 +232,32 @@ jobs:
echo "$BODY" | jq . echo "$BODY" | jq .
# 调用 OpenRouter # 调用 OpenRouter
RESPONSE=$(curl -s -X POST "$OPENROUTER_API_URL" \ if RESPONSE=$(curl -s -X POST "$OPENROUTER_API_URL" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$BODY") -d "$BODY"); then
echo "=== raw response ==="
echo "$RESPONSE"
echo "=== OpenRouter raw response ==="
if echo "$RESPONSE" | jq . >/dev/null 2>&1; then
echo "$RESPONSE" | jq .
else
echo "jq failed to parse response"
fi
echo "=== OpenRouter raw response ===" # 提取生成内容
echo "$RESPONSE" | jq . RELEASE_BODY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .choices[0].text // ""' 2>/dev/null || echo "")
# 提取生成内容 if [ -z "$RELEASE_BODY" ]; then
RELEASE_BODY=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // .choices[0].text // ""') echo "❌ OpenRouter failed to generate release note, using default.md"
cp .github/prompt/default.md CHANGELOG.md
if [ -z "$RELEASE_BODY" ]; then else
echo "❌ OpenRouter failed to generate release note, terminating workflow." echo -e "$RELEASE_BODY" > CHANGELOG.md
exit 1 fi
else
echo "❌ Curl failed, using default.md"
cp .github/prompt/default.md CHANGELOG.md
fi fi
# 输出到 CHANGELOG.md
echo -e "$RELEASE_BODY" > CHANGELOG.md
echo "=== generated release note ===" echo "=== generated release note ==="
cat CHANGELOG.md cat CHANGELOG.md

12
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node-terminal",
"request": "launch",
"name": "调试程序",
"command": "pnpm run dev:shell",
"cwd": "${workspaceFolder}"
}
]
}

35
.vscode/settings.json vendored
View File

@@ -1,2 +1,37 @@
{ {
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
".env.universal": ".env.*",
"vite.config.ts": "vite*.ts",
"README.md": "CODE_OF_CONDUCT.md, RELEASES.md, CONTRIBUTING.md, CHANGELOG.md, SECURITY.md",
"tsconfig.json": "tsconfig.*.json, env.d.ts",
"package.json": "package-lock.json, eslint*, .prettier*, .editorconfig, manifest.json, logo.png, .gitignore, LICENSE"
},
"css.customData": [
".vscode/tailwindcss.json"
],
"editor.detectIndentation": false,
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.formatOnType": false,
"editor.formatOnPaste": true,
"editor.formatOnSaveMode": "file",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"files.autoSave": "onFocusChange",
"javascript.preferences.quoteStyle": "single",
"typescript.preferences.quoteStyle": "single",
"javascript.format.semicolons": "insert",
"typescript.format.semicolons": "insert",
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"typescript.format.insertSpaceBeforeFunctionParenthesis": true,
"typescript.format.insertSpaceAfterConstructor": true,
"javascript.format.insertSpaceAfterConstructor": true,
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.importModuleSpecifierEnding": "minimal",
"javascript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifierEnding": "minimal",
"typescript.disableAutomaticTypeAcquisition": true
} }

55
.vscode/tailwindcss.json vendored Normal file
View File

@@ -0,0 +1,55 @@
{
"version": 1.1,
"atDirectives": [
{
"name": "@tailwind",
"description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
}
]
},
{
"name": "@apply",
"description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that youd like to extract to a new component.",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#apply"
}
]
},
{
"name": "@responsive",
"description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
}
]
},
{
"name": "@screen",
"description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#screen"
}
]
},
{
"name": "@variants",
"description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n",
"references": [
{
"name": "Tailwind Documentation",
"url": "https://tailwindcss.com/docs/functions-and-directives#variants"
}
]
}
]
}

52
eslint.config.js Normal file
View File

@@ -0,0 +1,52 @@
import neostandard from 'neostandard';
/** 尾随逗号 */
const commaDangle = val => {
if (val?.rules?.['@stylistic/comma-dangle']?.[0] === 'warn') {
const rule = val?.rules?.['@stylistic/comma-dangle']?.[1];
Object.keys(rule).forEach(key => {
rule[key] = 'always-multiline';
});
val.rules['@stylistic/comma-dangle'][1] = rule;
}
/** 三元表达式 */
if (val?.rules?.['@stylistic/indent']) {
val.rules['@stylistic/indent'][2] = {
...val.rules?.['@stylistic/indent']?.[2],
flatTernaryExpressions: true,
offsetTernaryExpressions: false,
};
}
/** 支持下划线 - 禁用 camelcase 规则 */
if (val?.rules?.camelcase) {
val.rules.camelcase = 'off';
}
/** 未使用的变量强制报错 */
if (val?.rules?.['@typescript-eslint/no-unused-vars']) {
val.rules['@typescript-eslint/no-unused-vars'] = ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}];
}
return val;
};
/** 忽略的文件 */
const ignores = [
'node_modules',
'**/dist/**',
'launcher',
];
const options = neostandard({
ts: true,
ignores,
semi: true, // 强制使用分号
}).map(commaDangle);
export default options;

View File

@@ -5,17 +5,26 @@
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"build:shell": "pnpm --filter napcat-shell run build || exit 1", "build:shell": "pnpm --filter napcat-shell run build || exit 1",
"build:shell:dev": "pnpm --filter napcat-shell run build:dev || exit 1",
"build:framework": "pnpm --filter napcat-framework run build || exit 1", "build:framework": "pnpm --filter napcat-framework run build || exit 1",
"build:webui": "pnpm --filter napcat-webui-frontend run build || exit 1", "build:webui": "pnpm --filter napcat-webui-frontend run build || exit 1",
"dev:shell": "pnpm --filter napcat-develop run dev || exit 1", "dev:shell": "pnpm --filter napcat-develop run dev || exit 1",
"typecheck": "pnpm -w -r --filter napcat-shell --filter napcat-framework run typecheck" "typecheck": "pnpm -r --if-present run typecheck",
"test": "pnpm --filter napcat-test run test",
"test:ui": "pnpm --filter napcat-test run test:ui",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-node-resolve": "^16.0.3",
"@vitejs/plugin-react-swc": "^4.2.2", "@vitejs/plugin-react-swc": "^4.2.2",
"@vitest/ui": "^4.0.9",
"eslint": "^9.39.1",
"neostandard": "^0.12.2",
"typescript": "^5.3.0", "typescript": "^5.3.0",
"vite": "^6.4.1", "vite": "^6.4.1",
"vite-plugin-cp": "^6.0.3" "vite-plugin-cp": "^6.0.3",
"vitest": "^4.0.9"
}, },
"dependencies": { "dependencies": {
"express": "^5.0.0", "express": "^5.0.0",

View File

@@ -4,6 +4,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": {
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
},
"exports": { "exports": {
".": { ".": {
"import": "./src/index.ts" "import": "./src/index.ts"
@@ -13,14 +16,9 @@
} }
}, },
"dependencies": { "dependencies": {
"compressing": "^1.10.1",
"json5": "^2.2.3",
"ajv": "^8.13.0", "ajv": "^8.13.0",
"file-type": "^21.0.0", "file-type": "^21.0.0",
"napcat-image-size": "workspace:*", "silk-wasm": "^3.6.1"
"napcat-core": "workspace:*",
"silk-wasm": "^3.6.1",
"winston": "^3.17.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.0.1" "@types/node": "^22.0.1"

View File

@@ -1,4 +1,4 @@
import { Peer } from 'napcat-core/index'; import { Peer } from './types';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
class TimeBasedCache<K, V> { class TimeBasedCache<K, V> {

View File

@@ -2,7 +2,7 @@ import fs from 'fs';
import { stat } from 'fs/promises'; import { stat } from 'fs/promises';
import crypto, { randomUUID } from 'crypto'; import crypto, { randomUUID } from 'crypto';
import path from 'node:path'; import path from 'node:path';
import { solveProblem } from '@/napcat-common/helper'; import { solveProblem } from '@/napcat-common/src/helper';
export interface HttpDownloadOptions { export interface HttpDownloadOptions {
url: string; url: string;

View File

@@ -1,8 +1,8 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'fs'; import fs from 'fs';
import os from 'node:os'; import os from 'node:os';
import { QQLevel } from 'napcat-core/index'; import { QQVersionConfigType, QQLevel } from './types';
import { QQVersionConfigType } from './types'; import { RequestUtil } from './request';
export async function solveProblem<T extends (...arg: any[]) => any> (func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> { export async function solveProblem<T extends (...arg: any[]) => any> (func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
return new Promise<ReturnType<T> | undefined>((resolve) => { return new Promise<ReturnType<T> | undefined>((resolve) => {
@@ -212,3 +212,80 @@ export function parseAppidFromMajor (nodeMajor: string): string | undefined {
return undefined; return undefined;
} }
const baseUrl = 'https://github.com/NapNeko/NapCatQQ.git/info/refs?service=git-upload-pack';
const urls = [
'https://j.1win.ggff.net/' + baseUrl,
'https://git.yylx.win/' + baseUrl,
'https://ghfile.geekertao.top/' + baseUrl,
'https://gh-proxy.net/' + baseUrl,
'https://ghm.078465.xyz/' + baseUrl,
'https://gitproxy.127731.xyz/' + baseUrl,
'https://jiashu.1win.eu.org/' + baseUrl,
baseUrl,
];
async function testUrl (url: string): Promise<boolean> {
try {
await PromiseTimer(RequestUtil.HttpGetText(url), 5000);
return true;
} catch {
return false;
}
}
async function findAvailableUrl (): Promise<string | null> {
for (const url of urls) {
if (await testUrl(url)) {
return url;
}
}
return null;
}
export async function getAllTags (): Promise<string[]> {
const availableUrl = await findAvailableUrl();
if (!availableUrl) {
throw new Error('No available URL for fetching tags');
}
const raw = await RequestUtil.HttpGetText(availableUrl);
return raw
.split('\n')
.map(line => {
const match = line.match(/refs\/tags\/(.+)$/);
return match ? match[1] : null;
})
.filter(tag => tag !== null && !tag!.endsWith('^{}')) as string[];
}
export async function getLatestTag (): Promise<string> {
const tags = await getAllTags();
tags.sort((a, b) => compareVersion(a, b));
const latest = tags.at(-1);
if (!latest) {
throw new Error('No tags found');
}
return latest;
}
function compareVersion (a: string, b: string): number {
const normalize = (v: string) =>
v.replace(/^v/, '') // 去掉开头的 v
.split('.')
.map(n => parseInt(n) || 0);
const pa = normalize(a);
const pb = normalize(b);
const len = Math.max(pa.length, pb.length);
for (let i = 0; i < len; i++) {
const na = pa[i] || 0;
const nb = pb[i] || 0;
if (na !== nb) return na - nb;
}
return 0;
}

View File

@@ -0,0 +1,24 @@
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
}
export interface ILogWrapper {
fileLogEnabled: boolean;
consoleLogEnabled: boolean;
cleanOldLogs (logDir: string): void;
setFileAndConsoleLogLevel (fileLogLevel: LogLevel, consoleLogLevel: LogLevel): void;
setLogSelfInfo (selfInfo: { nick: string; uid: string; }): void;
setFileLogEnabled (isEnabled: boolean): void;
setConsoleLogEnabled (isEnabled: boolean): void;
formatMsg (msg: any[]): string;
_log (level: LogLevel, ...args: any[]): void;
log (...args: any[]): void;
logDebug (...args: any[]): void;
logError (...args: any[]): void;
logWarn (...args: any[]): void;
logFatal (...args: any[]): void;
logMessage (msg: unknown, selfInfo: unknown): void;
}

View File

@@ -1,6 +1,5 @@
import { Peer } from 'napcat-core/index';
import crypto from 'crypto'; import crypto from 'crypto';
import { Peer } from './types';
export class LimitedHashTable<K, V> { export class LimitedHashTable<K, V> {
private readonly keyToValue: Map<K, V> = new Map(); private readonly keyToValue: Map<K, V> = new Map();
private readonly valueToKey: Map<V, K> = new Map(); private readonly valueToKey: Map<V, K> = new Map();

View File

@@ -1,317 +0,0 @@
/**
* 性能监控器 - 用于统计函数调用次数、耗时等信息
*/
import * as fs from 'fs';
import * as path from 'path';
export interface FunctionStats {
name: string;
callCount: number;
totalTime: number;
averageTime: number;
minTime: number;
maxTime: number;
fileName?: string;
lineNumber?: number;
}
export class PerformanceMonitor {
private static instance: PerformanceMonitor;
private stats = new Map<string, FunctionStats>();
private startTimes = new Map<string, number>();
private reportInterval: NodeJS.Timeout | null = null;
static getInstance (): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor();
// 启动定时统计报告
PerformanceMonitor.instance.startPeriodicReport();
}
return PerformanceMonitor.instance;
}
/**
* 开始定时统计报告 (每60秒)
*/
private startPeriodicReport (): void {
if (this.reportInterval) {
clearInterval(this.reportInterval);
}
this.reportInterval = setInterval(() => {
if (this.stats.size > 0) {
this.printPeriodicReport();
this.writeDetailedLogToFile();
}
}, 60000); // 60秒
}
/**
* 停止定时统计报告
*/
stopPeriodicReport (): void {
if (this.reportInterval) {
clearInterval(this.reportInterval);
this.reportInterval = null;
}
}
/**
* 打印定时统计报告 (简化版本)
*/
private printPeriodicReport (): void {
const now = new Date().toLocaleString();
console.log(`\n=== 性能监控定时报告 [${now}] ===`);
const totalFunctions = this.stats.size;
const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0);
const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0);
console.log(`📊 总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms`);
// 显示Top 5最活跃的函数
console.log('\n🔥 最活跃函数 (Top 5):');
this.getTopByCallCount(5).forEach((stat, index) => {
console.log(`${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms`);
});
// 显示Top 5最耗时的函数
console.log('\n⏱ 最耗时函数 (Top 5):');
this.getTopByTotalTime(5).forEach((stat, index) => {
console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms`);
});
console.log('===============================\n');
}
/**
* 将详细统计数据写入日志文件
*/
private writeDetailedLogToFile (): void {
try {
const now = new Date();
const dateStr = now.toISOString().replace(/[:.]/g, '-').split('T')[0];
const timeStr = now.toTimeString().split(' ')[0]?.replace(/:/g, '-') || 'unknown-time';
const timestamp = `${dateStr}_${timeStr}`;
const fileName = `${timestamp}.log.txt`;
const logPath = path.join(process.cwd(), 'logs', fileName);
// 确保logs目录存在
const logsDir = path.dirname(logPath);
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
const totalFunctions = this.stats.size;
const totalCalls = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.callCount, 0);
const totalTime = Array.from(this.stats.values()).reduce((sum, stat) => sum + stat.totalTime, 0);
let logContent = '';
logContent += '=== 性能监控详细报告 ===\n';
logContent += `生成时间: ${now.toLocaleString()}\n`;
logContent += '统计周期: 60秒\n';
logContent += `总览: ${totalFunctions} 个函数, ${totalCalls} 次调用, 总耗时: ${totalTime.toFixed(2)}ms\n\n`;
// 详细函数统计
logContent += '=== 所有函数详细统计 ===\n';
const allStats = this.getStats().sort((a, b) => b.totalTime - a.totalTime);
allStats.forEach((stat, index) => {
logContent += `${index + 1}. 函数: ${stat.name}\n`;
logContent += ` 文件: ${stat.fileName || 'N/A'}\n`;
logContent += ` 行号: ${stat.lineNumber || 'N/A'}\n`;
logContent += ` 调用次数: ${stat.callCount}\n`;
logContent += ` 总耗时: ${stat.totalTime.toFixed(4)}ms\n`;
logContent += ` 平均耗时: ${stat.averageTime.toFixed(4)}ms\n`;
logContent += ` 最小耗时: ${stat.minTime === Infinity ? 'N/A' : stat.minTime.toFixed(4)}ms\n`;
logContent += ` 最大耗时: ${stat.maxTime.toFixed(4)}ms\n`;
logContent += ` 性能占比: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`;
logContent += '\n';
});
// 排行榜统计
logContent += '=== 总耗时排行榜 (Top 20) ===\n';
this.getTopByTotalTime(20).forEach((stat, index) => {
logContent += `${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 平均: ${stat.averageTime.toFixed(2)}ms\n`;
});
logContent += '\n=== 调用次数排行榜 (Top 20) ===\n';
this.getTopByCallCount(20).forEach((stat, index) => {
logContent += `${index + 1}. ${stat.name} - 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均: ${stat.averageTime.toFixed(2)}ms\n`;
});
logContent += '\n=== 平均耗时排行榜 (Top 20) ===\n';
this.getTopByAverageTime(20).forEach((stat, index) => {
logContent += `${index + 1}. ${stat.name} - 平均: ${stat.averageTime.toFixed(2)}ms, 调用: ${stat.callCount}次, 总耗时: ${stat.totalTime.toFixed(2)}ms\n`;
});
logContent += '\n=== 性能热点分析 ===\n';
// 找出最耗时的前10个函数
const hotSpots = this.getTopByTotalTime(10);
hotSpots.forEach((stat, index) => {
const efficiency = stat.callCount / stat.totalTime; // 每毫秒的调用次数
logContent += `${index + 1}. ${stat.name}\n`;
logContent += ` 性能影响: ${((stat.totalTime / totalTime) * 100).toFixed(2)}%\n`;
logContent += ` 调用效率: ${efficiency.toFixed(4)} 调用/ms\n`;
logContent += ` 优化建议: ${stat.averageTime > 10
? '考虑优化此函数的执行效率'
: stat.callCount > 1000
? '考虑减少此函数的调用频率'
: '性能表现良好'}\n\n`;
});
logContent += '=== 报告结束 ===\n';
// 写入文件
fs.writeFileSync(logPath, logContent, 'utf8');
console.log(`📄 详细性能报告已保存到: ${logPath}`);
} catch (error) {
console.error('写入性能日志文件时出错:', error);
}
}
/**
* 开始记录函数调用
*/
startFunction (functionName: string, fileName?: string, lineNumber?: number): string {
const callId = `${functionName}_${Date.now()}_${Math.random()}`;
this.startTimes.set(callId, performance.now());
// 初始化或更新统计信息
if (!this.stats.has(functionName)) {
this.stats.set(functionName, {
name: functionName,
callCount: 0,
totalTime: 0,
averageTime: 0,
minTime: Infinity,
maxTime: 0,
fileName,
lineNumber,
});
}
const stat = this.stats.get(functionName)!;
stat.callCount++;
return callId;
}
/**
* 结束记录函数调用
*/
endFunction (callId: string, functionName: string): void {
const startTime = this.startTimes.get(callId);
if (!startTime) return;
const endTime = performance.now();
const duration = endTime - startTime;
this.startTimes.delete(callId);
const stat = this.stats.get(functionName);
if (!stat) return;
stat.totalTime += duration;
stat.averageTime = stat.totalTime / stat.callCount;
stat.minTime = Math.min(stat.minTime, duration);
stat.maxTime = Math.max(stat.maxTime, duration);
}
/**
* 获取所有统计信息
*/
getStats (): FunctionStats[] {
return Array.from(this.stats.values());
}
/**
* 获取排行榜 - 按总耗时排序
*/
getTopByTotalTime (limit = 20): FunctionStats[] {
return this.getStats()
.sort((a, b) => b.totalTime - a.totalTime)
.slice(0, limit);
}
/**
* 获取排行榜 - 按调用次数排序
*/
getTopByCallCount (limit = 20): FunctionStats[] {
return this.getStats()
.sort((a, b) => b.callCount - a.callCount)
.slice(0, limit);
}
/**
* 获取排行榜 - 按平均耗时排序
*/
getTopByAverageTime (limit = 20): FunctionStats[] {
return this.getStats()
.sort((a, b) => b.averageTime - a.averageTime)
.slice(0, limit);
}
/**
* 清空统计数据
*/
clear (): void {
this.stats.clear();
this.startTimes.clear();
}
/**
* 打印统计报告
*/
printReport (): void {
console.log('\n=== 函数性能监控报告 ===');
console.log('\n🔥 总耗时排行榜 (Top 10):');
this.getTopByTotalTime(10).forEach((stat, index) => {
console.log(`${index + 1}. ${stat.name} - 总耗时: ${stat.totalTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 平均耗时: ${stat.averageTime.toFixed(2)}ms`);
});
console.log('\n📈 调用次数排行榜 (Top 10):');
this.getTopByCallCount(10).forEach((stat, index) => {
console.log(`${index + 1}. ${stat.name} - 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms, 平均耗时: ${stat.averageTime.toFixed(2)}ms`);
});
console.log('\n⏱ 平均耗时排行榜 (Top 10):');
this.getTopByAverageTime(10).forEach((stat, index) => {
console.log(`${index + 1}. ${stat.name} - 平均耗时: ${stat.averageTime.toFixed(2)}ms, 调用次数: ${stat.callCount}, 总耗时: ${stat.totalTime.toFixed(2)}ms`);
});
console.log('\n========================\n');
}
/**
* 获取JSON格式的统计数据
*/
toJSON (): FunctionStats[] {
return this.getStats();
}
}
// 全局性能监控器实例
export const performanceMonitor = PerformanceMonitor.getInstance();
// 在进程退出时打印报告并停止定时器
if (typeof process !== 'undefined') {
process.on('exit', () => {
performanceMonitor.stopPeriodicReport();
performanceMonitor.printReport();
});
process.on('SIGINT', () => {
performanceMonitor.stopPeriodicReport();
performanceMonitor.printReport();
process.exit(0);
});
process.on('SIGTERM', () => {
performanceMonitor.stopPeriodicReport();
performanceMonitor.printReport();
process.exit(0);
});
}

View File

@@ -0,0 +1,24 @@
export interface SystemStatus {
cpu: {
model: string,
speed: string;
usage: {
system: string;
qq: string;
},
core: number;
},
memory: {
total: string;
usage: {
system: string;
qq: string;
};
},
arch: string;
}
export interface IStatusHelperSubscription {
on (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
off (event: 'statusUpdate', listener: (status: SystemStatus) => void): this;
emit (event: 'statusUpdate', status: SystemStatus): boolean;
}

View File

@@ -19,4 +19,4 @@ class Store {
const store = new Store(); const store = new Store();
export default store; export default store;

View File

@@ -0,0 +1,6 @@
export type LogListener = (msg: string) => void;
export interface ISubscription {
subscribe (listener: LogListener): void;
unsubscribe (listener: LogListener): void;
notify (msg: string): void;
}

View File

@@ -15,3 +15,14 @@ export type QQVersionConfigType = {
export type QQAppidTableType = { export type QQAppidTableType = {
[key: string]: { appid: string, qua: string }; [key: string]: { appid: string, qua: string };
}; };
export interface Peer {
chatType: number; // 聊天类型
peerUid: string; // 对等方的唯一标识符
guildId?: string; // 可选的频道ID
}
export interface QQLevel {
crownNum: number;
sunNum: number;
moonNum: number;
starNum: number;
}

View File

@@ -1,3 +1,2 @@
// @ts-ignore // @ts-ignore
export const napCatVersion = (typeof import.meta?.env !== 'undefined' && import.meta.env.VITE_NAPCAT_VERSION) || 'alpha'; export const napCatVersion = (typeof import.meta?.env !== 'undefined' && import.meta.env.VITE_NAPCAT_VERSION) || 'alpha';

View File

@@ -11,11 +11,11 @@
], ],
"esModuleInterop": true, "esModuleInterop": true,
"outDir": "dist", "outDir": "dist",
"rootDir": "src", "rootDir": ".",
"noEmit": false, "noEmit": false,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"noImplicitAny": true, "noImplicitAny": false,
"strictFunctionTypes": true, "strictFunctionTypes": true,
"strictBindCallApply": true, "strictBindCallApply": true,
"alwaysStrict": true, "alwaysStrict": true,
@@ -36,8 +36,8 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/napcat-common/*": [ "@/*": [
"src/*" "../*"
] ]
}, },
"skipLibCheck": true, "skipLibCheck": true,

View File

@@ -24,4 +24,4 @@ export class NodeIDependsAdapter {
// console.log('[NodeIDependsAdapter] onSendMsfReply', _seq, _cmd, _uk1, _uk2, Buffer.from(_rsp.pbBuffer).toString('hex')); // console.log('[NodeIDependsAdapter] onSendMsfReply', _seq, _cmd, _uk1, _uk2, Buffer.from(_rsp.pbBuffer).toString('hex'));
// } // }
} }

View File

@@ -5,12 +5,7 @@ import {
IMAGE_HTTP_HOST_NT, IMAGE_HTTP_HOST_NT,
Peer, Peer,
PicElement, PicElement,
PicSubType,
RawMessage, RawMessage,
SendFileElement,
SendPicElement,
SendPttElement,
SendVideoElement,
} from '@/napcat-core/types'; } from '@/napcat-core/types';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
@@ -19,16 +14,9 @@ import { InstanceContext, NapCatCore, SearchResultItem } from '@/napcat-core/ind
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import { RkeyManager } from '@/napcat-core/helper/rkey'; import { RkeyManager } from '@/napcat-core/helper/rkey';
import { calculateFileMD5 } from 'napcat-common/src/file'; import { calculateFileMD5 } from 'napcat-common/src/file';
import pathLib from 'node:path';
import { defaultVideoThumbB64 } from 'napcat-common/src/video';
import { encodeSilk } from 'napcat-common/src/audio';
import { SendMessageContext } from 'napcat-onebot/api/msg';
import { getFileTypeForSendType } from '../helper/msg';
import { FFmpegService } from 'napcat-common/src/ffmpeg';
import { rkeyDataType } from '../types/file'; import { rkeyDataType } from '../types/file';
import { NapProtoMsg } from 'napcat-protobuf'; import { NapProtoMsg } from 'napcat-protobuf';
import { FileId } from '../packet/transformer/proto/misc/fileid'; import { FileId } from '../packet/transformer/proto/misc/fileid';
import { imageSizeFallBack } from 'napcat-image-size';
export class NTQQFileApi { export class NTQQFileApi {
context: InstanceContext; context: InstanceContext;
@@ -45,7 +33,7 @@ export class NTQQFileApi {
'http://ss.xingzhige.com/music_card/rkey', 'http://ss.xingzhige.com/music_card/rkey',
'https://secret-service.bietiaop.com/rkeys', 'https://secret-service.bietiaop.com/rkeys',
], ],
this.context.logger this.context.logger
); );
} }
@@ -181,165 +169,6 @@ export class NTQQFileApi {
}; };
} }
async createValidSendFileElement (context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
const {
fileName: _fileName,
path,
fileSize,
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.FILE,
elementId: '',
fileElement: {
fileName: fileName || _fileName,
folderId,
filePath: path,
fileSize: fileSize.toString(),
},
};
}
async createValidSendPicElement (context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const imageSize = await imageSizeFallBack(picPath);
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.PIC,
elementId: '',
picElement: {
md5HexStr: md5,
fileSize: fileSize.toString(),
picWidth: imageSize.width,
picHeight: imageSize.height,
fileName,
sourcePath: path,
original: true,
picType: await getFileTypeForSendType(picPath),
picSubType: subType,
fileUuid: '',
fileSubId: '',
thumbFileSize: 0,
summary,
} as PicElement,
};
}
async createValidSendVideoElement (context: SendMessageContext, filePath: string, fileName: string = '', _diyThumbPath: string = ''): Promise<SendVideoElement> {
let videoInfo = {
width: 1920,
height: 1080,
time: 15,
format: 'mp4',
size: 0,
filePath,
};
let fileExt = 'mp4';
try {
const tempExt = (await fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt;
} catch (e) {
this.context.logger.logError('获取文件类型失败', e);
}
const newFilePath = `${filePath}.${fileExt}`;
fs.copyFileSync(filePath, newFilePath);
context.deleteAfterSentFiles.push(newFilePath);
filePath = newFilePath;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
context.deleteAfterSentFiles.push(path);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
fs.mkdirSync(pathLib.dirname(thumbDir), { recursive: true });
const thumbPath = pathLib.join(pathLib.dirname(thumbDir), `${md5}_0.png`);
try {
videoInfo = await FFmpegService.getVideoInfo(filePath, thumbPath);
if (!fs.existsSync(thumbPath)) {
this.context.logger.logError('获取视频缩略图失败', new Error('缩略图不存在'));
throw new Error('获取视频缩略图失败');
}
} catch (e) {
this.context.logger.logError('获取视频信息失败', e);
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
}
if (_diyThumbPath) {
try {
await this.copyFile(_diyThumbPath, thumbPath);
} catch (e) {
this.context.logger.logError('复制自定义缩略图失败', e);
}
}
context.deleteAfterSentFiles.push(thumbPath);
const thumbSize = (await fsPromises.stat(thumbPath)).size;
const thumbMd5 = await calculateFileMD5(thumbPath);
context.deleteAfterSentFiles.push(thumbPath);
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith(`.${fileExt.toLocaleLowerCase()}`) ? (fileName || _fileName) : `${fileName || _fileName}.${fileExt}`;
return {
elementType: ElementType.VIDEO,
elementId: '',
videoElement: {
fileName: uploadName,
filePath: path,
videoMd5: md5,
thumbMd5,
fileTime: videoInfo.time,
thumbPath: new Map([[0, thumbPath]]),
thumbSize,
thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height,
fileSize: fileSize.toString(),
},
};
}
async createValidSendPttElement (_context: SendMessageContext, pttPath: string): Promise<SendPttElement> {
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
if (!silkPath) {
throw new Error('语音转换失败, 请检查语音文件是否正常');
}
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
if (converted) {
fsPromises.unlink(silkPath).then().catch((e) => this.context.logger.logError('删除临时文件失败', e));
}
return {
elementType: ElementType.PTT,
elementId: '',
pttElement: {
fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize.toString(),
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
voiceChangeType: 0,
canConvert2Text: true,
waveAmplitudes: [
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
],
fileSubId: '',
playState: 1,
autoConvertText: 0,
storeID: 0,
otherBusinessInfo: {
aiVoiceType: 0,
},
},
};
}
async downloadFileForModelId (peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) { async downloadFileForModelId (peer: Peer, modelId: string, unknown: string, timeout = 1000 * 60 * 2) {
const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2( const [, fileTransNotifyInfo] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelRichMediaService/downloadFileForModelId', 'NodeIKernelRichMediaService/downloadFileForModelId',

View File

@@ -1,5 +1,5 @@
import { FriendRequest, FriendV2 } from 'napcat-core/types'; import { FriendRequest, FriendV2 } from '@/napcat-core/types';
import { BuddyListReqType, InstanceContext, NapCatCore } from 'napcat-core/index'; import { BuddyListReqType, InstanceContext, NapCatCore } from '@/napcat-core/index';
import { LimitedHashTable } from 'napcat-common/src/message-unique'; import { LimitedHashTable } from 'napcat-common/src/message-unique';
export class NTQQFriendApi { export class NTQQFriendApi {

View File

@@ -15,9 +15,9 @@ import {
} from '@/napcat-core/index'; } from '@/napcat-core/index';
import { isNumeric, solveAsyncProblem } from 'napcat-common/src/helper'; import { isNumeric, solveAsyncProblem } from 'napcat-common/src/helper';
import { LimitedHashTable } from 'napcat-common/src/message-unique'; import { LimitedHashTable } from 'napcat-common/src/message-unique';
import { NTEventWrapper } from 'napcat-common/src/event';
import { CancelableTask, TaskExecutor } from 'napcat-common/src/cancel-task'; import { CancelableTask, TaskExecutor } from 'napcat-common/src/cancel-task';
import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data'; import { createGroupDetailInfoV2Param, createGroupExtFilter, createGroupExtInfo } from '../data';
import { NTEventWrapper } from '../helper/event';
export class NTQQGroupApi { export class NTQQGroupApi {
context: InstanceContext; context: InstanceContext;
@@ -395,7 +395,7 @@ export class NTQQGroupApi {
'NodeIKernelGroupListener/onMemberInfoChange', 'NodeIKernelGroupListener/onMemberInfoChange',
[groupCode, [uid], forced], [groupCode, [uid], forced],
(ret) => ret.result === 0, (ret) => ret.result === 0,
(params, _, members) => params === GroupCode && members.size > 0 && members.has(uid), (params: string, _: any, members: Map<string, GroupMember>) => params === GroupCode && members.size > 0 && members.has(uid),
1, 1,
forced ? 2500 : 250 forced ? 2500 : 250
); );

View File

@@ -1,9 +1,9 @@
import * as os from 'os'; import * as os from 'os';
import offset from '@/napcat-core/external/napi2native.json'; import offset from '@/napcat-core/external/napi2native.json';
import { InstanceContext, NapCatCore } from '@/napcat-core/index'; import { InstanceContext, NapCatCore } from '@/napcat-core/index';
import { LogWrapper } from 'napcat-common/src/log';
import { PacketClientSession } from '@/napcat-core/packet/clientSession'; import { PacketClientSession } from '@/napcat-core/packet/clientSession';
import { napCatVersion } from 'napcat-common/src/version'; import { napCatVersion } from 'napcat-common/src/version';
import { LogWrapper } from '../helper/log';
interface OffsetType { interface OffsetType {
[key: string]: { [key: string]: {

View File

@@ -2,10 +2,10 @@ import fsPromise from 'fs/promises';
import path from 'node:path'; import path from 'node:path';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm'; import { EncodeResult, getDuration, getWavFileInfo, isSilk, isWav } from 'silk-wasm';
import { LogWrapper } from '@/napcat-common/log'; import { LogWrapper } from '@/napcat-core/helper/log';
import { EncodeArgs } from '@/napcat-common/audio-worker'; import { EncodeArgs } from 'napcat-common/src/audio-worker';
import { FFmpegService } from '@/napcat-common/ffmpeg'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
import { runTask } from './worker'; import { runTask } from 'napcat-common/src/worker';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000]; const ALLOW_SAMPLE_RATE = [8000, 12000, 16000, 24000, 32000, 44100, 48000];

View File

@@ -1,6 +1,6 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import type { NapCatCore } from 'napcat-core'; import type { NapCatCore } from '@/napcat-core';
import json5 from 'json5'; import json5 from 'json5';
import Ajv, { AnySchema, ValidateFunction } from 'ajv'; import Ajv, { AnySchema, ValidateFunction } from 'ajv';

View File

@@ -1,4 +1,4 @@
import { ConfigBase } from 'napcat-common/src/config-base'; import { ConfigBase } from '@/napcat-core/helper/config-base';
import { NapCatCore } from '@/napcat-core/index'; import { NapCatCore } from '@/napcat-core/index';
import { Type, Static } from '@sinclair/typebox'; import { Type, Static } from '@sinclair/typebox';
import { AnySchema } from 'ajv'; import { AnySchema } from 'ajv';

View File

@@ -1,6 +1,6 @@
import { NodeIQQNTWrapperSession } from 'napcat-core/wrapper'; import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { ListenerNamingMapping, ServiceNamingMapping } from 'napcat-core/index'; import { ListenerNamingMapping, ServiceNamingMapping } from '@/napcat-core/index';
interface InternalMapKey { interface InternalMapKey {
timeout: number; timeout: number;
@@ -54,7 +54,7 @@ export class NTEventWrapper {
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]> T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>
> (eventName: `${Service}/${ServiceMethod}`): T | undefined { >(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/'); const eventNameArr = eventName.split('/');
type eventType = { type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>>; }; [key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>>; };
@@ -78,18 +78,15 @@ export class NTEventWrapper {
return undefined; return undefined;
} }
createListenerFunction<T, Listener extends keyof ListenerNamingMapping, createListenerFunction<T> (listenerMainName: string, uniqueCode: string = ''): T {
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>> (listenerMainName: string, uniqueCode: string = ''): T {
const existListener = this.listenerManager.get(listenerMainName + uniqueCode); const existListener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!existListener) { if (!existListener) {
const Listener = this.createProxyDispatch(listenerMainName); const Listener = this.createProxyDispatch(listenerMainName);
const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1]; const ServiceSubName = /^NodeIKernel(.*?)Listener$/.exec(listenerMainName)![1];
const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener` as `${Listener}/${ListenerMethod}`; const Service = `NodeIKernel${ServiceSubName}Service/addKernel${ServiceSubName}Listener`;
this.createEventFunction<
keyof ServiceNamingMapping, // @ts-ignore
FuncKeys<ServiceNamingMapping[keyof ServiceNamingMapping]>, this.createEventFunction(Service)(Listener as T);
(...args: any[]) => any
>(Service as `${keyof ServiceNamingMapping}/${FuncKeys<ServiceNamingMapping[keyof ServiceNamingMapping]>}`);
this.listenerManager.set(listenerMainName + uniqueCode, Listener); this.listenerManager.set(listenerMainName + uniqueCode, Listener);
return Listener as T; return Listener as T;
} }
@@ -115,7 +112,7 @@ export class NTEventWrapper {
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]> EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>
> ( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType> ...args: Parameters<EventType>
): Promise<Awaited<ReturnType<EventType>>> { ): Promise<Awaited<ReturnType<EventType>>> {
@@ -126,7 +123,7 @@ export class NTEventWrapper {
Listener extends keyof ListenerNamingMapping, Listener extends keyof ListenerNamingMapping,
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]> ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
> ( >(
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,
checker: (...args: Parameters<ListenerType>) => boolean, checker: (...args: Parameters<ListenerType>) => boolean,
waitTimes = 1, waitTimes = 1,
@@ -180,7 +177,7 @@ export class NTEventWrapper {
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>, EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]> ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
> ( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,
args: Parameters<EventType>, args: Parameters<EventType>,

View File

@@ -6,7 +6,7 @@ import * as os from 'os';
import * as compressing from 'compressing'; // 修正导入方式 import * as compressing from 'compressing'; // 修正导入方式
import { pipeline } from 'stream/promises'; import { pipeline } from 'stream/promises';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { LogWrapper } from './log'; import { LogWrapper } from '@/napcat-core/helper/log';
const downloadOri = 'https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip'; const downloadOri = 'https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip';
const urls = [ const urls = [

View File

@@ -3,7 +3,7 @@
* FFmpeg * FFmpeg
*/ */
import { LogWrapper } from './log'; import { LogWrapper } from '@/napcat-core/helper/log';
import { FFmpegAddonAdapter } from './ffmpeg-addon-adapter'; import { FFmpegAddonAdapter } from './ffmpeg-addon-adapter';
import { FFmpegExecAdapter } from './ffmpeg-exec-adapter'; import { FFmpegExecAdapter } from './ffmpeg-exec-adapter';
import type { IFFmpegAdapter } from './ffmpeg-adapter-interface'; import type { IFFmpegAdapter } from './ffmpeg-adapter-interface';

View File

@@ -68,13 +68,13 @@ export class FFmpegAddonAdapter implements IFFmpegAdapter {
const addon = this.ensureAddon(); const addon = this.ensureAddon();
const info = await addon.getVideoInfo(videoPath); const info = await addon.getVideoInfo(videoPath);
let format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format; const format = info.format.includes(',') ? info.format.split(',')[0] ?? info.format : info.format;
console.log('[FFmpegAddonAdapter] Detected format:', format); console.log('[FFmpegAddonAdapter] Detected format:', format);
return { return {
width: info.width, width: info.width,
height: info.height, height: info.height,
duration: info.duration, duration: info.duration,
format: format, format,
thumbnail: info.image, thumbnail: info.image,
}; };
} }

View File

@@ -10,7 +10,7 @@ import { promisify } from 'util';
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import { imageSizeFallBack } from 'napcat-image-size/src/index'; import { imageSizeFallBack } from 'napcat-image-size/src/index';
import { downloadFFmpegIfNotExists } from './download-ffmpeg'; import { downloadFFmpegIfNotExists } from './download-ffmpeg';
import { LogWrapper } from './log'; import { LogWrapper } from '@/napcat-core/helper/log';
import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface'; import type { IFFmpegAdapter, VideoInfoResult } from './ffmpeg-adapter-interface';
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);

View File

@@ -3,7 +3,7 @@ import path from 'path';
import type { VideoInfo } from './video'; import type { VideoInfo } from './video';
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import { platform } from 'node:os'; import { platform } from 'node:os';
import { LogWrapper } from './log'; import { LogWrapper } from '@/napcat-core/helper/log';
import { FFmpegAdapterFactory } from './ffmpeg-adapter-factory'; import { FFmpegAdapterFactory } from './ffmpeg-adapter-factory';
import type { IFFmpegAdapter } from './ffmpeg-adapter-interface'; import type { IFFmpegAdapter } from './ffmpeg-adapter-interface';
@@ -53,7 +53,6 @@ export class FFmpegService {
throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.'); throw new Error('FFmpeg service not initialized. Please call FFmpegService.init() first.');
} }
return this.adapter.name; return this.adapter.name;
} }
/** /**

View File

@@ -1,5 +1,5 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import { PacketMsg } from 'napcat-core/packet/message/message'; import { PacketMsg } from '@/napcat-core/packet/message/message';
interface ForwardMsgJson { interface ForwardMsgJson {
app: string app: string

View File

@@ -1,8 +1,9 @@
import winston, { format, transports } from 'winston'; import winston, { format, transports } from 'winston';
import { truncateString } from './helper'; import { truncateString } from 'napcat-common/src/helper';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from 'napcat-core/index'; import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/napcat-core/index';
import { ILogWrapper } from 'napcat-common/src/log-interface';
import EventEmitter from 'node:events'; import EventEmitter from 'node:events';
export enum LogLevel { export enum LogLevel {
DEBUG = 'debug', DEBUG = 'debug',
@@ -56,7 +57,7 @@ class Subscription {
export const logSubscription = new Subscription(); export const logSubscription = new Subscription();
export class LogWrapper { export class LogWrapper implements ILogWrapper {
fileLogEnabled = true; fileLogEnabled = true;
consoleLogEnabled = true; consoleLogEnabled = true;
logger: winston.Logger; logger: winston.Logger;

View File

@@ -1,4 +1,4 @@
import { LogWrapper } from '@/napcat-common/log'; import { LogWrapper } from '@/napcat-core/helper/log';
export function proxyHandlerOf (logger: LogWrapper) { export function proxyHandlerOf (logger: LogWrapper) {
return { return {

View File

@@ -1,10 +1,10 @@
import fs from 'node:fs'; import fs from 'node:fs';
import { systemPlatform } from '@/napcat-common/system'; import { systemPlatform } from 'napcat-common/src/system';
import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from './helper'; import { getDefaultQQVersionConfigInfo, getQQPackageInfoPath, getQQVersionConfigPath, parseAppidFromMajor } from 'napcat-common/src/helper';
import AppidTable from 'napcat-core/external/appid.json'; import AppidTable from '@/napcat-core/external/appid.json';
import { LogWrapper } from '@/napcat-common/log'; import { LogWrapper } from './log';
import { getMajorPath } from 'napcat-core'; import { getMajorPath } from '@/napcat-core/index';
import { QQAppidTableType, QQPackageInfoType, QQVersionConfigType } from './types'; import { QQAppidTableType, QQPackageInfoType, QQVersionConfigType } from 'napcat-common/src/types';
export class QQBasicInfoWrapper { export class QQBasicInfoWrapper {
QQMainPath: string | undefined; QQMainPath: string | undefined;

View File

@@ -1,5 +1,5 @@
import { LogWrapper } from 'napcat-common/src/log';
import { RequestUtil } from 'napcat-common/src/request'; import { RequestUtil } from 'napcat-common/src/request';
import { LogWrapper } from './log';
interface ServerRkeyData { interface ServerRkeyData {
group_rkey: string; group_rkey: string;

View File

@@ -1,24 +1,24 @@
import os from 'node:os'; import os from 'node:os';
import EventEmitter from 'node:events'; import EventEmitter from 'node:events';
import { IStatusHelperSubscription } from 'napcat-common/src/status-interface';
export interface SystemStatus { export interface SystemStatus {
cpu: { cpu: {
model: string, model: string,
speed: string speed: string;
usage: { usage: {
system: string system: string;
qq: string qq: string;
}, },
core: number core: number;
}, },
memory: { memory: {
total: string total: string;
usage: { usage: {
system: string system: string;
qq: string qq: string;
} };
}, },
arch: string arch: string;
} }
export class StatusHelper { export class StatusHelper {
@@ -101,7 +101,7 @@ export class StatusHelper {
} }
} }
class StatusHelperSubscription extends EventEmitter { class StatusHelperSubscription extends EventEmitter implements IStatusHelperSubscription {
private statusHelper: StatusHelper; private statusHelper: StatusHelper;
private interval: NodeJS.Timeout | null = null; private interval: NodeJS.Timeout | null = null;

View File

@@ -6,7 +6,7 @@ import {
NTQQSystemApi, NTQQSystemApi,
NTQQUserApi, NTQQUserApi,
NTQQWebApi, NTQQWebApi,
} from 'napcat-core/apis'; } from '@/napcat-core/apis';
import { NTQQCollectionApi } from '@/napcat-core/apis/collection'; import { NTQQCollectionApi } from '@/napcat-core/apis/collection';
import { import {
NodeIQQNTWrapperSession, NodeIQQNTWrapperSession,
@@ -16,21 +16,24 @@ import {
WrapperNodeApi, WrapperNodeApi,
WrapperSessionInitConfig, WrapperSessionInitConfig,
} from '@/napcat-core/wrapper'; } from '@/napcat-core/wrapper';
import { LogLevel, LogWrapper } from 'napcat-common/src/log'; import { LogLevel, LogWrapper } from '@/napcat-core/helper/log';
import { NodeIKernelLoginService } from '@/napcat-core/services'; import { NodeIKernelLoginService } from '@/napcat-core/services';
import { QQBasicInfoWrapper } from 'napcat-common/src/qq-basic-info'; import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
import { NapCatPathWrapper } from 'napcat-common/src/path'; import { NapCatPathWrapper } from 'napcat-common/src/path';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import { hostname, systemName, systemVersion } from 'napcat-common/src/system'; import { hostname, systemName, systemVersion } from 'napcat-common/src/system';
import { NTEventWrapper } from 'napcat-common/src/event'; import { NTEventWrapper } from '@/napcat-core/helper/event';
import { KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/napcat-core/types'; import { KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/napcat-core/types';
import { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config'; import { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config';
import os from 'node:os'; import os from 'node:os';
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-core/listeners'; import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-core/listeners';
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler'; import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
import { NTQQPacketApi } from './apis/packet'; import { NTQQPacketApi } from './apis/packet';
import { NativePacketHandler } from './packet/handler/client'; import { NativePacketHandler } from './packet/handler/client';
import { container, ReceiverServiceRegistry } from './packet/handler/serviceRegister';
import { appEvent } from './packet/handler/eventList';
import { TypedEventEmitter } from './packet/handler/typeEvent';
export * from './wrapper'; export * from './wrapper';
export * from './types/index'; export * from './types/index';
export * from './services/index'; export * from './services/index';
@@ -92,6 +95,7 @@ export function getMajorPath (QQVersion: string): string {
export class NapCatCore { export class NapCatCore {
readonly context: InstanceContext; readonly context: InstanceContext;
readonly eventWrapper: NTEventWrapper; readonly eventWrapper: NTEventWrapper;
event = appEvent;
NapCatDataPath: string = ''; NapCatDataPath: string = '';
NapCatTempPath: string = ''; NapCatTempPath: string = '';
apis: StableNTApiWrapper; apis: StableNTApiWrapper;
@@ -118,6 +122,16 @@ export class NapCatCore {
UserApi: new NTQQUserApi(this.context, this), UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this), GroupApi: new NTQQGroupApi(this.context, this),
}; };
container.bind(NapCatCore).toConstantValue(this);
container.bind(TypedEventEmitter).toConstantValue(this.event);
ReceiverServiceRegistry.forEach((ServiceClass, serviceName) => {
container.bind(ServiceClass).toSelf();
console.log(`Registering service handler for: ${serviceName}`);
this.context.packetHandler.onCmd(serviceName, ({ seq, hex_data }) => {
const serviceInstance = container.get(ServiceClass);
return serviceInstance.handler(seq, hex_data);
});
});
} }
async initCore () { async initCore () {

View File

@@ -1,5 +1,5 @@
import { ChatType, KickedOffLineInfo, RawMessage } from '@/napcat-core/types'; import { ChatType, KickedOffLineInfo, RawMessage } from '@/napcat-core/types';
import { CommonFileInfo } from 'napcat-core/index'; import { CommonFileInfo } from '@/napcat-core/index';
export interface OnRichMediaDownloadCompleteParams { export interface OnRichMediaDownloadCompleteParams {
fileModelId: string, fileModelId: string,

View File

@@ -4,6 +4,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"main": "index.ts", "main": "index.ts",
"scripts": {
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
},
"exports": { "exports": {
".": { ".": {
"import": "./index.ts" "import": "./index.ts"
@@ -13,15 +16,18 @@
} }
}, },
"dependencies": { "dependencies": {
"winston": "^3.17.0",
"json5": "^2.2.3",
"inversify": "^7.10.4",
"reflect-metadata": "^0.2.2",
"@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1",
"napcat-protobuf": "workspace:*",
"ajv": "^8.13.0", "ajv": "^8.13.0",
"@sinclair/typebox": "^0.34.38", "@sinclair/typebox": "^0.34.38",
"file-type": "^21.0.0", "file-type": "^21.0.0",
"compressing": "^1.10.1",
"napcat-image-size": "workspace:*", "napcat-image-size": "workspace:*",
"napcat-core": "workspace:*",
"napcat-common": "workspace:*", "napcat-common": "workspace:*",
"napcat-onebot": "workspace:*" "napcat-protobuf": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.0.1" "@types/node": "^22.0.1"

View File

@@ -1,5 +1,5 @@
import { LogLevel, LogWrapper } from 'napcat-common/src/log';
import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext'; import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext';
import { LogWrapper, LogLevel } from '@/napcat-core/helper/log';
// TODO: check bind? // TODO: check bind?
export class PacketLogger { export class PacketLogger {

View File

@@ -9,7 +9,7 @@ import {
PacketMsgReplyElement, PacketMsgReplyElement,
PacketMsgVideoElement, PacketMsgVideoElement,
} from '@/napcat-core/packet/message/element'; } from '@/napcat-core/packet/message/element';
import { ChatType, MsgSourceType, NTMsgType, RawMessage } from 'napcat-core/index'; import { ChatType, MsgSourceType, NTMsgType, RawMessage } from '@/napcat-core/index';
import { MiniAppRawData, MiniAppReqParams } from '@/napcat-core/packet/entities/miniApp'; import { MiniAppRawData, MiniAppReqParams } from '@/napcat-core/packet/entities/miniApp';
import { AIVoiceChatType } from '@/napcat-core/packet/entities/aiChat'; import { AIVoiceChatType } from '@/napcat-core/packet/entities/aiChat';
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from 'napcat-protobuf'; import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from 'napcat-protobuf';

View File

@@ -1,5 +1,5 @@
import { PacketHighwayContext } from '@/napcat-core/packet/highway/highwayContext'; import { PacketHighwayContext } from '@/napcat-core/packet/highway/highwayContext';
import { NapCatCore } from 'napcat-core/index'; import { NapCatCore } from '@/napcat-core/index';
import { PacketLogger } from '@/napcat-core/packet/context/loggerContext'; import { PacketLogger } from '@/napcat-core/packet/context/loggerContext';
import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext'; import { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext';
import { PacketClientContext } from '@/napcat-core/packet/context/clientContext'; import { PacketClientContext } from '@/napcat-core/packet/context/clientContext';

View File

@@ -2,8 +2,8 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs'; import fs from 'fs';
import { constants } from 'node:os'; import { constants } from 'node:os';
import { LogWrapper } from 'napcat-common/src/log';
import offset from '@/napcat-core/external/packet.json'; import offset from '@/napcat-core/external/packet.json';
import { LogWrapper } from '../../helper/log';
interface OffsetType { interface OffsetType {
[key: string]: { [key: string]: {
recv: string; recv: string;
@@ -50,7 +50,6 @@ export class NativePacketHandler {
this.logger.logError('NativePacketClient 加载出错:', error); this.logger.logError('NativePacketClient 加载出错:', error);
this.loaded = false; this.loaded = false;
} }
} }
/** /**

View File

@@ -0,0 +1,6 @@
import { TypedEventEmitter } from './typeEvent';
export interface AppEvents {
'event:emoji_like': { groupId: string; senderUin: string; emojiId: string, msgSeq: string, isAdd: boolean, count: number };
}
export const appEvent = new TypedEventEmitter<AppEvents>();

View File

@@ -0,0 +1,28 @@
import 'reflect-metadata';
import { Container, injectable } from 'inversify';
import { NapCatCore } from '../..';
import { TypedEventEmitter } from './typeEvent';
export const container = new Container();
export const ReceiverServiceRegistry = new Map<string, new (...args: any[]) => ServiceBase>();
export abstract class ServiceBase {
get core (): NapCatCore {
return container.get(NapCatCore);
}
get event () {
return container.get(TypedEventEmitter);
}
abstract handler (seq: number, hex_data: string): Promise<void> | void;
}
export function ReceiveService (serviceName: string) {
return function <T extends new (...args: any[]) => ServiceBase>(constructor: T) {
injectable()(constructor);
ReceiverServiceRegistry.set(serviceName, constructor);
return constructor;
};
}

View File

@@ -0,0 +1,22 @@
import { EventEmitter } from 'node:events';
export class TypedEventEmitter<E extends Record<string, any>> {
private emitter = new EventEmitter();
on<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
this.emitter.on(event as string, listener);
return () => this.off(event, listener);
}
once<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
this.emitter.once(event as string, listener);
}
off<K extends keyof E>(event: K, listener: (payload: E[K]) => void) {
this.emitter.off(event as string, listener);
}
emit<K extends keyof E>(event: K, payload: E[K]) {
this.emitter.emit(event as string, payload);
}
}

View File

@@ -32,8 +32,8 @@ import {
SendTextElement, SendTextElement,
SendVideoElement, SendVideoElement,
Peer, Peer,
} from 'napcat-core/index'; } from '@/napcat-core/index';
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder'; import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
import { PacketMsg, PacketSendMsgElement } from '@/napcat-core/packet/message/message'; import { PacketMsg, PacketSendMsgElement } from '@/napcat-core/packet/message/message';
export type ParseElementFnR = [MessageElement, NapProtoDecodeStructType<typeof Elem> | null] | undefined; export type ParseElementFnR = [MessageElement, NapProtoDecodeStructType<typeof Elem> | null] | undefined;

View File

@@ -1,5 +1,5 @@
import { IPacketMsgElement } from '@/napcat-core/packet/message/element'; import { IPacketMsgElement } from '@/napcat-core/packet/message/element';
import { SendMessageElement, SendMultiForwardMsgElement } from 'napcat-core/index'; import { SendMessageElement, SendMultiForwardMsgElement } from '@/napcat-core/index';
export type PacketSendMsgElement = SendMessageElement | SendMultiForwardMsgElement; export type PacketSendMsgElement = SendMessageElement | SendMultiForwardMsgElement;

View File

@@ -0,0 +1,36 @@
import { NapProtoMsg } from 'napcat-protobuf';
import { ReceiveService, ServiceBase } from '../packet/handler/serviceRegister';
import { GroupReactNotify, PushMsg } from '../packet/transformer/proto';
@ReceiveService('trpc.msg.olpush.OlPushService.MsgPush')
export class OlPushService extends ServiceBase {
async handler (_seq: number, hex_data: string) {
const data = new NapProtoMsg(PushMsg).decode(Buffer.from(hex_data, 'hex'));
if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) {
const pbNotify = data.message.body?.msgContent?.slice(7);
if (!pbNotify) {
return;
}
// 开始解析Notify
const notify = new NapProtoMsg(GroupReactNotify).decode(pbNotify);
if ((notify.field13 ?? 0) === 35) {
// Group React Notify
const groupCode = notify.groupUin?.toString() ?? '';
const operatorUid = notify.groupReactionData?.data?.data?.groupReactionDataContent?.operatorUid ?? '';
const type = notify.groupReactionData?.data?.data?.groupReactionDataContent?.type ?? 0;
const seq = notify.groupReactionData?.data?.data?.groupReactionTarget?.seq?.toString() ?? '';
const code = notify.groupReactionData?.data?.data?.groupReactionDataContent?.code ?? '';
const count = notify.groupReactionData?.data?.data?.groupReactionDataContent?.count ?? 0;
const senderUin = await this.core.apis.UserApi.getUinByUidV2(operatorUid);
this.event.emit('event:emoji_like', {
groupId: groupCode,
senderUin,
emojiId: code,
msgSeq: seq,
isAdd: type === 1,
count,
});
}
}
}
}

View File

@@ -24,7 +24,6 @@ export interface NodeIKernelBuddyService {
}>; }>;
}>; }>;
getBuddyListFromCache (reqType: BuddyListReqType): Promise<Array< getBuddyListFromCache (reqType: BuddyListReqType): Promise<Array<
{ {
categoryId: number, // 9999为特别关心 categoryId: number, // 9999为特别关心

View File

@@ -1,5 +1,5 @@
import { AnyCnameRecord } from 'node:dns'; import { AnyCnameRecord } from 'node:dns';
import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailInfoListenerArg, UserDetailSource } from 'napcat-core/index'; import { BizKey, ModifyProfileParams, NodeIKernelProfileListener, ProfileBizType, SimpleInfo, UserDetailInfoByUin, UserDetailInfoListenerArg, UserDetailSource } from '@/napcat-core/index';
import { GeneralCallResult } from '@/napcat-core/services/common'; import { GeneralCallResult } from '@/napcat-core/services/common';
export interface NodeIKernelProfileService { export interface NodeIKernelProfileService {

View File

@@ -1,48 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/napcat-core/*": [
"*"
]
},
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"*.ts", "*.ts",
"**/*.ts" "**/*.ts"

View File

@@ -23,13 +23,13 @@ type ElementBase<
K extends keyof ElementFullBase, K extends keyof ElementFullBase,
S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object S extends Partial<{ [P in K]: keyof NonNullable<ElementFullBase[P]> | Array<keyof NonNullable<ElementFullBase[P]>> }> = object
> = { > = {
[P in K]: [P in K]:
S[P] extends Array<infer U> S[P] extends Array<infer U>
? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>> ? Pick<NonNullable<ElementFullBase[P]>, U & keyof NonNullable<ElementFullBase[P]>>
: S[P] extends keyof NonNullable<ElementFullBase[P]> : S[P] extends keyof NonNullable<ElementFullBase[P]>
? Pick<NonNullable<ElementFullBase[P]>, S[P]> ? Pick<NonNullable<ElementFullBase[P]>, S[P]>
: NonNullable<ElementFullBase[P]>; : NonNullable<ElementFullBase[P]>;
}; };
export interface TextElement { export interface TextElement {
content: string; content: string;

View File

@@ -10,5 +10,5 @@ const NAPCAT_MJS_PATH = path.join(BASE_DIR, 'napcat', 'napcat.mjs');
process.env.NAPCAT_WRAPPER_PATH = WRAPPER_NODE_PATH; process.env.NAPCAT_WRAPPER_PATH = WRAPPER_NODE_PATH;
process.env.NAPCAT_QQ_PACKAGE_INFO_PATH = PACKAGE_JSON_PATH; process.env.NAPCAT_QQ_PACKAGE_INFO_PATH = PACKAGE_JSON_PATH;
process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = CONFIG_JSON_PATH; process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = CONFIG_JSON_PATH;
process.env.NAPCAT_DISABLE_PIPE = '1'; process.env.NAPCAT_DISABLE_PIPE = '1';
import(pathToFileURL(NAPCAT_MJS_PATH).href); import(pathToFileURL(NAPCAT_MJS_PATH).href);

View File

@@ -12,15 +12,28 @@ if (!mainPath) {
const versionsDir = path.join(mainPath, 'versions'); const versionsDir = path.join(mainPath, 'versions');
console.log(`Looking for version folders in: ${versionsDir}`); console.log(`Looking for version folders in: ${versionsDir}`);
const versionFolders = fs.readdirSync(versionsDir).filter(f => fs.statSync(path.join(versionsDir, f)).isDirectory()); const versionFolders = fs.readdirSync(versionsDir).filter(f => fs.statSync(path.join(versionsDir, f)).isDirectory());
if (versionFolders.length !== 1) { let selectedFolder;
console.error('versions 文件夹下必须且只能有一个版本目录'); if (versionFolders.length === 0) {
console.error('versions 文件夹下没有找到版本目录');
process.exit(1); process.exit(1);
} else if (versionFolders.length === 1) {
selectedFolder = versionFolders[0];
} else {
// 获取时间最新的文件夹
const stats = versionFolders.map(folder => {
const folderPath = path.join(versionsDir, folder);
return { folder, mtime: fs.statSync(folderPath).mtime };
});
stats.sort((a, b) => b.mtime - a.mtime);
selectedFolder = stats[0].folder;
console.log(`多个版本文件夹存在,选择最新的: ${selectedFolder}`);
} }
const BASE_DIR = path.join(versionsDir, versionFolders[0], 'resources', 'app'); const BASE_DIR = path.join(versionsDir, selectedFolder, 'resources', 'app');
console.log(`BASE_DIR: ${BASE_DIR}`);
const TARGET_DIR = path.join(__dirname, 'dist'); const TARGET_DIR = path.join(__dirname, 'dist');
const QQNT_FILE = path.join(__dirname, 'QQNT.dll'); const QQNT_FILE = path.join(__dirname, 'QQNT.dll');
const NAPCAT_MJS_PATH = path.join(__dirname, '..', 'napcat-shell','dist', 'napcat.mjs'); const NAPCAT_MJS_PATH = path.join(__dirname, '..', 'napcat-shell', 'dist', 'napcat.mjs');
const itemsToCopy = [ const itemsToCopy = [
'avif_convert.dll', 'avif_convert.dll',
@@ -32,15 +45,15 @@ const itemsToCopy = [
'opencv.dll', 'opencv.dll',
'package.json', 'package.json',
'QBar.dll', 'QBar.dll',
'wrapper.node' 'wrapper.node',
]; ];
async function copyAll () { async function copyAll () {
const qqntDllPath = path.join(TARGET_DIR, 'QQNT.dll'); const qqntDllPath = path.join(TARGET_DIR, 'QQNT.dll');
const configPath = path.join(TARGET_DIR, 'config.json'); const configPath = path.join(TARGET_DIR, 'config.json');
const allItemsExist = await fs.pathExists(qqntDllPath) const allItemsExist = await fs.pathExists(qqntDllPath) &&
&& await fs.pathExists(configPath) await fs.pathExists(configPath) &&
&& (await Promise.all(itemsToCopy.map(item => fs.pathExists(path.join(TARGET_DIR, item))))).every(exists => exists); (await Promise.all(itemsToCopy.map(item => fs.pathExists(path.join(TARGET_DIR, item))))).every(exists => exists);
if (!allItemsExist) { if (!allItemsExist) {
console.log('Copying required files...'); console.log('Copying required files...');
@@ -61,9 +74,11 @@ async function copyAll () {
process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = path.join(TARGET_DIR, 'config.json'); process.env.NAPCAT_QQ_VERSION_CONFIG_PATH = path.join(TARGET_DIR, 'config.json');
process.env.NAPCAT_DISABLE_PIPE = '1'; process.env.NAPCAT_DISABLE_PIPE = '1';
process.env.NAPCAT_WORKDIR = TARGET_DIR; process.env.NAPCAT_WORKDIR = TARGET_DIR;
// 开发环境使用固定密钥
process.env.NAPCAT_WEBUI_JWT_SECRET_KEY = 'napcat_dev_secret_key';
process.env.NAPCAT_WEBUI_SECRET_KEY = 'napcat';
console.log('Loading NapCat module...'); console.log('Loading NapCat module...');
await import(pathToFileURL(NAPCAT_MJS_PATH).href); await import(pathToFileURL(NAPCAT_MJS_PATH).href);
} }
copyAll().catch(console.error); copyAll().catch(console.error);

View File

@@ -5,4 +5,4 @@ $uninstall = $uninstall.Trim('"')
$qqPath = Split-Path $uninstall -Parent $qqPath = Split-Path $uninstall -Parent
Write-Host "QQPath: $qqPath" Write-Host "QQPath: $qqPath"
node.exe "./loadNapCat.cjs" "$qqPath" node.exe --inspect "./loadNapCat.cjs" "$qqPath"

View File

@@ -3,13 +3,13 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "index.cjs", "main": "index.js",
"scripts": { "scripts": {
"dev": "powershell ./nodeTest.ps1" "dev": "powershell ./nodeTest.ps1"
}, },
"exports": { "exports": {
".": { ".": {
"require": "./index.cjs" "require": "./index.js"
}, },
"./*": { "./*": {
"require": "./*" "require": "./*"

View File

@@ -1,46 +1,14 @@
{ {
"extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "./",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": "./",
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"allowJs": true "allowJs": true
}, },
"include": [ "include": [
"**/*.cjs" "*.cjs",
"**/*.cjs",
"**/*.ts",
"*.js",
"**/*.js"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",

View File

@@ -1,16 +1,15 @@
import { NapCatPathWrapper } from 'napcat-common/src/path'; import { NapCatPathWrapper } from 'napcat-common/src/path';
import { LogWrapper } from 'napcat-common/src/log'; import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index';
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
import { QQBasicInfoWrapper } from 'napcat-common/src/qq-basic-info';
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv } from 'napcat-core/index';
import { SelfInfo } from 'napcat-core/types';
import { NodeIKernelLoginListener } from 'napcat-core/listeners';
import { NodeIKernelLoginService } from 'napcat-core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from 'napcat-core/wrapper';
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/src/index';
import { NapCatOneBot11Adapter } from 'napcat-onebot/index'; import { NapCatOneBot11Adapter } from 'napcat-onebot/index';
import { FFmpegService } from 'napcat-common/src/ffmpeg';
import { NativePacketHandler } from 'napcat-core/packet/handler/client'; import { NativePacketHandler } from 'napcat-core/packet/handler/client';
import { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg';
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
import { QQBasicInfoWrapper } from '@/napcat-core/helper/qq-basic-info';
import { InstanceContext, loadQQWrapper, NapCatCore, NapCatCoreWorkingEnv, NodeIKernelLoginListener, NodeIKernelLoginService, NodeIQQNTWrapperSession, SelfInfo, WrapperNodeApi } from '@/napcat-core';
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
import { statusHelperSubscription } from '@/napcat-core/helper/status';
import { applyPendingUpdates } from '@/napcat-webui-backend/src/api/UpdateNapCat';
import { WebUiDataRuntime } from '@/napcat-webui-backend/src/helper/Data';
// Framework ES入口文件 // Framework ES入口文件
export async function getWebUiUrl () { export async function getWebUiUrl () {
@@ -35,6 +34,7 @@ export async function NCoreInitFramework (
}); });
const pathWrapper = new NapCatPathWrapper(); const pathWrapper = new NapCatPathWrapper();
await applyPendingUpdates(pathWrapper);
const logger = new LogWrapper(pathWrapper.logsPath); const logger = new LogWrapper(pathWrapper.logsPath);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger }); const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion()); const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVersion());
@@ -76,7 +76,8 @@ export async function NCoreInitFramework (
await loaderObject.core.initCore(); await loaderObject.core.initCore();
// 启动WebUi // 启动WebUi
InitWebUi(logger, pathWrapper).then().catch(e => logger.logError(e)); WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
// 初始化LLNC的Onebot实现 // 初始化LLNC的Onebot实现
await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot(); await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot();
} }

View File

@@ -1,49 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/napcat-onebot/*": ["../napcat-onebot/*"],
"@/napcat-core/*": ["../napcat-core/*"],
"@/napcat-common/*": ["../napcat-common/src/*"],
"@/napcat-webui-backend/*": ["../napcat-webui-backend/src/*"]
},
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"*.ts", "*.ts",
"**/*.ts" "**/*.ts"

View File

@@ -2,22 +2,23 @@ import cp from 'vite-plugin-cp';
import { defineConfig, PluginOption, UserConfig } from 'vite'; import { defineConfig, PluginOption, UserConfig } from 'vite';
import path, { resolve } from 'path'; import path, { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve'; import nodeResolve from '@rollup/plugin-node-resolve';
import { autoIncludeTSPlugin } from "napcat-vite/vite-auto-include.js"; import { autoIncludeTSPlugin } from 'napcat-vite/vite-auto-include.js';
import { builtinModules } from 'module'; import { builtinModules } from 'module';
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react-swc';
import napcatVersion from "napcat-vite/vite-plugin-version.js"; import napcatVersion from 'napcat-vite/vite-plugin-version.js';
//依赖排除 // 依赖排除
const external = [ const external = [
'silk-wasm', 'silk-wasm',
'ws', 'ws',
'express' 'express',
]; ];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat(); const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
const FrameworkBaseConfigPlugin: PluginOption[] = [ const FrameworkBaseConfigPlugin: PluginOption[] = [
autoIncludeTSPlugin({ autoIncludeTSPlugin({
entries: [ entries: [
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') } { entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-core/protocol') },
] { entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') },
],
}), }),
react({ tsDecorators: true }), react({ tsDecorators: true }),
cp({ cp({
@@ -45,10 +46,10 @@ const FrameworkBaseConfig = () =>
conditions: ['node', 'default'], conditions: ['node', 'default'],
alias: { alias: {
'@/napcat-core': resolve(__dirname, '../napcat-core'), '@/napcat-core': resolve(__dirname, '../napcat-core'),
'@/napcat-common': resolve(__dirname, '../napcat-common/src'), '@/napcat-common': resolve(__dirname, '../napcat-common'),
'@/napcat-onebot': resolve(__dirname, '../napcat-onebot'), '@/napcat-onebot': resolve(__dirname, '../napcat-onebot'),
'@/napcat-pty': resolve(__dirname, '../napcat-pty'), '@/napcat-pty': resolve(__dirname, '../napcat-pty'),
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend/src'), '@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'),
'@/image-size': resolve(__dirname, '../image-size'), '@/image-size': resolve(__dirname, '../image-size'),
}, },
}, },

View File

@@ -1,48 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "src",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/napcat-webui-backend/*": [
"src/*"
]
},
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"
], ],

View File

@@ -1,6 +1,6 @@
import { OneBotAction } from './OneBotAction'; import { OneBotAction } from './OneBotAction';
export const AutoRegisterRouter: Array<new (...args: any[]) => OneBotAction<unknown, unknown>> = []; export const AutoRegisterRouter: Array<new (...args: any[]) => OneBotAction<unknown, unknown>> = [];
export function ActionHandler(target: new (...args: any[]) => OneBotAction<unknown, unknown>) { export function ActionHandler (target: new (...args: any[]) => OneBotAction<unknown, unknown>) {
AutoRegisterRouter.push(target); AutoRegisterRouter.push(target);
} }

View File

@@ -6,23 +6,23 @@ import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
phoneNumber: Type.String({ default: '' }), phone_number: Type.String({ default: '' }),
}); });
type Payload = Static<typeof SchemaData>; type Payload = Static<typeof SchemaData>;
export class SharePeer extends OneBotAction<Payload, GeneralCallResult & { export class SharePeerBase extends OneBotAction<Payload, GeneralCallResult & {
arkMsg?: string; arkMsg?: string;
arkJson?: string; arkJson?: string;
}> { }> {
override actionName = ActionName.SharePeer;
override payloadSchema = SchemaData; override payloadSchema = SchemaData;
async _handle (payload: Payload) { async _handle (payload: Payload) {
if (payload.group_id) { if (payload.group_id) {
return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id.toString()); return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id.toString());
} else if (payload.user_id) { } else if (payload.user_id) {
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phoneNumber); return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phone_number);
} }
throw new Error('group_id or user_id is required'); throw new Error('group_id or user_id is required');
} }
@@ -31,14 +31,25 @@ export class SharePeer extends OneBotAction<Payload, GeneralCallResult & {
const SchemaDataGroupEx = Type.Object({ const SchemaDataGroupEx = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]), group_id: Type.Union([Type.Number(), Type.String()]),
}); });
export class SharePeer extends SharePeerBase {
override actionName = ActionName.SharePeer;
}
type PayloadGroupEx = Static<typeof SchemaDataGroupEx>; type PayloadGroupEx = Static<typeof SchemaDataGroupEx>;
export class ShareGroupEx extends OneBotAction<PayloadGroupEx, string> { export class ShareGroupExBase extends OneBotAction<PayloadGroupEx, string> {
override actionName = ActionName.ShareGroupEx;
override payloadSchema = SchemaDataGroupEx; override payloadSchema = SchemaDataGroupEx;
async _handle (payload: PayloadGroupEx) { async _handle (payload: PayloadGroupEx) {
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString()); return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
} }
} }
export class ShareGroupEx extends ShareGroupExBase {
override actionName = ActionName.ShareGroupEx;
}
export class SendGroupArkShare extends ShareGroupExBase {
override actionName = ActionName.SendGroupArkShare;
}
export class SendArkShare extends SharePeerBase {
override actionName = ActionName.SendArkShare;
}

View File

@@ -2,7 +2,7 @@ import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
import { ActionName } from '@/napcat-onebot/action/router'; import { ActionName } from '@/napcat-onebot/action/router';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { decode } from 'silk-wasm'; import { decode } from 'silk-wasm';
import { FFmpegService } from 'napcat-common/src/ffmpeg'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];

View File

@@ -14,10 +14,11 @@ const SchemaData = Type.Object({
user_id: Type.String(), user_id: Type.String(),
message_seq: Type.Optional(Type.String()), message_seq: Type.Optional(Type.String()),
count: Type.Number({ default: 20 }), count: Type.Number({ default: 20 }),
reverseOrder: Type.Boolean({ default: false }), reverse_order: Type.Boolean({ default: false }),
disable_get_url: Type.Boolean({ default: false }), disable_get_url: Type.Boolean({ default: false }),
parse_mult_msg: Type.Boolean({ default: true }), parse_mult_msg: Type.Boolean({ default: true }),
quick_reply: Type.Boolean({ default: false }), quick_reply: Type.Boolean({ default: false }),
reverseOrder: Type.Boolean({ default: false }),// @deprecated 兼容旧版本
}); });
type Payload = Static<typeof SchemaData>; type Payload = Static<typeof SchemaData>;
@@ -35,7 +36,7 @@ export default class GetFriendMsgHistory extends OneBotAction<Payload, Response>
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0'); const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0'; const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq const msgList = hasMessageSeq
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverseOrder)).msgList ? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverse_order || payload.reverseOrder)).msgList
: (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
// 转换序号 // 转换序号

View File

@@ -14,10 +14,11 @@ const SchemaData = Type.Object({
group_id: Type.String(), group_id: Type.String(),
message_seq: Type.Optional(Type.String()), message_seq: Type.Optional(Type.String()),
count: Type.Number({ default: 20 }), count: Type.Number({ default: 20 }),
reverseOrder: Type.Boolean({ default: false }), reverse_order: Type.Boolean({ default: false }),
disable_get_url: Type.Boolean({ default: false }), disable_get_url: Type.Boolean({ default: false }),
parse_mult_msg: Type.Boolean({ default: true }), parse_mult_msg: Type.Boolean({ default: true }),
quick_reply: Type.Boolean({ default: false }), quick_reply: Type.Boolean({ default: false }),
reverseOrder: Type.Boolean({ default: false }),// @deprecated 兼容旧版本
}); });
type Payload = Static<typeof SchemaData>; type Payload = Static<typeof SchemaData>;
@@ -32,7 +33,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends OneBotAction<Payload, Re
// 拉取消息 // 拉取消息
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0'; const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
const msgList = hasMessageSeq const msgList = hasMessageSeq
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverseOrder)).msgList ? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverse_order || payload.reverseOrder)).msgList
: (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList; : (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`); if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
// 转换序号 // 转换序号

View File

@@ -41,7 +41,7 @@ export default class GoCQHTTPUploadGroupFile extends OneBotAction<Payload, Uploa
peer, peer,
deleteAfterSentFiles: [], deleteAfterSentFiles: [],
}; };
const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id); const sendFileEle = await this.obContext.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id);
msgContext.deleteAfterSentFiles.push(downloadResult.path); msgContext.deleteAfterSentFiles.push(downloadResult.path);
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles);

View File

@@ -51,7 +51,7 @@ export default class GoCQHTTPUploadPrivateFile extends OneBotAction<Payload, Upl
}, ContextMode.Private), }, ContextMode.Private),
deleteAfterSentFiles: [], deleteAfterSentFiles: [],
}; };
const sendFileEle: SendFileElement = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name); const sendFileEle: SendFileElement = await this.obContext.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name);
msgContext.deleteAfterSentFiles.push(downloadResult.path); msgContext.deleteAfterSentFiles.push(downloadResult.path);
const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], msgContext.deleteAfterSentFiles); const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(await this.getPeer(payload), [sendFileEle], msgContext.deleteAfterSentFiles);

View File

@@ -54,7 +54,7 @@ import { GetOnlineClient } from './go-cqhttp/GetOnlineClient';
import { IOCRImage, OCRImage } from './extends/OCRImage'; import { IOCRImage, OCRImage } from './extends/OCRImage';
import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn'; import { TranslateEnWordToZn } from './extends/TranslateEnWordToZn';
import { SetQQProfile } from './go-cqhttp/SetQQProfile'; import { SetQQProfile } from './go-cqhttp/SetQQProfile';
import { ShareGroupEx, SharePeer } from './extends/ShareContact'; import { SendArkShare, SendGroupArkShare, ShareGroupEx, SharePeer } from './extends/ShareContact';
import { CreateCollection } from './extends/CreateCollection'; import { CreateCollection } from './extends/CreateCollection';
import { SetLongNick } from './extends/SetLongNick'; import { SetLongNick } from './extends/SetLongNick';
import DelEssenceMsg from './group/DelEssenceMsg'; import DelEssenceMsg from './group/DelEssenceMsg';
@@ -138,7 +138,7 @@ import { TestDownloadStream } from './stream/TestStreamDownload';
import { UploadFileStream } from './stream/UploadFileStream'; import { UploadFileStream } from './stream/UploadFileStream';
import { AutoRegisterRouter } from './auto-register'; import { AutoRegisterRouter } from './auto-register';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) { export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [ const actionHandlers = [
new CleanStreamTempFile(obContext, core), new CleanStreamTempFile(obContext, core),
new DownloadFileStream(obContext, core), new DownloadFileStream(obContext, core),
@@ -170,6 +170,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new SetQQProfile(obContext, core), new SetQQProfile(obContext, core),
new ShareGroupEx(obContext, core), new ShareGroupEx(obContext, core),
new SharePeer(obContext, core), new SharePeer(obContext, core),
new SendGroupArkShare(obContext, core),
new SendArkShare(obContext, core),
new CreateCollection(obContext, core), new CreateCollection(obContext, core),
new SetLongNick(obContext, core), new SetLongNick(obContext, core),
new ForwardFriendSingleMsg(obContext, core), new ForwardFriendSingleMsg(obContext, core),
@@ -315,7 +317,7 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
// function get<K extends keyof MapType>(key: K): MapType[K]; // function get<K extends keyof MapType>(key: K): MapType[K];
// function get<K extends keyof MapType>(key: K): null; // function get<K extends keyof MapType>(key: K): null;
// function get<K extends keyof MapType>(key: K): HandlerUnion | null | MapType[K] // function get<K extends keyof MapType>(key: K): HandlerUnion | null | MapType[K]
function get<K extends keyof MapType>(key: K): MapType[K] | undefined { function get<K extends keyof MapType> (key: K): MapType[K] | undefined {
return _map.get(key as keyof MapType) as MapType[K] | undefined; return _map.get(key as keyof MapType) as MapType[K] | undefined;
} }
return { get }; return { get };

View File

@@ -11,7 +11,7 @@ import { decodeCQCode } from '@/napcat-onebot/helper/cqcode';
import { MessageUnique } from 'napcat-common/src/message-unique'; import { MessageUnique } from 'napcat-common/src/message-unique';
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from 'napcat-core'; import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from 'napcat-core';
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction'; import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder'; import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
import { stringifyWithBigInt } from 'napcat-common/src/helper'; import { stringifyWithBigInt } from 'napcat-common/src/helper';
import { PacketMsg } from 'napcat-core/packet/message/message'; import { PacketMsg } from 'napcat-core/packet/message/message';
import { rawMsgWithSendMsg } from 'napcat-core/packet/message/converter'; import { rawMsgWithSendMsg } from 'napcat-core/packet/message/converter';

View File

@@ -125,8 +125,11 @@ export const ActionName = {
// 以下为扩展napcat扩展 // 以下为扩展napcat扩展
Unknown: 'unknown', Unknown: 'unknown',
SetDiyOnlineStatus: 'set_diy_online_status', SetDiyOnlineStatus: 'set_diy_online_status',
SharePeer: 'ArkSharePeer', SharePeer: 'ArkSharePeer',// @deprecated
ShareGroupEx: 'ArkShareGroup', ShareGroupEx: 'ArkShareGroup',// @deprecated
// 标准化接口
SendGroupArkShare: 'send_group_ark_share',
SendArkShare: 'send_ark_share',
// RebootNormal : 'reboot_normal', //无快速登录重新启动 // RebootNormal : 'reboot_normal', //无快速登录重新启动
GetRobotUinRange: 'get_robot_uin_range', GetRobotUinRange: 'get_robot_uin_range',
SetOnlineStatus: 'set_online_status', SetOnlineStatus: 'set_online_status',

View File

@@ -5,7 +5,7 @@ import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
import { StreamPacket, StreamStatus } from './StreamBasic'; import { StreamPacket, StreamStatus } from './StreamBasic';
import fs from 'fs'; import fs from 'fs';
import { decode } from 'silk-wasm'; import { decode } from 'silk-wasm';
import { FFmpegService } from 'napcat-common/src/ffmpeg'; import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream';
const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];

View File

@@ -0,0 +1,180 @@
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
import { encodeSilk } from '@/napcat-core/helper/audio';
import { FFmpegService } from '@/napcat-core/helper/ffmpeg/ffmpeg';
import { calculateFileMD5 } from 'napcat-common/src/file';
import { ElementType, NapCatCore, PicElement, PicSubType, SendFileElement, SendPicElement, SendPttElement, SendVideoElement } from 'napcat-core';
import { getFileTypeForSendType } from 'napcat-core/helper/msg';
import { imageSizeFallBack } from 'napcat-image-size';
import { SendMessageContext } from './msg';
import { fileTypeFromFile } from 'file-type';
import pathLib from 'node:path';
import fsPromises from 'fs/promises';
import fs from 'fs';
import { defaultVideoThumbB64 } from '@/napcat-core/helper/ffmpeg/video';
export class OneBotFileApi {
obContext: NapCatOneBot11Adapter;
core: NapCatCore;
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
this.obContext = obContext;
this.core = core;
}
async createValidSendFileElement (context: SendMessageContext, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
const {
fileName: _fileName,
path,
fileSize,
} = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.FILE,
elementId: '',
fileElement: {
fileName: fileName || _fileName,
folderId,
filePath: path,
fileSize: fileSize.toString(),
},
};
}
async createValidSendPicElement (context: SendMessageContext, picPath: string, summary: string = '', subType: PicSubType = 0): Promise<SendPicElement> {
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const imageSize = await imageSizeFallBack(picPath);
context.deleteAfterSentFiles.push(path);
return {
elementType: ElementType.PIC,
elementId: '',
picElement: {
md5HexStr: md5,
fileSize: fileSize.toString(),
picWidth: imageSize.width,
picHeight: imageSize.height,
fileName,
sourcePath: path,
original: true,
picType: await getFileTypeForSendType(picPath),
picSubType: subType,
fileUuid: '',
fileSubId: '',
thumbFileSize: 0,
summary,
} as PicElement,
};
}
async createValidSendVideoElement (context: SendMessageContext, filePath: string, fileName: string = '', _diyThumbPath: string = ''): Promise<SendVideoElement> {
let videoInfo = {
width: 1920,
height: 1080,
time: 15,
format: 'mp4',
size: 0,
filePath,
};
let fileExt = 'mp4';
try {
const tempExt = (await fileTypeFromFile(filePath))?.ext;
if (tempExt) fileExt = tempExt;
} catch (e) {
this.core.context.logger.logError('获取文件类型失败', e);
}
const newFilePath = `${filePath}.${fileExt}`;
fs.copyFileSync(filePath, newFilePath);
context.deleteAfterSentFiles.push(newFilePath);
filePath = newFilePath;
const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO);
context.deleteAfterSentFiles.push(path);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
const thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`);
fs.mkdirSync(pathLib.dirname(thumbDir), { recursive: true });
const thumbPath = pathLib.join(pathLib.dirname(thumbDir), `${md5}_0.png`);
try {
videoInfo = await FFmpegService.getVideoInfo(filePath, thumbPath);
if (!fs.existsSync(thumbPath)) {
this.core.context.logger.logError('获取视频缩略图失败', new Error('缩略图不存在'));
throw new Error('获取视频缩略图失败');
}
} catch (e) {
this.core.context.logger.logError('获取视频信息失败', e);
fs.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64'));
}
if (_diyThumbPath) {
try {
await this.core.apis.FileApi.copyFile(_diyThumbPath, thumbPath);
} catch (e) {
this.core.context.logger.logError('复制自定义缩略图失败', e);
}
}
context.deleteAfterSentFiles.push(thumbPath);
const thumbSize = (await fsPromises.stat(thumbPath)).size;
const thumbMd5 = await calculateFileMD5(thumbPath);
context.deleteAfterSentFiles.push(thumbPath);
const uploadName = (fileName || _fileName).toLocaleLowerCase().endsWith(`.${fileExt.toLocaleLowerCase()}`) ? (fileName || _fileName) : `${fileName || _fileName}.${fileExt}`;
return {
elementType: ElementType.VIDEO,
elementId: '',
videoElement: {
fileName: uploadName,
filePath: path,
videoMd5: md5,
thumbMd5,
fileTime: videoInfo.time,
thumbPath: new Map([[0, thumbPath]]),
thumbSize,
thumbWidth: videoInfo.width,
thumbHeight: videoInfo.height,
fileSize: fileSize.toString(),
},
};
}
async createValidSendPttElement (_context: SendMessageContext, pttPath: string): Promise<SendPttElement> {
const { converted, path: silkPath, duration } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger);
if (!silkPath) {
throw new Error('语音转换失败, 请检查语音文件是否正常');
}
const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath, ElementType.PTT);
if (fileSize === 0) {
throw new Error('文件异常大小为0');
}
if (converted) {
fsPromises.unlink(silkPath).then().catch((e) => this.core.context.logger.logError('删除临时文件失败', e));
}
return {
elementType: ElementType.PTT,
elementId: '',
pttElement: {
fileName,
filePath: path,
md5HexStr: md5,
fileSize: fileSize.toString(),
duration: duration ?? 1,
formatType: 1,
voiceType: 1,
voiceChangeType: 0,
canConvert2Text: true,
waveAmplitudes: [
0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17,
],
fileSubId: '',
playState: 1,
autoConvertText: 0,
storeID: 0,
otherBusinessInfo: {
aiVoiceType: 0,
},
},
};
}
}

View File

@@ -11,7 +11,6 @@ import {
TipGroupElement, TipGroupElement,
TipGroupElementType, TipGroupElementType,
} from 'napcat-core'; } from 'napcat-core';
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
import { OB11GroupBanEvent } from '@/napcat-onebot/event/notice/OB11GroupBanEvent'; import { OB11GroupBanEvent } from '@/napcat-onebot/event/notice/OB11GroupBanEvent';
import fastXmlParser from 'fast-xml-parser'; import fastXmlParser from 'fast-xml-parser';
import { OB11GroupMsgEmojiLikeEvent } from '@/napcat-onebot/event/notice/OB11MsgEmojiLikeEvent'; import { OB11GroupMsgEmojiLikeEvent } from '@/napcat-onebot/event/notice/OB11MsgEmojiLikeEvent';
@@ -26,6 +25,7 @@ import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent'; import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { NapProtoMsg } from 'napcat-protobuf'; import { NapProtoMsg } from 'napcat-protobuf';
import { GroupReactNotify, PushMsg } from 'napcat-core/packet/transformer/proto'; import { GroupReactNotify, PushMsg } from 'napcat-core/packet/transformer/proto';
import { NapCatOneBot11Adapter } from '..';
export class OneBotGroupApi { export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter; obContext: NapCatOneBot11Adapter;
@@ -172,6 +172,23 @@ export class OneBotGroupApi {
}); });
} }
async registerParseGroupReactEventByCore () {
this.core.event.on('event:emoji_like', async (data) => {
console.log('Received emoji_like event from core:', data);
const event = await this.createGroupEmojiLikeEvent(
data.groupId,
data.senderUin,
data.msgSeq,
data.emojiId,
data.isAdd,
data.count
);
if (event) {
this.obContext.networkManager.emitEvent(event);
}
});
}
async parsePaiYiPai (msg: RawMessage, jsonStr: string) { async parsePaiYiPai (msg: RawMessage, jsonStr: string) {
const json = JSON.parse(jsonStr); const json = JSON.parse(jsonStr);
// 判断业务类型 // 判断业务类型

View File

@@ -36,7 +36,7 @@ import { uriToLocalFile } from 'napcat-common/src/file';
import { RequestUtil } from 'napcat-common/src/request'; import { RequestUtil } from 'napcat-common/src/request';
import fsPromise from 'node:fs/promises'; import fsPromise from 'node:fs/promises';
import { OB11FriendAddNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendAddNoticeEvent'; import { OB11FriendAddNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendAddNoticeEvent';
import { ForwardMsgBuilder } from 'napcat-common/src/forward-msg-builder'; import { ForwardMsgBuilder } from '@/napcat-core/helper/forward-msg-builder';
import { NapProtoMsg } from 'napcat-protobuf'; import { NapProtoMsg } from 'napcat-protobuf';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent'; import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent'; import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
@@ -666,7 +666,7 @@ export class OneBotMsgApi {
// File service // File service
[OB11MessageDataType.image]: async (sendMsg, context) => { [OB11MessageDataType.image]: async (sendMsg, context) => {
return await this.core.apis.FileApi.createValidSendPicElement( return await this.obContext.apis.FileApi.createValidSendPicElement(
context, context,
(await this.handleOb11FileLikeMessage(sendMsg, context)).path, (await this.handleOb11FileLikeMessage(sendMsg, context)).path,
sendMsg.data.summary, sendMsg.data.summary,
@@ -676,7 +676,7 @@ export class OneBotMsgApi {
[OB11MessageDataType.file]: async (sendMsg, context) => { [OB11MessageDataType.file]: async (sendMsg, context) => {
const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context);
return await this.core.apis.FileApi.createValidSendFileElement(context, path, fileName); return await this.obContext.apis.FileApi.createValidSendFileElement(context, path, fileName);
}, },
[OB11MessageDataType.video]: async (sendMsg, context) => { [OB11MessageDataType.video]: async (sendMsg, context) => {
@@ -691,11 +691,11 @@ export class OneBotMsgApi {
} }
} }
return await this.core.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb); return await this.obContext.apis.FileApi.createValidSendVideoElement(context, path, fileName, thumb);
}, },
[OB11MessageDataType.voice]: async (sendMsg, context) => [OB11MessageDataType.voice]: async (sendMsg, context) =>
this.core.apis.FileApi.createValidSendPttElement(context, this.obContext.apis.FileApi.createValidSendPttElement(context,
(await this.handleOb11FileLikeMessage(sendMsg, context)).path), (await this.handleOb11FileLikeMessage(sendMsg, context)).path),
[OB11MessageDataType.json]: async ({ data: { data } }) => ({ [OB11MessageDataType.json]: async ({ data: { data } }) => ({

View File

@@ -1,4 +1,4 @@
import { ConfigBase } from 'napcat-common/src/config-base'; import { ConfigBase } from 'napcat-core/helper/config-base';
import type { NapCatCore } from 'napcat-core'; import type { NapCatCore } from 'napcat-core';
import { OneBotConfig } from './config'; import { OneBotConfig } from './config';
import { AnySchema } from 'ajv'; import { AnySchema } from 'ajv';

View File

@@ -17,7 +17,7 @@ import {
NTMsgAtType, NTMsgAtType,
} from 'napcat-core'; } from 'napcat-core';
import { OB11ConfigLoader } from '@/napcat-onebot/config'; import { OB11ConfigLoader } from '@/napcat-onebot/config';
import { pendingTokenToSend } from 'napcat-webui-backend/src/index'; import { pendingTokenToSend } from 'napcat-webui-backend/index';
import { import {
OB11HttpClientAdapter, OB11HttpClientAdapter,
OB11WebSocketClientAdapter, OB11WebSocketClientAdapter,
@@ -38,7 +38,6 @@ import { ActionMap, createActionMap } from '@/napcat-onebot/action';
import { WebUiDataRuntime } from 'napcat-webui-backend/src/helper/Data'; import { WebUiDataRuntime } from 'napcat-webui-backend/src/helper/Data';
import { OB11InputStatusEvent } from '@/napcat-onebot/event/notice/OB11InputStatusEvent'; import { OB11InputStatusEvent } from '@/napcat-onebot/event/notice/OB11InputStatusEvent';
import { MessageUnique } from 'napcat-common/src/message-unique'; import { MessageUnique } from 'napcat-common/src/message-unique';
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
import { OB11FriendRequestEvent } from '@/napcat-onebot/event/request/OB11FriendRequest'; import { OB11FriendRequestEvent } from '@/napcat-onebot/event/request/OB11FriendRequest';
import { OB11GroupRequestEvent } from '@/napcat-onebot/event/request/OB11GroupRequest'; import { OB11GroupRequestEvent } from '@/napcat-onebot/event/request/OB11GroupRequest';
import { OB11FriendRecallNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendRecallNoticeEvent'; import { OB11FriendRecallNoticeEvent } from '@/napcat-onebot/event/notice/OB11FriendRecallNoticeEvent';
@@ -54,14 +53,24 @@ import { IOB11NetworkAdapter } from '@/napcat-onebot/network/adapter';
import { OB11HttpSSEServerAdapter } from './network/http-server-sse'; import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
import { OB11PluginMangerAdapter } from './network/plugin-manger'; import { OB11PluginMangerAdapter } from './network/plugin-manger';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import { proxiedListenerOf } from '@/napcat-core/helper/proxy-handler';
import { OneBotFileApi } from './api/file';
interface ApiListType {
GroupApi: OneBotGroupApi;
UserApi: OneBotUserApi;
FriendApi: OneBotFriendApi;
MsgApi: OneBotMsgApi;
QuickActionApi: OneBotQuickActionApi;
FileApi: OneBotFileApi;
}
// OneBot实现类 // OneBot实现类
export class NapCatOneBot11Adapter { export class NapCatOneBot11Adapter {
readonly core: NapCatCore; readonly core: NapCatCore;
readonly context: InstanceContext; readonly context: InstanceContext;
configLoader: OB11ConfigLoader; configLoader: OB11ConfigLoader;
public readonly apis; public apis: ApiListType;
networkManager: OB11NetworkManager; networkManager: OB11NetworkManager;
actions: ActionMap; actions: ActionMap;
private readonly bootTime = Date.now() / 1000; private readonly bootTime = Date.now() / 1000;
@@ -76,6 +85,7 @@ export class NapCatOneBot11Adapter {
FriendApi: new OneBotFriendApi(this, core), FriendApi: new OneBotFriendApi(this, core),
MsgApi: new OneBotMsgApi(this, core), MsgApi: new OneBotMsgApi(this, core),
QuickActionApi: new OneBotQuickActionApi(this, core), QuickActionApi: new OneBotQuickActionApi(this, core),
FileApi: new OneBotFileApi(this, core),
} as const; } as const;
this.actions = createActionMap(this, core); this.actions = createActionMap(this, core);
this.networkManager = new OB11NetworkManager(); this.networkManager = new OB11NetworkManager();
@@ -218,7 +228,7 @@ export class NapCatOneBot11Adapter {
// this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`); // this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
await this.reloadNetwork(prev, newConfig); await this.reloadNetwork(prev, newConfig);
}); });
this.apis.GroupApi.registerParseGroupReactEvent().catch(e => this.apis.GroupApi.registerParseGroupReactEventByCore().catch(e =>
this.context.logger.logError('注册群消息反应表情失败', e) this.context.logger.logError('注册群消息反应表情失败', e)
); );
} }
@@ -236,7 +246,7 @@ export class NapCatOneBot11Adapter {
await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter); await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter);
} }
private async handleConfigChange<CT extends NetworkAdapterConfig> ( private async handleConfigChange<CT extends NetworkAdapterConfig>(
prevConfig: NetworkAdapterConfig[], prevConfig: NetworkAdapterConfig[],
nowConfig: NetworkAdapterConfig[], nowConfig: NetworkAdapterConfig[],
adapterClass: new ( adapterClass: new (

View File

@@ -1,5 +1,5 @@
import { NetworkAdapterConfig } from '@/napcat-onebot/config/config'; import { NetworkAdapterConfig } from '@/napcat-onebot/config/config';
import { LogWrapper } from 'napcat-common/src/log'; import { LogWrapper } from 'napcat-core/helper/log';
import { NapCatCore } from 'napcat-core'; import { NapCatCore } from 'napcat-core';
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index'; import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
import { ActionMap } from '@/napcat-onebot/action'; import { ActionMap } from '@/napcat-onebot/action';

View File

@@ -4,6 +4,9 @@
"private": true, "private": true,
"type": "module", "type": "module",
"main": "index.ts", "main": "index.ts",
"scripts": {
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
},
"exports": { "exports": {
".": { ".": {
"import": "./index.ts" "import": "./index.ts"

View File

@@ -1,48 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/napcat-onebot/*": [
"*"
]
},
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"*.ts", "*.ts",
"**/*.ts" "**/*.ts"

View File

@@ -1,43 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"*.ts", "*.ts",
"**/*.ts" "**/*.ts"

View File

@@ -7,18 +7,18 @@ type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
type CamelCaseHelper< type CamelCaseHelper<
S extends string, S extends string,
CapNext extends boolean, CapNext extends boolean,
IsFirstChar extends boolean, IsFirstChar extends boolean
> = S extends `${infer F}${infer R}` > = S extends `${infer F}${infer R}`
? F extends '_' ? F extends '_'
? CamelCaseHelper<R, true, false> ? CamelCaseHelper<R, true, false>
: F extends `${number}` : F extends `${number}`
? `${F}${CamelCaseHelper<R, true, false>}` ? `${F}${CamelCaseHelper<R, true, false>}`
: CapNext extends true : CapNext extends true
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}` ? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
: IsFirstChar extends true : IsFirstChar extends true
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}` ? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
: `${F}${CamelCaseHelper<R, false, false>}` : `${F}${CamelCaseHelper<R, false, false>}`
: ''; : '';
type ScalarTypeToTsType<T extends ScalarType> = T extends type ScalarTypeToTsType<T extends ScalarType> = T extends
| ScalarType.DOUBLE | ScalarType.DOUBLE
@@ -28,36 +28,36 @@ type ScalarTypeToTsType<T extends ScalarType> = T extends
| ScalarType.UINT32 | ScalarType.UINT32
| ScalarType.SFIXED32 | ScalarType.SFIXED32
| ScalarType.SINT32 | ScalarType.SINT32
? number ? number
: T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 : T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64
? bigint ? bigint
: T extends ScalarType.BOOL : T extends ScalarType.BOOL
? boolean ? boolean
: T extends ScalarType.STRING : T extends ScalarType.STRING
? string ? string
: T extends ScalarType.BYTES : T extends ScalarType.BYTES
? Uint8Array ? Uint8Array
: never; : never;
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> { interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
kind: 'scalar' | 'message'; kind: 'scalar' | 'message';
no: number; no: number;
type: T; type: T;
optional: O; optional: O;
repeat: R; repeat: R;
} }
export interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean> export interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean>
extends BaseProtoFieldType<T, O, R> { extends BaseProtoFieldType<T, O, R> {
kind: 'scalar'; kind: 'scalar';
} }
export interface MessageProtoFieldType< export interface MessageProtoFieldType<
T extends () => ProtoMessageType, T extends () => ProtoMessageType,
O extends boolean, O extends boolean,
R extends O extends true ? false : boolean, R extends O extends true ? false : boolean
> extends BaseProtoFieldType<T, O, R> { > extends BaseProtoFieldType<T, O, R> {
kind: 'message'; kind: 'message';
} }
type ProtoFieldType = type ProtoFieldType =
@@ -65,62 +65,62 @@ type ProtoFieldType =
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>; | MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
type ProtoMessageType = { type ProtoMessageType = {
[key: string]: ProtoFieldType; [key: string]: ProtoFieldType;
}; };
export function ProtoField< export function ProtoField<
T extends ScalarType, T extends ScalarType,
O extends boolean = false, O extends boolean = false,
R extends O extends true ? false : boolean = false, R extends O extends true ? false : boolean = false
>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>; > (no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
export function ProtoField< export function ProtoField<
T extends () => ProtoMessageType, T extends () => ProtoMessageType,
O extends boolean = false, O extends boolean = false,
R extends O extends true ? false : boolean = false, R extends O extends true ? false : boolean = false
>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>; > (no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
export function ProtoField( export function ProtoField (
no: number, no: number,
type: ScalarType | (() => ProtoMessageType), type: ScalarType | (() => ProtoMessageType),
optional?: boolean, optional?: boolean,
repeat?: boolean repeat?: boolean
): ProtoFieldType { ): ProtoFieldType {
if (typeof type === 'function') { if (typeof type === 'function') {
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false }; return { kind: 'message', no, type, optional: optional ?? false, repeat: repeat ?? false };
} else { } else {
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false }; return { kind: 'scalar', no, type, optional: optional ?? false, repeat: repeat ?? false };
} }
} }
type ProtoFieldReturnType<T, E extends boolean> = type ProtoFieldReturnType<T, E extends boolean> =
NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R> NonNullable<T> extends ScalarProtoFieldType<infer S, infer _O, infer _R>
? ScalarTypeToTsType<S> ? ScalarTypeToTsType<S>
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>> : T extends NonNullable<MessageProtoFieldType<infer S, infer _O, infer _R>>
? NonNullable<NapProtoStructType<ReturnType<S>, E>> ? NonNullable<NapProtoStructType<ReturnType<S>, E>>
: never; : never;
type RequiredFieldsBaseType<T, E extends boolean> = { type RequiredFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]: T[K] extends { [K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]: T[K] extends {
repeat: true; repeat: true;
} }
? ProtoFieldReturnType<T[K], E>[] ? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>; : ProtoFieldReturnType<T[K], E>;
}; };
type OptionalFieldsBaseType<T, E extends boolean> = { type OptionalFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?: T[K] extends { [K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?: T[K] extends {
repeat: true; repeat: true;
} }
? ProtoFieldReturnType<T[K], E>[] ? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>; : ProtoFieldReturnType<T[K], E>;
}; };
type RequiredFieldsType<T, E extends boolean> = E extends true type RequiredFieldsType<T, E extends boolean> = E extends true
? Partial<RequiredFieldsBaseType<T, E>> ? Partial<RequiredFieldsBaseType<T, E>>
: RequiredFieldsBaseType<T, E>; : RequiredFieldsBaseType<T, E>;
type OptionalFieldsType<T, E extends boolean> = E extends true type OptionalFieldsType<T, E extends boolean> = E extends true
? Partial<OptionalFieldsBaseType<T, E>> ? Partial<OptionalFieldsBaseType<T, E>>
: OptionalFieldsBaseType<T, E>; : OptionalFieldsBaseType<T, E>;
type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>; type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
@@ -129,72 +129,74 @@ export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>; export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
class NapProtoRealMsg<T extends ProtoMessageType> { class NapProtoRealMsg<T extends ProtoMessageType> {
private readonly _field: PartialFieldInfo[]; private readonly _field: PartialFieldInfo[];
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>; private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>(); private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>();
private constructor(fields: T) { private constructor (fields: T) {
this._field = Object.keys(fields).map((key) => { this._field = Object.keys(fields).map((key) => {
const field = fields[key]; const field = fields[key];
if (field.kind === 'scalar') { if (field.kind === 'scalar') {
const repeatType = field.repeat const repeatType = field.repeat
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type) ? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
? RepeatType.UNPACKED ? RepeatType.UNPACKED
: RepeatType.PACKED : RepeatType.PACKED
: RepeatType.NO; : RepeatType.NO;
return { return {
no: field.no, no: field.no,
name: key, name: key,
kind: 'scalar', kind: 'scalar',
T: field.type, T: field.type,
opt: field.optional, opt: field.optional,
repeat: repeatType, repeat: repeatType,
}; };
} else if (field.kind === 'message') { } else if (field.kind === 'message') {
return { return {
no: field.no, no: field.no,
name: key, name: key,
kind: 'message', kind: 'message',
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO, repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg, T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg,
}; };
} } else {
}) as PartialFieldInfo[]; throw new Error(`Unknown field kind: ${field.kind}`);
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field); }
} }) as PartialFieldInfo[];
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
}
static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> { static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> {
let instance = this.cache.get(fields); let instance = this.cache.get(fields);
if (!instance) { if (!instance) {
instance = new NapProtoRealMsg(fields); instance = new NapProtoRealMsg(fields);
this.cache.set(fields, instance); this.cache.set(fields, instance);
}
return instance;
} }
return instance;
}
encode(data: NapProtoEncodeStructType<T>): Uint8Array { encode (data: NapProtoEncodeStructType<T>): Uint8Array {
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>)); return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
} }
decode(data: Uint8Array): NapProtoDecodeStructType<T> { decode (data: Uint8Array): NapProtoDecodeStructType<T> {
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>; return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
} }
} }
export class NapProtoMsg<T extends ProtoMessageType> { export class NapProtoMsg<T extends ProtoMessageType> {
private realMsg: NapProtoRealMsg<T>; private realMsg: NapProtoRealMsg<T>;
constructor(fields: T) { constructor (fields: T) {
this.realMsg = NapProtoRealMsg.getInstance(fields); this.realMsg = NapProtoRealMsg.getInstance(fields);
} }
encode(data: NapProtoEncodeStructType<T>): Uint8Array { encode (data: NapProtoEncodeStructType<T>): Uint8Array {
return this.realMsg.encode(data); return this.realMsg.encode(data);
} }
decode(data: Uint8Array): NapProtoDecodeStructType<T> { decode (data: Uint8Array): NapProtoDecodeStructType<T> {
return this.realMsg.decode(data); return this.realMsg.decode(data);
} }
} }
export { ScalarType } from '@protobuf-ts/runtime'; export { ScalarType } from '@protobuf-ts/runtime';

View File

@@ -14,12 +14,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@homebridge/node-pty-prebuilt-multiarch":"^0.12.0", "@homebridge/node-pty-prebuilt-multiarch":"^0.12.0"
"napcat-core": "workspace:*",
"napcat-common": "workspace:*",
"napcat-onebot": "workspace:*",
"napcat-webui-backend": "workspace:*",
"napcat-qrcode": "workspace:*"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,4 +1,4 @@
import { require_dlopen } from '.'; import { require_dlopen } from './index';
export function pty_loader () { export function pty_loader () {
let pty: any; let pty: any;
try { try {

View File

@@ -12,7 +12,17 @@ import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/t
import { assign } from '@homebridge/node-pty-prebuilt-multiarch/src/utils'; import { assign } from '@homebridge/node-pty-prebuilt-multiarch/src/utils';
import { pty_loader } from './prebuild-loader'; import { pty_loader } from './prebuild-loader';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
export const pty = pty_loader();
// 懒加载pty避免在模块导入时立即执行pty_loader()
let _pty: any;
export const pty: any = new Proxy({}, {
get (_target, prop) {
if (!_pty) {
_pty = pty_loader();
}
return _pty[prop];
}
});
let helperPath: string; let helperPath: string;
helperPath = '../build/Release/spawn-helper'; helperPath = '../build/Release/spawn-helper';

View File

@@ -11,7 +11,7 @@ import { Socket } from 'net';
import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/types'; import { ArgvOrCommandLine } from '@homebridge/node-pty-prebuilt-multiarch/src/types';
import { fork } from 'child_process'; import { fork } from 'child_process';
import { ConoutConnection } from './windowsConoutConnection'; import { ConoutConnection } from './windowsConoutConnection';
import { require_dlopen } from '.'; import { require_dlopen } from './index';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path'; import { dirname } from 'path';

View File

@@ -1,28 +1,21 @@
{ {
"name": "napcat-qrcode", "name": "napcat-qrcode",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "index.ts", "main": "index.ts",
"exports": { "exports": {
".": { ".": {
"import": "./index.ts" "import": "./index.ts"
},
"./*": {
"import": "./*"
}
}, },
"dependencies": { "./*": {
"napcat-core": "workspace:*", "import": "./*"
"napcat-common": "workspace:*",
"napcat-onebot": "workspace:*",
"napcat-webui-backend": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.0.1"
},
"engines": {
"node": ">=18.0.0"
} }
},
"devDependencies": {
"@types/node": "^22.0.1"
},
"engines": {
"node": ">=18.0.0"
}
} }

View File

@@ -1,43 +1,5 @@
{ {
"compilerOptions": { "extends": "../../tsconfig.base.json",
"target": "ES2021",
"module": "ESNext",
"moduleResolution": "Node",
"lib": [
"ES2021"
],
"typeRoots": [
"./node_modules/@types"
],
"esModuleInterop": true,
"outDir": "dist",
"rootDir": ".",
"noEmit": false,
"sourceMap": true,
"strict": true,
"noImplicitAny": false,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"alwaysStrict": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"baseUrl": ".",
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
"include": [ "include": [
"*.ts", "*.ts",
"**/*.ts" "**/*.ts"

Some files were not shown because too many files have changed in this diff Show More