mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 08:10:25 +00:00
Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c79370cb | ||
|
|
39460e4acb | ||
|
|
f971c312b9 | ||
|
|
2c3a304440 | ||
|
|
286b0e03f7 | ||
|
|
447f86e2b5 | ||
|
|
0592f1a99a | ||
|
|
90e3936204 | ||
|
|
1239f622d2 | ||
|
|
d511e2bb3f | ||
|
|
ff93aa3dc7 | ||
|
|
cc8891b6a1 | ||
|
|
7c65b1eaf1 | ||
|
|
ebe3e9c63c | ||
|
|
d33a872c42 | ||
|
|
9377dc3d52 | ||
|
|
17322bb5a4 | ||
|
|
c0bcced5fb | ||
|
|
805c1d5ea2 | ||
|
|
b3399b07ad | ||
|
|
71f8504849 | ||
|
|
3b7ca1a08f | ||
|
|
57f3c4dd31 | ||
|
|
5b20ebb7b0 | ||
|
|
3a3eaeec7c | ||
|
|
b0cc7b6ee5 | ||
|
|
e5108c0427 | ||
|
|
927797f3d5 | ||
|
|
72e01f8c84 | ||
|
|
c38b98a0c4 | ||
|
|
05d27e86ce | ||
|
|
40409a3841 | ||
|
|
65bae6b57a | ||
|
|
0b6afb66d9 | ||
|
|
52be000fdd | ||
|
|
55ce5bcfd3 | ||
|
|
29888cb38b | ||
|
|
6ea4c9ec65 | ||
|
|
4bec3aa597 | ||
|
|
38c320d2c9 | ||
|
|
76cbd8a1c1 | ||
|
|
0779628be5 | ||
|
|
34ca919c4d | ||
|
|
b1b357347b | ||
|
|
129d63f66e | ||
|
|
699b46acbd | ||
|
|
7f05aee11d | ||
|
|
542036f46e | ||
|
|
b958e9e803 | ||
|
|
73fcfb5900 | ||
|
|
adabc4da46 | ||
|
|
bf073b544b | ||
|
|
a71219062a | ||
|
|
001fe01ace | ||
|
|
0aa0c44634 | ||
|
|
93126e514e | ||
|
|
1ae10ae0c6 | ||
|
|
4b693bf6e2 | ||
|
|
574c257591 | ||
|
|
d680328762 | ||
|
|
d711cdecaf | ||
|
|
c5f1792009 | ||
|
|
a5e705e6a4 | ||
|
|
007f1db339 | ||
|
|
008fb39f8f | ||
|
|
6b8cc6756d | ||
|
|
24623f18d8 | ||
|
|
8b676ed693 | ||
|
|
ab2dfcfd8f | ||
|
|
2998e04435 | ||
|
|
613690f5af | ||
|
|
a60d8d109c | ||
|
|
28c9761e3d | ||
|
|
805cc32d7f | ||
|
|
de9d5180fe | ||
|
|
791e359199 | ||
|
|
63a9d571f3 | ||
|
|
59d4b08982 | ||
|
|
81e4e54f25 | ||
|
|
b75b733bb0 | ||
|
|
246269b519 |
2
.github/prompt/default.md
vendored
2
.github/prompt/default.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
[使用文档](https://napneko.github.io/)
|
[使用文档](https://napneko.github.io/)
|
||||||
|
|
||||||
## Windows 一键包
|
## Windows 一键包
|
||||||
我们为提供了的轻量化一键部署方案
|
我们提供了轻量化的一键部署方案
|
||||||
相对于普通需要安装QQ的方案,下面已内置QQ和Napcat 阅读使用文档参考
|
相对于普通需要安装QQ的方案,下面已内置QQ和Napcat 阅读使用文档参考
|
||||||
|
|
||||||
你可以下载
|
你可以下载
|
||||||
|
|||||||
2
.github/prompt/release_note_prompt.txt
vendored
2
.github/prompt/release_note_prompt.txt
vendored
@@ -31,7 +31,7 @@
|
|||||||
[使用文档](https://napneko.github.io/)
|
[使用文档](https://napneko.github.io/)
|
||||||
|
|
||||||
## Windows 一键包
|
## Windows 一键包
|
||||||
我们为提供了的轻量化一键部署方案
|
我们提供了轻量化的一键部署方案
|
||||||
相对于普通需要安装QQ的方案,下面已内置QQ和Napcat 阅读使用文档参考
|
相对于普通需要安装QQ的方案,下面已内置QQ和Napcat 阅读使用文档参考
|
||||||
|
|
||||||
你可以下载
|
你可以下载
|
||||||
|
|||||||
57
.github/workflows/auto-release.yml
vendored
57
.github/workflows/auto-release.yml
vendored
@@ -5,6 +5,63 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
publish-schema:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1))
|
||||||
|
version=${latest_tag#v}
|
||||||
|
echo "version=${version}" >> $GITHUB_ENV
|
||||||
|
echo "latest_tag=${latest_tag}" >> $GITHUB_ENV
|
||||||
|
echo "Debug: Version is ${version}"
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build napcat-schema
|
||||||
|
run: |
|
||||||
|
cd packages/napcat-schema
|
||||||
|
pnpm run build:openapi
|
||||||
|
|
||||||
|
- name: Checkout NapCatDocs
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: NapNeko/NapCatDocs
|
||||||
|
token: ${{ secrets.NAPCAT_BUILD }}
|
||||||
|
path: napcat-docs
|
||||||
|
|
||||||
|
- name: Copy OpenAPI Schema
|
||||||
|
run: |
|
||||||
|
mkdir -p napcat-docs/src/api/${{ env.version }}
|
||||||
|
cp packages/napcat-schema/dist/openapi.json napcat-docs/src/api/${{ env.version }}/openapi.json
|
||||||
|
echo "OpenAPI schema copied to napcat-docs/src/api/${{ env.version }}/openapi.json"
|
||||||
|
|
||||||
|
- name: Commit and Push
|
||||||
|
run: |
|
||||||
|
cd napcat-docs
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add src/api/${{ env.version }}/openapi.json
|
||||||
|
git commit -m "chore: update OpenAPI schema for version ${{ env.version }}" || echo "No changes to commit"
|
||||||
|
git push
|
||||||
|
|
||||||
shell-docker:
|
shell-docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ _Modern protocol-side framework implemented based on NTQQ._
|
|||||||
| Docs | [](https://napneko.github.io/) | [](https://doc.napneko.icu/) | [](https://napcat.napneko.icu/) |
|
| Docs | [](https://napneko.github.io/) | [](https://doc.napneko.icu/) | [](https://napcat.napneko.icu/) |
|
||||||
|:-:|:-:|:-:|:-:|
|
|:-:|:-:|:-:|:-:|
|
||||||
|
|
||||||
| Docs | [](https://napneko.pages.dev/) | [](https://napcat.cyou/) | [](https://www.napcat.wiki) |
|
| Docs | [](https://napneko.pages.dev/) | [](https://napcat.top/) | [](https://napcat.top/) |
|
||||||
|:-:|:-:|:-:|:-:|
|
|:-:|:-:|:-:|:-:|
|
||||||
|
|
||||||
| QQ Group | [](https://qm.qq.com/q/CMmPbGw0jA) | [](https://qm.qq.com/q/8zJMLjqy2Y) | [](https://qm.qq.com/q/CMmPbGw0jA) | [](https://qm.qq.com/q/I6LU87a0Yq) |
|
| QQ Group | [](https://qm.qq.com/q/CMmPbGw0jA) | [](https://qm.qq.com/q/8zJMLjqy2Y) | [](https://qm.qq.com/q/CMmPbGw0jA) | [](https://qm.qq.com/q/I6LU87a0Yq) |
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"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:shell:dev": "pnpm --filter napcat-shell run build:dev || exit 1",
|
||||||
|
"build:shell:config": "pnpm --filter napcat-shell run build && pnpm --filter napcat-develop run copy-env",
|
||||||
"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",
|
||||||
"build:plugin-builtin": "pnpm --filter napcat-plugin-builtin run build || exit 1",
|
"build:plugin-builtin": "pnpm --filter napcat-plugin-builtin run build || exit 1",
|
||||||
|
|||||||
176
packages/napcat-adapter/index.ts
Normal file
176
packages/napcat-adapter/index.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { InstanceContext, NapCatCore } from 'napcat-core';
|
||||||
|
import { NapCatPathWrapper } from 'napcat-common/src/path';
|
||||||
|
import { NapCatOneBot11Adapter } from 'napcat-onebot';
|
||||||
|
import { NapCatProtocolAdapter } from 'napcat-protocol';
|
||||||
|
|
||||||
|
// 协议适配器类型
|
||||||
|
export type ProtocolAdapterType = 'onebot11' | 'napcat-protocol';
|
||||||
|
|
||||||
|
// 协议适配器接口
|
||||||
|
export interface IProtocolAdapter {
|
||||||
|
readonly name: string;
|
||||||
|
readonly enabled: boolean;
|
||||||
|
init (): Promise<void>;
|
||||||
|
close (): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 协议适配器包装器
|
||||||
|
class OneBotAdapterWrapper implements IProtocolAdapter {
|
||||||
|
readonly name = 'onebot11';
|
||||||
|
private adapter: NapCatOneBot11Adapter;
|
||||||
|
|
||||||
|
constructor (adapter: NapCatOneBot11Adapter) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
get enabled (): boolean {
|
||||||
|
return true; // OneBot11 默认启用
|
||||||
|
}
|
||||||
|
|
||||||
|
async init (): Promise<void> {
|
||||||
|
await this.adapter.InitOneBot();
|
||||||
|
}
|
||||||
|
|
||||||
|
async close (): Promise<void> {
|
||||||
|
await this.adapter.networkManager.closeAllAdapters();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdapter (): NapCatOneBot11Adapter {
|
||||||
|
return this.adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NapCat Protocol 适配器包装器
|
||||||
|
class NapCatProtocolAdapterWrapper implements IProtocolAdapter {
|
||||||
|
readonly name = 'napcat-protocol';
|
||||||
|
private adapter: NapCatProtocolAdapter;
|
||||||
|
|
||||||
|
constructor (adapter: NapCatProtocolAdapter) {
|
||||||
|
this.adapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
get enabled (): boolean {
|
||||||
|
return this.adapter.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init (): Promise<void> {
|
||||||
|
await this.adapter.initProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
async close (): Promise<void> {
|
||||||
|
await this.adapter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdapter (): NapCatProtocolAdapter {
|
||||||
|
return this.adapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 协议适配器管理器
|
||||||
|
export class NapCatAdapterManager {
|
||||||
|
private core: NapCatCore;
|
||||||
|
private context: InstanceContext;
|
||||||
|
private pathWrapper: NapCatPathWrapper;
|
||||||
|
|
||||||
|
// 协议适配器实例
|
||||||
|
private onebotAdapter: OneBotAdapterWrapper | null = null;
|
||||||
|
private napcatProtocolAdapter: NapCatProtocolAdapterWrapper | null = null;
|
||||||
|
|
||||||
|
// 所有已注册的适配器
|
||||||
|
private adapters: Map<string, IProtocolAdapter> = new Map();
|
||||||
|
|
||||||
|
constructor (core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) {
|
||||||
|
this.core = core;
|
||||||
|
this.context = context;
|
||||||
|
this.pathWrapper = pathWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化所有协议适配器
|
||||||
|
async initAdapters (): Promise<void> {
|
||||||
|
this.context.logger.log('[AdapterManager] 开始初始化协议适配器...');
|
||||||
|
|
||||||
|
// 初始化 OneBot11 适配器 (默认启用)
|
||||||
|
try {
|
||||||
|
const onebot = new NapCatOneBot11Adapter(this.core, this.context, this.pathWrapper);
|
||||||
|
this.onebotAdapter = new OneBotAdapterWrapper(onebot);
|
||||||
|
this.adapters.set('onebot11', this.onebotAdapter);
|
||||||
|
await this.onebotAdapter.init();
|
||||||
|
this.context.logger.log('[AdapterManager] OneBot11 适配器初始化完成');
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.logError('[AdapterManager] OneBot11 适配器初始化失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 NapCat Protocol 适配器 (默认关闭,需要配置启用)
|
||||||
|
try {
|
||||||
|
const napcatProtocol = new NapCatProtocolAdapter(this.core, this.context, this.pathWrapper);
|
||||||
|
this.napcatProtocolAdapter = new NapCatProtocolAdapterWrapper(napcatProtocol);
|
||||||
|
this.adapters.set('napcat-protocol', this.napcatProtocolAdapter);
|
||||||
|
|
||||||
|
if (this.napcatProtocolAdapter.enabled) {
|
||||||
|
await this.napcatProtocolAdapter.init();
|
||||||
|
this.context.logger.log('[AdapterManager] NapCat Protocol 适配器初始化完成');
|
||||||
|
} else {
|
||||||
|
this.context.logger.log('[AdapterManager] NapCat Protocol 适配器未启用,跳过初始化');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.logError('[AdapterManager] NapCat Protocol 适配器初始化失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.context.logger.log(`[AdapterManager] 协议适配器初始化完成,已加载 ${this.adapters.size} 个适配器`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 OneBot11 适配器
|
||||||
|
getOneBotAdapter (): NapCatOneBot11Adapter | null {
|
||||||
|
return this.onebotAdapter?.getAdapter() ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 NapCat Protocol 适配器
|
||||||
|
getNapCatProtocolAdapter (): NapCatProtocolAdapter | null {
|
||||||
|
return this.napcatProtocolAdapter?.getAdapter() ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定适配器
|
||||||
|
getAdapter (name: ProtocolAdapterType): IProtocolAdapter | undefined {
|
||||||
|
return this.adapters.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有已启用的适配器
|
||||||
|
getEnabledAdapters (): IProtocolAdapter[] {
|
||||||
|
return Array.from(this.adapters.values()).filter(adapter => adapter.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有适配器
|
||||||
|
getAllAdapters (): IProtocolAdapter[] {
|
||||||
|
return Array.from(this.adapters.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭所有适配器
|
||||||
|
async closeAllAdapters (): Promise<void> {
|
||||||
|
this.context.logger.log('[AdapterManager] 开始关闭所有协议适配器...');
|
||||||
|
|
||||||
|
for (const [name, adapter] of this.adapters) {
|
||||||
|
try {
|
||||||
|
await adapter.close();
|
||||||
|
this.context.logger.log(`[AdapterManager] ${name} 适配器已关闭`);
|
||||||
|
} catch (e) {
|
||||||
|
this.context.logger.logError(`[AdapterManager] 关闭 ${name} 适配器失败:`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapters.clear();
|
||||||
|
this.context.logger.log('[AdapterManager] 所有协议适配器已关闭');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新加载指定适配器
|
||||||
|
async reloadAdapter (name: ProtocolAdapterType): Promise<void> {
|
||||||
|
const adapter = this.adapters.get(name);
|
||||||
|
if (adapter) {
|
||||||
|
await adapter.close();
|
||||||
|
await adapter.init();
|
||||||
|
this.context.logger.log(`[AdapterManager] ${name} 适配器已重新加载`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NapCatOneBot11Adapter } from 'napcat-onebot';
|
||||||
|
export { NapCatProtocolAdapter } from 'napcat-protocol';
|
||||||
30
packages/napcat-adapter/package.json
Normal file
30
packages/napcat-adapter/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "napcat-adapter",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./index.ts"
|
||||||
|
},
|
||||||
|
"./*": {
|
||||||
|
"import": "./*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"napcat-core": "workspace:*",
|
||||||
|
"napcat-common": "workspace:*",
|
||||||
|
"napcat-onebot": "workspace:*",
|
||||||
|
"napcat-protocol": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "napcat-common",
|
"name": "napcat-common",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./src/index.ts"
|
||||||
},
|
},
|
||||||
"exports": {
|
"./src/*": {
|
||||||
".": {
|
"import": "./src/*"
|
||||||
"import": "./src/index.ts"
|
|
||||||
},
|
|
||||||
"./src/*": {
|
|
||||||
"import": "./src/*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ajv": "^8.13.0",
|
|
||||||
"file-type": "^21.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^22.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.13.0",
|
||||||
|
"file-type": "^21.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,14 @@ 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 http from 'node:http';
|
||||||
|
import tls from 'node:tls';
|
||||||
import { solveProblem } from '@/napcat-common/src/helper';
|
import { solveProblem } from '@/napcat-common/src/helper';
|
||||||
|
|
||||||
export interface HttpDownloadOptions {
|
export interface HttpDownloadOptions {
|
||||||
url: string;
|
url: string;
|
||||||
headers?: Record<string, string> | string;
|
headers?: Record<string, string> | string;
|
||||||
|
proxy?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Uri2LocalRes = {
|
type Uri2LocalRes = {
|
||||||
@@ -96,6 +99,7 @@ export function calculateFileMD5 (filePath: string): Promise<string> {
|
|||||||
|
|
||||||
async function tryDownload (options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
async function tryDownload (options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
||||||
let url: string;
|
let url: string;
|
||||||
|
let proxy: string | undefined;
|
||||||
let headers: Record<string, string> = {
|
let headers: Record<string, string> = {
|
||||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
|
||||||
};
|
};
|
||||||
@@ -104,6 +108,7 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
|||||||
headers['Host'] = new URL(url).hostname;
|
headers['Host'] = new URL(url).hostname;
|
||||||
} else {
|
} else {
|
||||||
url = options.url;
|
url = options.url;
|
||||||
|
proxy = options.proxy;
|
||||||
if (options.headers) {
|
if (options.headers) {
|
||||||
if (typeof options.headers === 'string') {
|
if (typeof options.headers === 'string') {
|
||||||
headers = JSON.parse(options.headers);
|
headers = JSON.parse(options.headers);
|
||||||
@@ -115,6 +120,18 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
|||||||
if (useReferer && !headers['Referer']) {
|
if (useReferer && !headers['Referer']) {
|
||||||
headers['Referer'] = url;
|
headers['Referer'] = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果配置了代理,使用代理下载
|
||||||
|
if (proxy) {
|
||||||
|
try {
|
||||||
|
const response = await httpDownloadWithProxy(url, headers, proxy);
|
||||||
|
return new Response(response, { status: 200, statusText: 'OK' });
|
||||||
|
} catch (proxyError) {
|
||||||
|
// 如果代理失败,记录错误并尝试直接下载
|
||||||
|
console.error('代理下载失败,尝试直接下载:', proxyError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchRes = await fetch(url, { headers, redirect: 'follow' }).catch((err) => {
|
const fetchRes = await fetch(url, { headers, redirect: 'follow' }).catch((err) => {
|
||||||
if (err.cause) {
|
if (err.cause) {
|
||||||
throw err.cause;
|
throw err.cause;
|
||||||
@@ -124,6 +141,220 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
|||||||
return fetchRes;
|
return fetchRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用 HTTP/HTTPS 代理下载文件
|
||||||
|
*/
|
||||||
|
function httpDownloadWithProxy (url: string, headers: Record<string, string>, proxy: string): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const targetUrl = new URL(url);
|
||||||
|
const proxyUrl = new URL(proxy);
|
||||||
|
|
||||||
|
const isTargetHttps = targetUrl.protocol === 'https:';
|
||||||
|
const proxyPort = parseInt(proxyUrl.port) || (proxyUrl.protocol === 'https:' ? 443 : 80);
|
||||||
|
|
||||||
|
// 代理认证头
|
||||||
|
const proxyAuthHeader = proxyUrl.username && proxyUrl.password
|
||||||
|
? { 'Proxy-Authorization': 'Basic ' + Buffer.from(`${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`).toString('base64') }
|
||||||
|
: {};
|
||||||
|
|
||||||
|
if (isTargetHttps) {
|
||||||
|
// HTTPS 目标:需要通过 CONNECT 建立隧道
|
||||||
|
const connectReq = http.request({
|
||||||
|
host: proxyUrl.hostname,
|
||||||
|
port: proxyPort,
|
||||||
|
method: 'CONNECT',
|
||||||
|
path: `${targetUrl.hostname}:${targetUrl.port || 443}`,
|
||||||
|
headers: {
|
||||||
|
'Host': `${targetUrl.hostname}:${targetUrl.port || 443}`,
|
||||||
|
...proxyAuthHeader,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
connectReq.on('connect', (res, socket) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
socket.destroy();
|
||||||
|
reject(new Error(`代理 CONNECT 失败: ${res.statusCode} ${res.statusMessage}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在隧道上建立 TLS 连接
|
||||||
|
const tlsSocket = tls.connect({
|
||||||
|
socket: socket,
|
||||||
|
servername: targetUrl.hostname,
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
}, () => {
|
||||||
|
// TLS 握手成功,发送 HTTP 请求
|
||||||
|
const requestPath = targetUrl.pathname + targetUrl.search;
|
||||||
|
const requestHeaders = {
|
||||||
|
...headers,
|
||||||
|
'Host': targetUrl.hostname,
|
||||||
|
'Connection': 'close',
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerLines = Object.entries(requestHeaders)
|
||||||
|
.map(([key, value]) => `${key}: ${value}`)
|
||||||
|
.join('\r\n');
|
||||||
|
|
||||||
|
const httpRequest = `GET ${requestPath} HTTP/1.1\r\n${headerLines}\r\n\r\n`;
|
||||||
|
tlsSocket.write(httpRequest);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析 HTTP 响应
|
||||||
|
let responseData = Buffer.alloc(0);
|
||||||
|
let headersParsed = false;
|
||||||
|
let statusCode = 0;
|
||||||
|
let isChunked = false;
|
||||||
|
let bodyData = Buffer.alloc(0);
|
||||||
|
let redirectLocation: string | null = null;
|
||||||
|
|
||||||
|
tlsSocket.on('data', (chunk: Buffer) => {
|
||||||
|
responseData = Buffer.concat([responseData, chunk]);
|
||||||
|
|
||||||
|
if (!headersParsed) {
|
||||||
|
const headerEndIndex = responseData.indexOf('\r\n\r\n');
|
||||||
|
if (headerEndIndex !== -1) {
|
||||||
|
headersParsed = true;
|
||||||
|
const headerStr = responseData.subarray(0, headerEndIndex).toString();
|
||||||
|
const headerLines = headerStr.split('\r\n');
|
||||||
|
|
||||||
|
// 解析状态码
|
||||||
|
const statusLine = headerLines[0];
|
||||||
|
const statusMatch = statusLine?.match(/HTTP\/\d\.\d\s+(\d+)/);
|
||||||
|
statusCode = statusMatch ? parseInt(statusMatch[1]!) : 0;
|
||||||
|
|
||||||
|
// 解析响应头
|
||||||
|
for (const line of headerLines.slice(1)) {
|
||||||
|
const [key, ...valueParts] = line.split(':');
|
||||||
|
const value = valueParts.join(':').trim();
|
||||||
|
if (key?.toLowerCase() === 'transfer-encoding' && value.toLowerCase() === 'chunked') {
|
||||||
|
isChunked = true;
|
||||||
|
} else if (key?.toLowerCase() === 'location') {
|
||||||
|
redirectLocation = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyData = responseData.subarray(headerEndIndex + 4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bodyData = Buffer.concat([bodyData, chunk]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tlsSocket.on('end', () => {
|
||||||
|
// 处理重定向
|
||||||
|
if (statusCode >= 300 && statusCode < 400 && redirectLocation) {
|
||||||
|
const redirectUrl = redirectLocation.startsWith('http')
|
||||||
|
? redirectLocation
|
||||||
|
: `${targetUrl.protocol}//${targetUrl.host}${redirectLocation}`;
|
||||||
|
httpDownloadWithProxy(redirectUrl, headers, proxy).then(resolve).catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusCode !== 200) {
|
||||||
|
reject(new Error(`下载失败: ${statusCode}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 chunked 编码
|
||||||
|
if (isChunked) {
|
||||||
|
resolve(parseChunkedBody(bodyData));
|
||||||
|
} else {
|
||||||
|
resolve(bodyData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tlsSocket.on('error', (err) => {
|
||||||
|
reject(new Error(`TLS 连接错误: ${err.message}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
connectReq.on('error', (err) => {
|
||||||
|
reject(new Error(`代理连接错误: ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
connectReq.end();
|
||||||
|
} else {
|
||||||
|
// HTTP 目标:直接通过代理请求
|
||||||
|
const req = http.request({
|
||||||
|
host: proxyUrl.hostname,
|
||||||
|
port: proxyPort,
|
||||||
|
method: 'GET',
|
||||||
|
path: url, // 完整 URL
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
'Host': targetUrl.hostname,
|
||||||
|
...proxyAuthHeader,
|
||||||
|
},
|
||||||
|
}, (response) => {
|
||||||
|
handleResponse(response, resolve, reject, url, headers, proxy);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (err) => {
|
||||||
|
reject(new Error(`代理请求错误: ${err.message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 chunked 编码的响应体
|
||||||
|
*/
|
||||||
|
function parseChunkedBody (data: Buffer): Buffer {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
while (offset < data.length) {
|
||||||
|
// 查找 chunk 大小行的结束
|
||||||
|
const lineEnd = data.indexOf('\r\n', offset);
|
||||||
|
if (lineEnd === -1) break;
|
||||||
|
|
||||||
|
const sizeStr = data.subarray(offset, lineEnd).toString().split(';')[0]; // 忽略 chunk 扩展
|
||||||
|
const chunkSize = parseInt(sizeStr!, 16);
|
||||||
|
|
||||||
|
if (chunkSize === 0) break; // 最后一个 chunk
|
||||||
|
|
||||||
|
const chunkStart = lineEnd + 2;
|
||||||
|
const chunkEnd = chunkStart + chunkSize;
|
||||||
|
|
||||||
|
if (chunkEnd > data.length) break;
|
||||||
|
|
||||||
|
chunks.push(data.subarray(chunkStart, chunkEnd));
|
||||||
|
offset = chunkEnd + 2; // 跳过 chunk 数据后的 \r\n
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.concat(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 HTTP 响应
|
||||||
|
*/
|
||||||
|
function handleResponse (
|
||||||
|
response: http.IncomingMessage,
|
||||||
|
resolve: (value: Buffer) => void,
|
||||||
|
reject: (reason: Error) => void,
|
||||||
|
_url: string,
|
||||||
|
headers: Record<string, string>,
|
||||||
|
proxy: string
|
||||||
|
): void {
|
||||||
|
// 处理重定向
|
||||||
|
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
||||||
|
httpDownloadWithProxy(response.headers.location, headers, proxy).then(resolve).catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
reject(new Error(`下载失败: ${response.statusCode} ${response.statusMessage}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
response.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
response.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
response.on('error', reject);
|
||||||
|
}
|
||||||
|
|
||||||
export async function httpDownload (options: string | HttpDownloadOptions): Promise<Buffer> {
|
export async function httpDownload (options: string | HttpDownloadOptions): Promise<Buffer> {
|
||||||
const useReferer = typeof options === 'string';
|
const useReferer = typeof options === 'string';
|
||||||
let resp = await tryDownload(options);
|
let resp = await tryDownload(options);
|
||||||
@@ -176,7 +407,7 @@ export async function checkUriType (Uri: string) {
|
|||||||
return { Uri, Type: FileUriType.Unknown };
|
return { Uri, Type: FileUriType.Unknown };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uriToLocalFile (dir: string, uri: string, filename: string = randomUUID(), headers?: Record<string, string>): Promise<Uri2LocalRes> {
|
export async function uriToLocalFile (dir: string, uri: string, filename: string = randomUUID(), headers?: Record<string, string>, proxy?: string): Promise<Uri2LocalRes> {
|
||||||
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
|
||||||
|
|
||||||
const filePath = path.join(dir, filename);
|
const filePath = path.join(dir, filename);
|
||||||
@@ -191,7 +422,7 @@ export async function uriToLocalFile (dir: string, uri: string, filename: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
case FileUriType.Remote: {
|
case FileUriType.Remote: {
|
||||||
const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} });
|
const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {}, proxy });
|
||||||
fs.writeFileSync(filePath, buffer);
|
fs.writeFileSync(filePath, buffer);
|
||||||
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
return { success: true, errMsg: '', fileName: filename, path: filePath };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ export class LimitedHashTable<K, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取最近刚写入的几个值
|
// 获取最近刚写入的几个值
|
||||||
getHeads (size: number): { key: K; value: V }[] | undefined {
|
getHeads (size: number): { key: K; value: V; }[] | undefined {
|
||||||
const keyList = this.getKeyList();
|
const keyList = this.getKeyList();
|
||||||
if (keyList.length === 0) {
|
if (keyList.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const result: { key: K; value: V }[] = [];
|
const result: { key: K; value: V; }[] = [];
|
||||||
const listSize = Math.min(size, keyList.length);
|
const listSize = Math.min(size, keyList.length);
|
||||||
for (let i = 0; i < listSize; i++) {
|
for (let i = 0; i < listSize; i++) {
|
||||||
const key = keyList[listSize - i];
|
const key = keyList[listSize - i];
|
||||||
@@ -108,7 +108,7 @@ class MessageUniqueWrapper {
|
|||||||
return shortId;
|
return shortId;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer } | undefined {
|
getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer; } | undefined {
|
||||||
const data = this.msgDataMap.getKey(shortId);
|
const data = this.msgDataMap.getKey(shortId);
|
||||||
if (data) {
|
if (data) {
|
||||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||||
@@ -136,6 +136,12 @@ class MessageUniqueWrapper {
|
|||||||
this.msgIdMap.resize(maxSize);
|
this.msgIdMap.resize(maxSize);
|
||||||
this.msgDataMap.resize(maxSize);
|
this.msgDataMap.resize(maxSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isShortId (message_id: string): boolean {
|
||||||
|
const num = Number(message_id);
|
||||||
|
// 判断是否是整数并且在 INT32 的范围内
|
||||||
|
return Number.isInteger(num) && num >= -2147483648 && num <= 2147483647;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
|
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();
|
||||||
|
|||||||
@@ -383,17 +383,14 @@ export async function testUrlHead (url: string, timeout: number = 5000): Promise
|
|||||||
}, (res) => {
|
}, (res) => {
|
||||||
const statusCode = res.statusCode || 0;
|
const statusCode = res.statusCode || 0;
|
||||||
const contentType = (res.headers['content-type'] as string) || '';
|
const contentType = (res.headers['content-type'] as string) || '';
|
||||||
const contentLength = parseInt((res.headers['content-length'] as string) || '0', 10);
|
|
||||||
|
|
||||||
// 验证条件:
|
// 简化验证条件:
|
||||||
// 1. 状态码 2xx 或 3xx
|
// 1. 状态码 2xx 或 3xx
|
||||||
// 2. Content-Type 不应该是 text/html(表示错误页面)
|
// 2. Content-Type 不应该是 text/html(表示错误页面)
|
||||||
// 3. 对于 .zip 文件,Content-Length 应该 > 1MB(避免获取到错误页面)
|
|
||||||
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
||||||
const isNotHtmlError = !contentType.includes('text/html');
|
const isNotHtmlError = !contentType.includes('text/html');
|
||||||
const isValidSize = url.endsWith('.zip') ? contentLength > 1024 * 1024 : true;
|
|
||||||
|
|
||||||
resolve(isValidStatus && isNotHtmlError && isValidSize);
|
resolve(isValidStatus && isNotHtmlError);
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', () => resolve(false));
|
req.on('error', () => resolve(false));
|
||||||
@@ -437,10 +434,9 @@ export async function validateUrl (url: string, timeout: number = 5000): Promise
|
|||||||
const contentType = (res.headers['content-type'] as string) || '';
|
const contentType = (res.headers['content-type'] as string) || '';
|
||||||
const contentLength = parseInt((res.headers['content-length'] as string) || '0', 10);
|
const contentLength = parseInt((res.headers['content-length'] as string) || '0', 10);
|
||||||
|
|
||||||
// 验证条件
|
// 简化验证条件
|
||||||
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
||||||
const isNotHtmlError = !contentType.includes('text/html');
|
const isNotHtmlError = !contentType.includes('text/html');
|
||||||
const isValidSize = url.endsWith('.zip') ? contentLength > 1024 * 1024 : true;
|
|
||||||
|
|
||||||
if (!isValidStatus) {
|
if (!isValidStatus) {
|
||||||
resolve({
|
resolve({
|
||||||
@@ -458,14 +454,6 @@ export async function validateUrl (url: string, timeout: number = 5000): Promise
|
|||||||
contentLength,
|
contentLength,
|
||||||
error: '返回了 HTML 页面而非文件',
|
error: '返回了 HTML 页面而非文件',
|
||||||
});
|
});
|
||||||
} else if (!isValidSize) {
|
|
||||||
resolve({
|
|
||||||
valid: false,
|
|
||||||
statusCode,
|
|
||||||
contentType,
|
|
||||||
contentLength,
|
|
||||||
error: `文件过小 (${contentLength} bytes),可能是错误页面`,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
resolve({
|
resolve({
|
||||||
valid: true,
|
valid: true,
|
||||||
@@ -542,21 +530,21 @@ export async function findAvailableDownloadUrl (
|
|||||||
const testWithValidation = async (url: string): Promise<boolean> => {
|
const testWithValidation = async (url: string): Promise<boolean> => {
|
||||||
if (validateContent) {
|
if (validateContent) {
|
||||||
const result = await validateUrl(url, timeout);
|
const result = await validateUrl(url, timeout);
|
||||||
// 额外检查文件大小
|
// 额外检查文件大小(仅当指定了 minFileSize 时)
|
||||||
if (result.valid && minFileSize && result.contentLength && result.contentLength < minFileSize) {
|
if (result.valid && minFileSize && result.contentLength && result.contentLength < minFileSize) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return result.valid;
|
return result.valid;
|
||||||
}
|
}
|
||||||
return testMethod === 'head' ? testUrlHead(url, timeout) : testUrl(url, timeout);
|
// 不验证内容,只检查状态码
|
||||||
|
const isValid = testMethod === 'head' ? await testUrlHead(url, timeout) : await testUrl(url, timeout);
|
||||||
|
return isValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. 如果设置了自定义镜像,优先使用
|
// 1. 如果设置了自定义镜像,直接使用(不测试,信任用户选择)
|
||||||
if (customMirror) {
|
if (customMirror) {
|
||||||
const customUrl = buildMirrorUrl(originalUrl, customMirror);
|
const customUrl = buildMirrorUrl(originalUrl, customMirror);
|
||||||
if (await testWithValidation(customUrl)) {
|
return customUrl;
|
||||||
return customUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 先测试原始 URL
|
// 2. 先测试原始 URL
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
FileListResponse,
|
FileListResponse,
|
||||||
FlashFileSetInfo,
|
FlashFileSetInfo,
|
||||||
SendStatus,
|
SendStatus,
|
||||||
|
UploadSceneType,
|
||||||
} from '@/napcat-core/data/flash';
|
} from '@/napcat-core/data/flash';
|
||||||
import { Peer } from '@/napcat-core/types';
|
import { Peer } from '@/napcat-core/types';
|
||||||
|
|
||||||
@@ -19,25 +20,44 @@ export class NTQQFlashApi {
|
|||||||
/**
|
/**
|
||||||
* 发起闪传上传任务
|
* 发起闪传上传任务
|
||||||
* @param fileListToUpload 上传文件绝对路径的列表,可以是文件夹!!
|
* @param fileListToUpload 上传文件绝对路径的列表,可以是文件夹!!
|
||||||
|
* @param thumbnailPath
|
||||||
|
* @param filesetName
|
||||||
*/
|
*/
|
||||||
async createFlashTransferUploadTask (fileListToUpload: string[]): Promise < GeneralCallResult & {
|
async createFlashTransferUploadTask (fileListToUpload: string[], thumbnailPath: string, filesetName: string): Promise<GeneralCallResult & {
|
||||||
createFlashTransferResult: createFlashTransferResult;
|
createFlashTransferResult: createFlashTransferResult;
|
||||||
seq: number;
|
seq: number;
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const timestamp : number = Date.now();
|
const timestamp: number = Date.now();
|
||||||
const selfInfo = this.core.selfInfo;
|
const selfInfo = this.core.selfInfo;
|
||||||
|
|
||||||
const fileUploadArg = {
|
const fileUploadArg = {
|
||||||
screen: 1, // 1
|
screen: 1, // 1
|
||||||
|
name: filesetName,
|
||||||
uploaders: [{
|
uploaders: [{
|
||||||
uin: selfInfo.uin,
|
uin: selfInfo.uin,
|
||||||
uid: selfInfo.uid,
|
uid: selfInfo.uid,
|
||||||
sendEntrance: '',
|
sendEntrance: '',
|
||||||
nickname: selfInfo.nick,
|
nickname: selfInfo.nick,
|
||||||
}],
|
}],
|
||||||
|
coverPath: thumbnailPath,
|
||||||
paths: fileListToUpload,
|
paths: fileListToUpload,
|
||||||
|
excludePaths: [],
|
||||||
|
expireLeftTime: 0,
|
||||||
|
isNeedDelDeviceInfo: false,
|
||||||
|
isNeedDelLocation: false,
|
||||||
|
coverOriginalInfos: [
|
||||||
|
{
|
||||||
|
path: fileListToUpload[0] || '',
|
||||||
|
thumbnailPath,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
uploadSceneType: UploadSceneType.KUPLOADSCENEAIOFILESELECTOR, // 不知道怎么枚举 先硬编码吧 (PC QQ 10)
|
||||||
|
detectPrivacyInfoResult: {
|
||||||
|
exists: false,
|
||||||
|
allDetectResults: new Map(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadResult = await flashService.createFlashTransferUploadTask(timestamp, fileUploadArg);
|
const uploadResult = await flashService.createFlashTransferUploadTask(timestamp, fileUploadArg);
|
||||||
@@ -54,9 +74,9 @@ export class NTQQFlashApi {
|
|||||||
* 下载闪传文件集
|
* 下载闪传文件集
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
async downloadFileSetBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
async downloadFileSetBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||||
extraInfo: unknown
|
extraInfo: unknown;
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const result = await flashService.startFileSetDownload(fileSetId, 1, { isIncludeCompressInnerFiles: false }); // 为了方便,暂时硬编码
|
const result = await flashService.startFileSetDownload(fileSetId, 1, { isIncludeCompressInnerFiles: false }); // 为了方便,暂时硬编码
|
||||||
@@ -72,7 +92,7 @@ export class NTQQFlashApi {
|
|||||||
* 获取闪传的外链分享
|
* 获取闪传的外链分享
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
async getShareLinkBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
async getShareLinkBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||||
shareLink: string;
|
shareLink: string;
|
||||||
expireTimestamp: string;
|
expireTimestamp: string;
|
||||||
}> {
|
}> {
|
||||||
@@ -91,9 +111,9 @@ export class NTQQFlashApi {
|
|||||||
* 从分享外链获取文件集id
|
* 从分享外链获取文件集id
|
||||||
* @param shareCode
|
* @param shareCode
|
||||||
*/
|
*/
|
||||||
async fromShareLinkFindSetId (shareCode: string): Promise < GeneralCallResult & {
|
async fromShareLinkFindSetId (shareCode: string): Promise<GeneralCallResult & {
|
||||||
fileSetId: string;
|
fileSetId: string;
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const result = await flashService.getFileSetIdByCode(shareCode);
|
const result = await flashService.getFileSetIdByCode(shareCode);
|
||||||
@@ -110,7 +130,7 @@ export class NTQQFlashApi {
|
|||||||
* == 注意返回结构和其它的不同,没有GeneralCallResult!!! ==
|
* == 注意返回结构和其它的不同,没有GeneralCallResult!!! ==
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
async getFileListBySetId (fileSetId: string): Promise < FileListResponse > {
|
async getFileListBySetId (fileSetId: string): Promise<FileListResponse> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const requestArg = {
|
const requestArg = {
|
||||||
@@ -153,11 +173,11 @@ export class NTQQFlashApi {
|
|||||||
* 获取闪传文件集合信息
|
* 获取闪传文件集合信息
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
async getFileSetIndoBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
async getFileSetIndoBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||||
seq: number;
|
seq: number;
|
||||||
isCache: boolean;
|
isCache: boolean;
|
||||||
fileSet: FlashFileSetInfo;
|
fileSet: FlashFileSetInfo;
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const requestArg = {
|
const requestArg = {
|
||||||
@@ -178,13 +198,13 @@ export class NTQQFlashApi {
|
|||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
* @param peer
|
* @param peer
|
||||||
*/
|
*/
|
||||||
async sendFlashMessage (fileSetId: string, peer:Peer): Promise < {
|
async sendFlashMessage (fileSetId: string, peer: Peer): Promise<{
|
||||||
errCode: number,
|
errCode: number,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
rsp: {
|
rsp: {
|
||||||
sendStatus: SendStatus[]
|
sendStatus: SendStatus[];
|
||||||
}
|
};
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
|
|
||||||
const target = {
|
const target = {
|
||||||
@@ -212,9 +232,9 @@ export class NTQQFlashApi {
|
|||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
async getFileTransUrl (fileSetId: string, options: { fileName?: string; fileIndex?: number }): Promise < GeneralCallResult & {
|
async getFileTransUrl (fileSetId: string, options: { fileName?: string; fileIndex?: number; }): Promise<GeneralCallResult & {
|
||||||
transferUrl: string;
|
transferUrl: string;
|
||||||
} > {
|
}> {
|
||||||
const flashService = this.context.session.getFlashTransferService();
|
const flashService = this.context.session.getFlashTransferService();
|
||||||
const result = await this.getFileListBySetId(fileSetId);
|
const result = await this.getFileListBySetId(fileSetId);
|
||||||
|
|
||||||
@@ -261,4 +281,27 @@ export class NTQQFlashApi {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createFileThumbnail (filePath: string): Promise<any> {
|
||||||
|
const msgService = this.context.session.getMsgService();
|
||||||
|
const savePath = msgService.getFileThumbSavePathForSend(750, true);
|
||||||
|
|
||||||
|
const result = await this.core.util.createThumbnailImage(
|
||||||
|
'flashtransfer',
|
||||||
|
filePath,
|
||||||
|
savePath,
|
||||||
|
{
|
||||||
|
width: 520,
|
||||||
|
height: 520,
|
||||||
|
},
|
||||||
|
'jpeg',
|
||||||
|
null
|
||||||
|
);
|
||||||
|
if (result.result === 0) {
|
||||||
|
this.context.logger.log('获取缩略图成功!!');
|
||||||
|
result.targetPath = savePath;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class NTQQFriendApi {
|
|||||||
return retMap;
|
return retMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async delBuudy (uid: string, tempBlock = false, tempBothDel = false) {
|
async delBuddy (uid: string, tempBlock = false, tempBothDel = false) {
|
||||||
return this.context.session.getBuddyService().delBuddy({
|
return this.context.session.getBuddyService().delBuddy({
|
||||||
friendUid: uid,
|
friendUid: uid,
|
||||||
tempBlock,
|
tempBlock,
|
||||||
|
|||||||
@@ -114,8 +114,9 @@ export class NTQQOnlineApi {
|
|||||||
fileElement: {
|
fileElement: {
|
||||||
fileName: actualFolderName,
|
fileName: actualFolderName,
|
||||||
filePath: folderPath,
|
filePath: folderPath,
|
||||||
|
fileSize: "",
|
||||||
},
|
},
|
||||||
} as any;
|
};
|
||||||
|
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
const startTime = Math.floor(Date.now() / 1000) - 2;
|
const startTime = Math.floor(Date.now() / 1000) - 2;
|
||||||
@@ -173,7 +174,7 @@ export class NTQQOnlineApi {
|
|||||||
* 获取好友的在线文件消息
|
* 获取好友的在线文件消息
|
||||||
* @param peer
|
* @param peer
|
||||||
*/
|
*/
|
||||||
async getOnlineFileMsg (peer: Peer) : Promise<any> {
|
async getOnlineFileMsg (peer: Peer): Promise<any> {
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
return await msgService.getOnlineFileMsgs(peer);
|
return await msgService.getOnlineFileMsgs(peer);
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ export class NTQQOnlineApi {
|
|||||||
* @param peer
|
* @param peer
|
||||||
* @param msgId
|
* @param msgId
|
||||||
*/
|
*/
|
||||||
async cancelMyOnlineFileMsg (peer: Peer, msgId: string) : Promise<void> {
|
async cancelMyOnlineFileMsg (peer: Peer, msgId: string): Promise<void> {
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
await msgService.cancelSendMsg(peer, msgId);
|
await msgService.cancelSendMsg(peer, msgId);
|
||||||
}
|
}
|
||||||
@@ -194,7 +195,7 @@ export class NTQQOnlineApi {
|
|||||||
* @param msgId
|
* @param msgId
|
||||||
* @param elementId
|
* @param elementId
|
||||||
*/
|
*/
|
||||||
async refuseOnlineFileMsg (peer: Peer, msgId: string, elementId: string) : Promise<void> {
|
async refuseOnlineFileMsg (peer: Peer, msgId: string, elementId: string): Promise<void> {
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
const arrToSend = {
|
const arrToSend = {
|
||||||
msgId,
|
msgId,
|
||||||
@@ -215,7 +216,7 @@ export class NTQQOnlineApi {
|
|||||||
* @param elementId
|
* @param elementId
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
async receiveOnlineFileOrFolder (peer: Peer, msgId: string, elementId: string) : Promise<any> {
|
async receiveOnlineFileOrFolder (peer: Peer, msgId: string, elementId: string): Promise<any> {
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
const arrToSend = {
|
const arrToSend = {
|
||||||
msgId,
|
msgId,
|
||||||
@@ -233,7 +234,7 @@ export class NTQQOnlineApi {
|
|||||||
* @param peer
|
* @param peer
|
||||||
* @param msgId
|
* @param msgId
|
||||||
*/
|
*/
|
||||||
async switchFileToOffline (peer: Peer, msgId: string) : Promise<void> {
|
async switchFileToOffline (peer: Peer, msgId: string): Promise<void> {
|
||||||
const msgService = this.context.session.getMsgService();
|
const msgService = this.context.session.getMsgService();
|
||||||
await msgService.switchToOfflineSendMsg(peer, msgId);
|
await msgService.switchToOfflineSendMsg(peer, msgId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface FlashBaseRequest {
|
export interface FlashBaseRequest {
|
||||||
fileSetId: string
|
fileSetId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UploaderInfo {
|
export interface UploaderInfo {
|
||||||
@@ -19,14 +19,14 @@ export interface thumbnailInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SendTarget {
|
export interface SendTarget {
|
||||||
destType: number // 1私聊
|
destType: number; // 1私聊
|
||||||
destUin?: string,
|
destUin?: string,
|
||||||
destUid: string,
|
destUid: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendTargetRequests {
|
export interface SendTargetRequests {
|
||||||
fileSetId: string
|
fileSetId: string;
|
||||||
targets: SendTarget[]
|
targets: SendTarget[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadStatusInfo {
|
export interface DownloadStatusInfo {
|
||||||
@@ -53,13 +53,13 @@ export interface DownloadStatusInfo {
|
|||||||
isAllFileAlreadyDownloaded: boolean,
|
isAllFileAlreadyDownloaded: boolean,
|
||||||
saveFileSetDir: string,
|
saveFileSetDir: string,
|
||||||
allWaitingStatusTask: boolean,
|
allWaitingStatusTask: boolean,
|
||||||
downloadSceneType: number,
|
downloadSceneType: DownloadSceneType,
|
||||||
retryCount: number,
|
retryCount: number,
|
||||||
statisticInfo: {
|
statisticInfo: {
|
||||||
downloadTaskId: string,
|
downloadTaskId: string,
|
||||||
downloadFilesetName: string,
|
downloadFilesetName: string,
|
||||||
downloadFileTypeDistribution: string,
|
downloadFileTypeDistribution: string,
|
||||||
downloadFileSizeDistribution: string
|
downloadFileSizeDistribution: string;
|
||||||
},
|
},
|
||||||
albumStorageFailImageNum: number,
|
albumStorageFailImageNum: number,
|
||||||
albumStorageFailVideoNum: number,
|
albumStorageFailVideoNum: number,
|
||||||
@@ -67,8 +67,8 @@ export interface DownloadStatusInfo {
|
|||||||
albumStorageSucImageNum: number,
|
albumStorageSucImageNum: number,
|
||||||
albumStorageSucVideoNum: number,
|
albumStorageSucVideoNum: number,
|
||||||
albumStorageSucFileIdList: [],
|
albumStorageSucFileIdList: [],
|
||||||
albumStorageFileNum: number
|
albumStorageFileNum: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface physicalInfo {
|
export interface physicalInfo {
|
||||||
@@ -95,94 +95,94 @@ export interface uploadInfo {
|
|||||||
svrRrrCode: number,
|
svrRrrCode: number,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
isNeedDelDeviceInfo: boolean,
|
isNeedDelDeviceInfo: boolean,
|
||||||
thumbnailUploadState: number
|
thumbnailUploadState: number;
|
||||||
isSecondHit: boolean,
|
isSecondHit: boolean,
|
||||||
hasModifiedErr: boolean,
|
hasModifiedErr: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface folderUploadInfo {
|
export interface folderUploadInfo {
|
||||||
totalUploadedFileSize: string
|
totalUploadedFileSize: string;
|
||||||
successCount: number
|
successCount: number;
|
||||||
failedCount: number
|
failedCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface folderDownloadInfo {
|
export interface folderDownloadInfo {
|
||||||
totalDownloadedFileSize: string
|
totalDownloadedFileSize: string;
|
||||||
totalFileSize: string
|
totalFileSize: string;
|
||||||
totalDownloadFileCount: number
|
totalDownloadFileCount: number;
|
||||||
successCount: number
|
successCount: number;
|
||||||
failedCount: number
|
failedCount: number;
|
||||||
pausedCount: number
|
pausedCount: number;
|
||||||
cancelCount: number
|
cancelCount: number;
|
||||||
downloadingCount: number
|
downloadingCount: number;
|
||||||
partialDownloadCount: number
|
partialDownloadCount: number;
|
||||||
curLevelDownloadedFileCount: number
|
curLevelDownloadedFileCount: number;
|
||||||
curLevelUnDownloadedFileCount: number
|
curLevelUnDownloadedFileCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface compressFileFolderInfo {
|
export interface compressFileFolderInfo {
|
||||||
downloadStatus: number
|
downloadStatus: number;
|
||||||
saveFileDirPath: string
|
saveFileDirPath: string;
|
||||||
totalFileCount: string
|
totalFileCount: string;
|
||||||
totalFileSize: string
|
totalFileSize: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface albumStorgeInfo {
|
export interface albumStorgeInfo {
|
||||||
status: number
|
status: number;
|
||||||
localIdentifier: string
|
localIdentifier: string;
|
||||||
errorCode: number
|
errorCode: number;
|
||||||
timeCost: number
|
timeCost: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlashOneFileInfo {
|
export interface FlashOneFileInfo {
|
||||||
fileSetId: string
|
fileSetId: string;
|
||||||
cliFileId: string // client?? 或许可以换取url
|
cliFileId: string; // client?? 或许可以换取url
|
||||||
compressedFileFolderId: string
|
compressedFileFolderId: string;
|
||||||
archiveIndex: 0
|
archiveIndex: 0;
|
||||||
indexPath: string
|
indexPath: string;
|
||||||
isDir: boolean // 文件或者文件夹!!
|
isDir: boolean; // 文件或者文件夹!!
|
||||||
parentId: string
|
parentId: string;
|
||||||
depth: number // 1
|
depth: number; // 1
|
||||||
cliFileIndex: number
|
cliFileIndex: number;
|
||||||
fileType: number // 枚举!! 已完成枚举!!
|
fileType: number; // 枚举!! 已完成枚举!!
|
||||||
name: string
|
name: string;
|
||||||
namePinyin: string
|
namePinyin: string;
|
||||||
isCover: boolean
|
isCover: boolean;
|
||||||
isCoverOriginal: boolean
|
isCoverOriginal: boolean;
|
||||||
fileSize: string
|
fileSize: string;
|
||||||
fileCount: number
|
fileCount: number;
|
||||||
thumbnail: thumbnailInfo
|
thumbnail: thumbnailInfo;
|
||||||
physical: physicalInfo
|
physical: physicalInfo;
|
||||||
srvFileId: string // service?? 服务器上面的id吗?
|
srvFileId: string; // service?? 服务器上面的id吗?
|
||||||
srvParentFileId: string
|
srvParentFileId: string;
|
||||||
svrLastUpdateTimestamp: string
|
svrLastUpdateTimestamp: string;
|
||||||
downloadInfo: downloadInfo
|
downloadInfo: downloadInfo;
|
||||||
saveFilePath: string
|
saveFilePath: string;
|
||||||
search_relative_path: string
|
search_relative_path: string;
|
||||||
disk_relative_path: string
|
disk_relative_path: string;
|
||||||
uploadInfo: uploadInfo
|
uploadInfo: uploadInfo;
|
||||||
status: number
|
status: number;
|
||||||
uploadStatus: number // 3已上传成功
|
uploadStatus: number; // 3已上传成功
|
||||||
downloadStatus: number // 0未下载
|
downloadStatus: number; // 0未下载
|
||||||
folderUploadInfo: folderUploadInfo
|
folderUploadInfo: folderUploadInfo;
|
||||||
folderDownloadInfo: folderDownloadInfo
|
folderDownloadInfo: folderDownloadInfo;
|
||||||
sha1: string
|
sha1: string;
|
||||||
bookmark: string
|
bookmark: string;
|
||||||
compressFileFolderInfo: compressFileFolderInfo
|
compressFileFolderInfo: compressFileFolderInfo;
|
||||||
uploadPauseReason: string
|
uploadPauseReason: string;
|
||||||
downloadPauseReason: string
|
downloadPauseReason: string;
|
||||||
filePhysicalSize: string
|
filePhysicalSize: string;
|
||||||
thumbnail_sha1: string | null
|
thumbnail_sha1: string | null;
|
||||||
thumbnail_size: string | null
|
thumbnail_size: string | null;
|
||||||
needAlbumStorage: boolean
|
needAlbumStorage: boolean;
|
||||||
albumStorageInfo: albumStorgeInfo
|
albumStorageInfo: albumStorgeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface fileListsInfo {
|
export interface fileListsInfo {
|
||||||
parentId: string,
|
parentId: string,
|
||||||
depth: number, // 1
|
depth: number, // 1
|
||||||
fileList: FlashOneFileInfo[],
|
fileList: FlashOneFileInfo[],
|
||||||
paginationInfo: {}
|
paginationInfo: {};
|
||||||
isEnd: boolean,
|
isEnd: boolean,
|
||||||
isCache: boolean,
|
isCache: boolean,
|
||||||
}
|
}
|
||||||
@@ -200,30 +200,50 @@ export interface createFlashTransferResult {
|
|||||||
expireTime: string,
|
expireTime: string,
|
||||||
expireLeftTime: string,
|
expireLeftTime: string,
|
||||||
}
|
}
|
||||||
|
export enum UploadSceneType {
|
||||||
|
KUPLOADSCENEUNKNOWN,
|
||||||
|
KUPLOADSCENEFLOATWINDOWRIGHTCLICKMENU,
|
||||||
|
KUPLOADSCENEFLOATWINDOWDRAG,
|
||||||
|
KUPLOADSCENEFLOATWINDOWFILESELECTOR,
|
||||||
|
KUPLOADSCENEFLOATWINDOWSHORTCUTKEYCTRLCV,
|
||||||
|
KUPLOADSCENEH5LAUNCHCLIENTRIGHTCLICKMENU,
|
||||||
|
KUPLOADSCENEH5LAUNCHCLIENTDRAG,
|
||||||
|
KUPLOADSCENEH5LAUNCHCLIENTFILESELECTOR,
|
||||||
|
KUPLOADSCENEH5LAUNCHCLIENTSHORTCUTKEYCTRLCV,
|
||||||
|
KUPLOADSCENEAIODRAG,
|
||||||
|
KUPLOADSCENEAIOFILESELECTOR,
|
||||||
|
KUPLOADSCENEAIOSHORTCUTKEYCTRLCV
|
||||||
|
}
|
||||||
export interface StartFlashTaskRequests {
|
export interface StartFlashTaskRequests {
|
||||||
screen?: number; // 1 PC-QQ
|
screen: number; // 1 PC-QQ
|
||||||
|
name?: string;
|
||||||
uploaders: UploaderInfo[];
|
uploaders: UploaderInfo[];
|
||||||
permission?: {};
|
permission?: {};
|
||||||
coverPath?: string;
|
coverPath?: string;
|
||||||
paths: string[]; // 文件的绝对路径,可以是文件夹
|
paths: string[]; // 文件的绝对路径,可以是文件夹
|
||||||
// excludePaths: [];
|
excludePaths?: string[];
|
||||||
// expireLeftTime: 0,
|
expireLeftTime?: number, // 0
|
||||||
// isNeedDelDeviceInfo: boolean,
|
isNeedDelDeviceInfo: boolean,
|
||||||
// isNeedDelLocation: boolean,
|
isNeedDelLocation: boolean,
|
||||||
// coverOriginalInfos: [],
|
coverOriginalInfos?: {
|
||||||
// uploadSceneType: 10, // 不知道怎么枚举 先硬编码吧
|
path: string,
|
||||||
// detectPrivacyInfoResult: {
|
thumbnailPath: string,
|
||||||
// exists: boolean,
|
}[],
|
||||||
// allDetectResults: {}
|
uploadSceneType: UploadSceneType, // 不知道怎么枚举 先硬编码吧 (PC QQ 10)
|
||||||
// }
|
detectPrivacyInfoResult: {
|
||||||
|
exists: boolean,
|
||||||
|
allDetectResults: {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export enum BusiScene {
|
||||||
|
KBUSISCENEINVALID,
|
||||||
|
KBUSISCENEFLASHSCENE
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileListInfoRequests {
|
export interface FileListInfoRequests {
|
||||||
seq: number, // 0
|
seq: number, // 0
|
||||||
fileSetId: string,
|
fileSetId: string,
|
||||||
isUseCache: boolean,
|
isUseCache: boolean,
|
||||||
sceneType: number, // 1
|
sceneType: BusiScene, // 1
|
||||||
reqInfos: {
|
reqInfos: {
|
||||||
count: number, // 18 ?? 硬编码吧 不懂
|
count: number, // 18 ?? 硬编码吧 不懂
|
||||||
paginationInfo: {},
|
paginationInfo: {},
|
||||||
@@ -238,10 +258,24 @@ export interface FileListInfoRequests {
|
|||||||
sortField: number,
|
sortField: number,
|
||||||
sortOrder: number,
|
sortOrder: number,
|
||||||
}[],
|
}[],
|
||||||
isNeedPhysicalInfoReady: boolean
|
isNeedPhysicalInfoReady: boolean;
|
||||||
}[]
|
}[];
|
||||||
|
}
|
||||||
|
export enum DownloadSceneType {
|
||||||
|
KDOWNLOADSCENEUNKNOWN,
|
||||||
|
KDOWNLOADSCENEARKC2C,
|
||||||
|
KDOWNLOADSCENEARKC2CDETAILPAGE,
|
||||||
|
KDOWNLOADSCENEARKGROUP,
|
||||||
|
KDOWNLOADSCENEARKGROUPDETAILPAGE,
|
||||||
|
KDOWNLOADSCENELINKC2C,
|
||||||
|
KDOWNLOADSCENELINKGROUP,
|
||||||
|
KDOWNLOADSCENELINKCHANNEL,
|
||||||
|
KDOWNLOADSCENELINKTEMPCHAT,
|
||||||
|
KDOWNLOADSCENELINKOTHERINQQ,
|
||||||
|
KDOWNLOADSCENESCANQRCODE,
|
||||||
|
KDWONLOADSCENEFLASHTRANSFERCENTERCLIENT,
|
||||||
|
KDWONLOADSCENEFLASHTRANSFERCENTERSCHEMA
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlashFileSetInfo {
|
export interface FlashFileSetInfo {
|
||||||
fileSetId: string,
|
fileSetId: string,
|
||||||
name: string,
|
name: string,
|
||||||
@@ -258,23 +292,23 @@ export interface FlashFileSetInfo {
|
|||||||
urls: [
|
urls: [
|
||||||
{
|
{
|
||||||
spec: number, // 2
|
spec: number, // 2
|
||||||
url: string
|
url: string;
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
localCachePath: string
|
localCachePath: string;
|
||||||
},
|
},
|
||||||
uploaders: [
|
uploaders: [
|
||||||
{
|
{
|
||||||
uin: string,
|
uin: string,
|
||||||
nickname: string,
|
nickname: string,
|
||||||
uid: string,
|
uid: string,
|
||||||
sendEntrance: string
|
sendEntrance: string;
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
expireLeftTime: number,
|
expireLeftTime: number,
|
||||||
aiClusteringStatus: {
|
aiClusteringStatus: {
|
||||||
firstClusteringList: [],
|
firstClusteringList: [],
|
||||||
shouldPull: boolean
|
shouldPull: boolean;
|
||||||
},
|
},
|
||||||
createTime: number,
|
createTime: number,
|
||||||
expireTime: number,
|
expireTime: number,
|
||||||
@@ -284,7 +318,7 @@ export interface FlashFileSetInfo {
|
|||||||
uploadInfo: {
|
uploadInfo: {
|
||||||
totalUploadedFileSize: number,
|
totalUploadedFileSize: number,
|
||||||
successCount: number,
|
successCount: number,
|
||||||
failedCount: number
|
failedCount: number;
|
||||||
},
|
},
|
||||||
downloadInfo: {
|
downloadInfo: {
|
||||||
totalDownloadedFileSize: 0,
|
totalDownloadedFileSize: 0,
|
||||||
@@ -296,7 +330,7 @@ export interface FlashFileSetInfo {
|
|||||||
cancelCount: 0,
|
cancelCount: 0,
|
||||||
status: 0,
|
status: 0,
|
||||||
curLevelDownloadedFileCount: number,
|
curLevelDownloadedFileCount: number,
|
||||||
curLevelUnDownloadedFileCount: 0
|
curLevelUnDownloadedFileCount: 0;
|
||||||
},
|
},
|
||||||
transferType: number,
|
transferType: number,
|
||||||
isLocalCreate: true,
|
isLocalCreate: true,
|
||||||
@@ -306,12 +340,12 @@ export interface FlashFileSetInfo {
|
|||||||
downloadStatus: 0,
|
downloadStatus: 0,
|
||||||
downloadPauseReason: 0,
|
downloadPauseReason: 0,
|
||||||
saveFileSetDir: string,
|
saveFileSetDir: string,
|
||||||
uploadSceneType: 10,
|
uploadSceneType: UploadSceneType,
|
||||||
downloadSceneType: 0, // 0 PC-QQ 103 web
|
downloadSceneType: DownloadSceneType, // 0 PC-QQ 103 web
|
||||||
retryCount: number,
|
retryCount: number,
|
||||||
isMergeShareUpload: 0,
|
isMergeShareUpload: 0,
|
||||||
isRemoveDeviceInfo: boolean,
|
isRemoveDeviceInfo: boolean,
|
||||||
isRemoveLocation: boolean
|
isRemoveLocation: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendStatus {
|
export interface SendStatus {
|
||||||
@@ -320,5 +354,5 @@ export interface SendStatus {
|
|||||||
target: {
|
target: {
|
||||||
destType: number,
|
destType: number,
|
||||||
destUid: string,
|
destUid: string,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
8
packages/napcat-core/external/appid.json
vendored
8
packages/napcat-core/external/appid.json
vendored
@@ -518,5 +518,13 @@
|
|||||||
"9.9.26-44725": {
|
"9.9.26-44725": {
|
||||||
"appid": 537337569,
|
"appid": 537337569,
|
||||||
"qua": "V1_WIN_NQ_9.9.26_44725_GW_B"
|
"qua": "V1_WIN_NQ_9.9.26_44725_GW_B"
|
||||||
|
},
|
||||||
|
"9.9.27-45627": {
|
||||||
|
"appid": 537340060,
|
||||||
|
"qua": "V1_WIN_NQ_9.9.26_45627_GW_B"
|
||||||
|
},
|
||||||
|
"6.9.88-44725": {
|
||||||
|
"appid": 537337594,
|
||||||
|
"qua": "V1_MAC_NQ_6.9.88_44725_GW_B"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
packages/napcat-core/external/napi2native.json
vendored
12
packages/napcat-core/external/napi2native.json
vendored
@@ -154,5 +154,17 @@
|
|||||||
"9.9.26-44725-x64": {
|
"9.9.26-44725-x64": {
|
||||||
"send": "0A18D0C",
|
"send": "0A18D0C",
|
||||||
"recv": "1D4BF0D"
|
"recv": "1D4BF0D"
|
||||||
|
},
|
||||||
|
"9.9.27-45627-x64": {
|
||||||
|
"send": "0A697CC",
|
||||||
|
"recv": "1E86AC1"
|
||||||
|
},
|
||||||
|
"6.9.88-44725-x64": {
|
||||||
|
"send": "2756EF6",
|
||||||
|
"recv": "0A36152"
|
||||||
|
},
|
||||||
|
"6.9.88-44725-arm64": {
|
||||||
|
"send": "2313C68",
|
||||||
|
"recv": "09693E4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
packages/napcat-core/external/packet.json
vendored
12
packages/napcat-core/external/packet.json
vendored
@@ -662,5 +662,17 @@
|
|||||||
"9.9.26-44725-x64": {
|
"9.9.26-44725-x64": {
|
||||||
"send": "2CEBB20",
|
"send": "2CEBB20",
|
||||||
"recv": "2CEF0A0"
|
"recv": "2CEF0A0"
|
||||||
|
},
|
||||||
|
"9.9.27-45627-x64": {
|
||||||
|
"send": "2E59CC0",
|
||||||
|
"recv": "2E5D240"
|
||||||
|
},
|
||||||
|
"6.9.88-44725-x64": {
|
||||||
|
"send": "451FE90",
|
||||||
|
"recv": "4522A40"
|
||||||
|
},
|
||||||
|
"6.9.88-44725-arm64": {
|
||||||
|
"send": "3D79168",
|
||||||
|
"recv": "3D7BA78"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,14 @@ 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;
|
||||||
config: ForwardMsgJsonConfig,
|
config: ForwardMsgJsonConfig,
|
||||||
desc: string,
|
desc: string,
|
||||||
extra: ForwardMsgJsonExtra,
|
extra: ForwardMsgJsonExtra,
|
||||||
meta: ForwardMsgJsonMeta,
|
meta: ForwardMsgJsonMeta,
|
||||||
prompt: string,
|
prompt: string,
|
||||||
ver: string,
|
ver: string,
|
||||||
view: string
|
view: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ForwardMsgJsonConfig {
|
interface ForwardMsgJsonConfig {
|
||||||
@@ -17,7 +17,7 @@ interface ForwardMsgJsonConfig {
|
|||||||
forward: number,
|
forward: number,
|
||||||
round: number,
|
round: number,
|
||||||
type: string,
|
type: string,
|
||||||
width: number
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ForwardMsgJsonExtra {
|
interface ForwardMsgJsonExtra {
|
||||||
@@ -26,17 +26,17 @@ interface ForwardMsgJsonExtra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ForwardMsgJsonMeta {
|
interface ForwardMsgJsonMeta {
|
||||||
detail: ForwardMsgJsonMetaDetail
|
detail: ForwardMsgJsonMetaDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ForwardMsgJsonMetaDetail {
|
interface ForwardMsgJsonMetaDetail {
|
||||||
news: {
|
news: {
|
||||||
text: string
|
text: string;
|
||||||
}[],
|
}[],
|
||||||
resid: string,
|
resid: string,
|
||||||
source: string,
|
source: string,
|
||||||
summary: string,
|
summary: string,
|
||||||
uniseq: string
|
uniseq: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ForwardAdaptMsg {
|
interface ForwardAdaptMsg {
|
||||||
@@ -50,8 +50,8 @@ interface ForwardAdaptMsgElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ForwardMsgBuilder {
|
export class ForwardMsgBuilder {
|
||||||
private static build (resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson {
|
private static build (resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string, uuid?: string): ForwardMsgJson {
|
||||||
const id = crypto.randomUUID();
|
const id = uuid ?? crypto.randomUUID();
|
||||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录');
|
source = msg.length === 0 ? '聊天记录' : (isGroupMsg ? '群聊的聊天记录' : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录');
|
||||||
@@ -104,13 +104,19 @@ export class ForwardMsgBuilder {
|
|||||||
return this.build(resId, []);
|
return this.build(resId, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromPacketMsg (resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson {
|
static fromPacketMsg (resId: string, packetMsg: PacketMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string, uuid?: string): ForwardMsgJson {
|
||||||
return this.build(resId, packetMsg.map(msg => ({
|
return this.build(resId, packetMsg.map(msg => ({
|
||||||
senderName: msg.senderName,
|
senderName: msg.senderName,
|
||||||
isGroupMsg: msg.groupId !== undefined,
|
isGroupMsg: msg.groupId !== undefined,
|
||||||
msg: msg.msg.map(m => ({
|
msg: msg.msg.map(m => ({
|
||||||
preview: m.valid ? m.toPreview() : '[该消息类型暂不支持查看]',
|
preview: m.valid ? m.toPreview() : '[该消息类型暂不支持查看]',
|
||||||
})),
|
})),
|
||||||
})), source, news, summary, prompt);
|
})),
|
||||||
|
source,
|
||||||
|
news,
|
||||||
|
summary,
|
||||||
|
prompt,
|
||||||
|
uuid,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
140
packages/napcat-core/helper/session-proxy.ts
Normal file
140
packages/napcat-core/helper/session-proxy.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { NodeIQQNTWrapperSession } from '@/napcat-core/wrapper';
|
||||||
|
import { ServiceNamingMapping } from '@/napcat-core/services/index';
|
||||||
|
import { NTEventWrapper } from './event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Service 方法的代理
|
||||||
|
* 拦截所有方法调用,通过 EventWrapper 进行调用
|
||||||
|
*/
|
||||||
|
function createServiceMethodProxy<S extends keyof ServiceNamingMapping>(
|
||||||
|
serviceName: S,
|
||||||
|
originalService: ServiceNamingMapping[S],
|
||||||
|
eventWrapper: NTEventWrapper
|
||||||
|
): ServiceNamingMapping[S] {
|
||||||
|
return new Proxy(originalService as object, {
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
const originalValue = Reflect.get(target, prop, receiver);
|
||||||
|
|
||||||
|
// 如果不是函数,直接返回原始值
|
||||||
|
if (typeof originalValue !== 'function') {
|
||||||
|
return originalValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodName = prop as string;
|
||||||
|
|
||||||
|
// 返回一个包装函数,通过 EventWrapper 调用
|
||||||
|
return function (this: unknown, ...args: unknown[]) {
|
||||||
|
// 构造 EventWrapper 需要的路径格式: ServiceName/MethodName
|
||||||
|
const eventPath = `${serviceName}/${methodName}`;
|
||||||
|
|
||||||
|
// 尝试通过 EventWrapper 调用
|
||||||
|
try {
|
||||||
|
// 使用 callNoListenerEvent 的底层实现逻辑
|
||||||
|
const eventFunc = (eventWrapper as any).createEventFunction(eventPath);
|
||||||
|
if (eventFunc) {
|
||||||
|
return eventFunc(...args);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 如果 EventWrapper 调用失败,回退到原始调用
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到原始方法调用
|
||||||
|
return originalValue.apply(originalService, args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}) as ServiceNamingMapping[S];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Session 的双层代理
|
||||||
|
* 第一层:拦截 getXXXService 方法
|
||||||
|
* 第二层:拦截 Service 上的具体方法调用
|
||||||
|
*/
|
||||||
|
export function createSessionProxy(
|
||||||
|
session: NodeIQQNTWrapperSession,
|
||||||
|
eventWrapper: NTEventWrapper
|
||||||
|
): NodeIQQNTWrapperSession {
|
||||||
|
// 缓存已代理的 Service,避免重复创建
|
||||||
|
const serviceProxyCache = new Map<string, unknown>();
|
||||||
|
|
||||||
|
return new Proxy(session, {
|
||||||
|
get(target, prop, receiver) {
|
||||||
|
const propName = prop as string;
|
||||||
|
|
||||||
|
// 检查是否是 getXXXService 方法
|
||||||
|
if (typeof propName === 'string' && propName.startsWith('get') && propName.endsWith('Service')) {
|
||||||
|
// 提取 Service 名称: getMsgService -> NodeIKernelMsgService
|
||||||
|
const servicePart = propName.slice(3); // 移除 'get' 前缀
|
||||||
|
const serviceName = `NodeIKernel${servicePart}` as keyof ServiceNamingMapping;
|
||||||
|
|
||||||
|
// 返回一个函数,该函数返回代理后的 Service
|
||||||
|
return function () {
|
||||||
|
// 检查缓存
|
||||||
|
if (serviceProxyCache.has(serviceName)) {
|
||||||
|
return serviceProxyCache.get(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取原始 Service
|
||||||
|
const originalGetter = Reflect.get(target, prop, receiver) as () => unknown;
|
||||||
|
const originalService = originalGetter.call(target);
|
||||||
|
|
||||||
|
// 检查是否在 ServiceNamingMapping 中定义
|
||||||
|
if (isKnownService(serviceName)) {
|
||||||
|
// 创建 Service 方法代理
|
||||||
|
const proxiedService = createServiceMethodProxy(
|
||||||
|
serviceName,
|
||||||
|
originalService as ServiceNamingMapping[typeof serviceName],
|
||||||
|
eventWrapper
|
||||||
|
);
|
||||||
|
serviceProxyCache.set(serviceName, proxiedService);
|
||||||
|
return proxiedService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未知的 Service,直接返回原始对象
|
||||||
|
serviceProxyCache.set(serviceName, originalService);
|
||||||
|
return originalService;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非 getXXXService 方法,直接返回原始值
|
||||||
|
return Reflect.get(target, prop, receiver);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 Service 名称是否在已知的映射中
|
||||||
|
*/
|
||||||
|
function isKnownService(serviceName: string): serviceName is keyof ServiceNamingMapping {
|
||||||
|
const knownServices: string[] = [
|
||||||
|
'NodeIKernelAvatarService',
|
||||||
|
'NodeIKernelBuddyService',
|
||||||
|
'NodeIKernelFileAssistantService',
|
||||||
|
'NodeIKernelGroupService',
|
||||||
|
'NodeIKernelLoginService',
|
||||||
|
'NodeIKernelMsgService',
|
||||||
|
'NodeIKernelOnlineStatusService',
|
||||||
|
'NodeIKernelProfileLikeService',
|
||||||
|
'NodeIKernelProfileService',
|
||||||
|
'NodeIKernelTicketService',
|
||||||
|
'NodeIKernelStorageCleanService',
|
||||||
|
'NodeIKernelRobotService',
|
||||||
|
'NodeIKernelRichMediaService',
|
||||||
|
'NodeIKernelDbToolsService',
|
||||||
|
'NodeIKernelTipOffService',
|
||||||
|
'NodeIKernelSearchService',
|
||||||
|
'NodeIKernelCollectionService',
|
||||||
|
];
|
||||||
|
return knownServices.includes(serviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建带有 EventWrapper 集成的 InstanceContext
|
||||||
|
* 这是推荐的使用方式,在创建 context 时自动代理 session
|
||||||
|
*/
|
||||||
|
export function createProxiedSession(
|
||||||
|
session: NodeIQQNTWrapperSession,
|
||||||
|
eventWrapper: NTEventWrapper
|
||||||
|
): NodeIQQNTWrapperSession {
|
||||||
|
return createSessionProxy(session, eventWrapper);
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ 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-core/helper/event';
|
import { NTEventWrapper } from '@/napcat-core/helper/event';
|
||||||
|
import { createSessionProxy } from '@/napcat-core/helper/session-proxy';
|
||||||
import { KickedOffLineInfo, RawMessage, SelfInfo, SelfStatusInfo } from '@/napcat-core/types';
|
import { KickedOffLineInfo, RawMessage, 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';
|
||||||
@@ -39,6 +40,14 @@ export * from './wrapper';
|
|||||||
export * from './types/index';
|
export * from './types/index';
|
||||||
export * from './services/index';
|
export * from './services/index';
|
||||||
export * from './listeners/index';
|
export * from './listeners/index';
|
||||||
|
export * from './apis/index';
|
||||||
|
export * from './helper/log';
|
||||||
|
export * from './helper/qq-basic-info';
|
||||||
|
export * from './helper/event';
|
||||||
|
export * from './helper/config';
|
||||||
|
export * from './helper/config-base';
|
||||||
|
export * from './helper/proxy-handler';
|
||||||
|
export * from './helper/session-proxy';
|
||||||
|
|
||||||
export enum NapCatCoreWorkingEnv {
|
export enum NapCatCoreWorkingEnv {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -74,6 +83,7 @@ export function loadQQWrapper (execPath: string | undefined, QQVersion: string):
|
|||||||
}
|
}
|
||||||
const nativemodule: { exports: WrapperNodeApi; } = { exports: {} as WrapperNodeApi };
|
const nativemodule: { exports: WrapperNodeApi; } = { exports: {} as WrapperNodeApi };
|
||||||
process.dlopen(nativemodule, wrapperNodePath);
|
process.dlopen(nativemodule, wrapperNodePath);
|
||||||
|
process.env['NAPCAT_WRAPPER_PATH'] = wrapperNodePath;
|
||||||
return nativemodule.exports;
|
return nativemodule.exports;
|
||||||
}
|
}
|
||||||
export function getMajorPath (execPath: string, QQVersion: string): string {
|
export function getMajorPath (execPath: string, QQVersion: string): string {
|
||||||
@@ -111,9 +121,19 @@ export class NapCatCore {
|
|||||||
// 通过构造器递过去的 runtime info 应该尽量少
|
// 通过构造器递过去的 runtime info 应该尽量少
|
||||||
constructor (context: InstanceContext, selfInfo: SelfInfo) {
|
constructor (context: InstanceContext, selfInfo: SelfInfo) {
|
||||||
this.selfInfo = selfInfo;
|
this.selfInfo = selfInfo;
|
||||||
this.context = context;
|
// 先用原始 session 创建 eventWrapper
|
||||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
|
||||||
this.eventWrapper = new NTEventWrapper(context.session);
|
this.eventWrapper = new NTEventWrapper(context.session);
|
||||||
|
// 通过环境变量 NAPCAT_SESSION_PROXY 开启 session 代理
|
||||||
|
if (process.env['NAPCAT_SESSION_PROXY'] === '1') {
|
||||||
|
const proxiedSession = createSessionProxy(context.session, this.eventWrapper);
|
||||||
|
this.context = {
|
||||||
|
...context,
|
||||||
|
session: proxiedSession,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||||
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath, NapcatConfigSchema);
|
this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath, NapcatConfigSchema);
|
||||||
this.apis = {
|
this.apis = {
|
||||||
FileApi: new NTQQFileApi(this.context, this),
|
FileApi: new NTQQFileApi(this.context, this),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OidbPacket } from '@/napcat-core/packet/transformer/base';
|
|||||||
import { ImageOcrResult } from '@/napcat-core/packet/entities/ocrResult';
|
import { ImageOcrResult } from '@/napcat-core/packet/entities/ocrResult';
|
||||||
import { gunzipSync } from 'zlib';
|
import { gunzipSync } from 'zlib';
|
||||||
import { PacketMsgConverter } from '@/napcat-core/packet/message/converter';
|
import { PacketMsgConverter } from '@/napcat-core/packet/message/converter';
|
||||||
|
import { UploadForwardMsgParams } from '@/napcat-core/packet/transformer/message/UploadForwardMsgV2';
|
||||||
|
|
||||||
export class PacketOperationContext {
|
export class PacketOperationContext {
|
||||||
private readonly context: PacketContext;
|
private readonly context: PacketContext;
|
||||||
@@ -26,7 +27,7 @@ export class PacketOperationContext {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPacket<T extends boolean = false>(pkt: OidbPacket, rsp?: T): Promise<T extends true ? Buffer : void> {
|
async sendPacket<T extends boolean = false> (pkt: OidbPacket, rsp?: T): Promise<T extends true ? Buffer : void> {
|
||||||
return await this.context.client.sendOidbPacket(pkt, rsp);
|
return await this.context.client.sendOidbPacket(pkt, rsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +95,15 @@ export class PacketOperationContext {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
);
|
);
|
||||||
const res = await Promise.allSettled(reqList);
|
const res = await Promise.allSettled(reqList);
|
||||||
this.context.logger.info(`上传资源${res.length}个,失败${res.filter((r) => r.status === 'rejected').length}个`);
|
const failedCount = res.filter((r) => r.status === 'rejected').length;
|
||||||
res.forEach((result, index) => {
|
if (failedCount > 0) {
|
||||||
if (result.status === 'rejected') {
|
this.context.logger.warn(`上传资源${res.length}个,失败${failedCount}个`);
|
||||||
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
|
res.forEach((result, index) => {
|
||||||
}
|
if (result.status === 'rejected') {
|
||||||
});
|
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async UploadImage (img: PacketMsgPicElement) {
|
async UploadImage (img: PacketMsgPicElement) {
|
||||||
@@ -224,7 +228,15 @@ export class PacketOperationContext {
|
|||||||
const res = trans.UploadForwardMsg.parse(resp);
|
const res = trans.UploadForwardMsg.parse(resp);
|
||||||
return res.result.resId;
|
return res.result.resId;
|
||||||
}
|
}
|
||||||
|
async UploadForwardMsgV2 (msg: UploadForwardMsgParams[], groupUin: number = 0) {
|
||||||
|
//await this.SendPreprocess(msg, groupUin);
|
||||||
|
// 遍历上传资源
|
||||||
|
await Promise.allSettled(msg.map(async (item) => { return await this.SendPreprocess(item.actionMsg, groupUin); }));
|
||||||
|
const req = trans.UploadForwardMsgV2.build(this.context.napcore.basicInfo.uid, msg, groupUin);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.UploadForwardMsg.parse(resp);
|
||||||
|
return res.result.resId;
|
||||||
|
}
|
||||||
async MoveGroupFile (
|
async MoveGroupFile (
|
||||||
groupUin: number,
|
groupUin: number,
|
||||||
fileUUID: string,
|
fileUUID: string,
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import zlib from 'node:zlib';
|
||||||
|
import * as proto from '@/napcat-core/packet/transformer/proto';
|
||||||
|
import { NapProtoMsg } from 'napcat-protobuf';
|
||||||
|
import { OidbPacket, PacketBufBuilder, PacketTransformer } from '@/napcat-core/packet/transformer/base';
|
||||||
|
import { PacketMsg } from '@/napcat-core/packet/message/message';
|
||||||
|
|
||||||
|
export interface UploadForwardMsgParams {
|
||||||
|
actionCommand: string;
|
||||||
|
actionMsg: PacketMsg[];
|
||||||
|
}
|
||||||
|
class UploadForwardMsgV2 extends PacketTransformer<typeof proto.SendLongMsgResp> {
|
||||||
|
build (selfUid: string, msg: UploadForwardMsgParams[], groupUin: number = 0): OidbPacket {
|
||||||
|
const reqdata = msg.map((item) => ({
|
||||||
|
actionCommand: item.actionCommand,
|
||||||
|
actionData: {
|
||||||
|
msgBody: this.msgBuilder.buildFakeMsg(selfUid, item.actionMsg),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const longMsgResultData = new NapProtoMsg(proto.LongMsgResult).encode(
|
||||||
|
{
|
||||||
|
action: reqdata,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
|
||||||
|
const req = new NapProtoMsg(proto.SendLongMsgReq).encode(
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
type: groupUin === 0 ? 1 : 3,
|
||||||
|
uid: {
|
||||||
|
uid: groupUin === 0 ? selfUid : groupUin.toString(),
|
||||||
|
},
|
||||||
|
groupUin,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
field1: 4, field2: 1, field3: 7, field4: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
cmd: 'trpc.group.long_msg_interface.MsgService.SsoSendLongMsg',
|
||||||
|
data: PacketBufBuilder(req),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parse (data: Buffer) {
|
||||||
|
return new NapProtoMsg(proto.SendLongMsgResp).decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadForwardMsgV2();
|
||||||
@@ -2,3 +2,4 @@ export { default as UploadForwardMsg } from './UploadForwardMsg';
|
|||||||
export { default as FetchGroupMessage } from './FetchGroupMessage';
|
export { default as FetchGroupMessage } from './FetchGroupMessage';
|
||||||
export { default as FetchC2CMessage } from './FetchC2CMessage';
|
export { default as FetchC2CMessage } from './FetchC2CMessage';
|
||||||
export { default as DownloadForwardMsg } from './DownloadForwardMsg';
|
export { default as DownloadForwardMsg } from './DownloadForwardMsg';
|
||||||
|
export { default as UploadForwardMsgV2 } from './UploadForwardMsgV2';
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
DownloadStatusInfo,
|
DownloadStatusInfo,
|
||||||
SendTargetRequests,
|
SendTargetRequests,
|
||||||
FlashOneFileInfo,
|
FlashOneFileInfo,
|
||||||
|
DownloadSceneType,
|
||||||
} from '../data/flash';
|
} from '../data/flash';
|
||||||
|
|
||||||
export interface NodeIKernelFlashTransferService {
|
export interface NodeIKernelFlashTransferService {
|
||||||
@@ -18,48 +19,48 @@ export interface NodeIKernelFlashTransferService {
|
|||||||
* @param timestamp
|
* @param timestamp
|
||||||
* @param fileInfo
|
* @param fileInfo
|
||||||
*/
|
*/
|
||||||
createFlashTransferUploadTask(timestamp: number, fileInfo: StartFlashTaskRequests): Promise < GeneralCallResult & {
|
createFlashTransferUploadTask (timestamp: number, fileInfo: StartFlashTaskRequests): Promise<GeneralCallResult & {
|
||||||
createFlashTransferResult: createFlashTransferResult;
|
createFlashTransferResult: createFlashTransferResult;
|
||||||
seq: number;
|
seq: number;
|
||||||
} >; // 2 arg 重点 // 自动上传
|
}>; // 2 arg 重点 // 自动上传
|
||||||
|
|
||||||
createMergeShareTask(...args: unknown[]): unknown; // 2 arg
|
createMergeShareTask (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
updateFlashTransfer(...args: unknown[]): unknown; // 2 arg
|
updateFlashTransfer (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
getFileSetList(...args: unknown[]): unknown; // 1 arg
|
getFileSetList (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
getFileSetListCount(...args: unknown[]): unknown; // 1 arg
|
getFileSetListCount (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取file set 的信息
|
* 获取file set 的信息
|
||||||
* @param fileSetIdDict
|
* @param fileSetIdDict
|
||||||
*/
|
*/
|
||||||
getFileSet(fileSetIdDict: FlashBaseRequest): Promise < GeneralCallResult & {
|
getFileSet (fileSetIdDict: FlashBaseRequest): Promise<GeneralCallResult & {
|
||||||
seq: number;
|
seq: number;
|
||||||
isCache: boolean;
|
isCache: boolean;
|
||||||
fileSet: FlashFileSetInfo;
|
fileSet: FlashFileSetInfo;
|
||||||
} >; // 1 arg
|
}>; // 1 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取file set 里面的文件信息(文件夹结构)
|
* 获取file set 里面的文件信息(文件夹结构)
|
||||||
* @param requestArgs
|
* @param requestArgs
|
||||||
*/
|
*/
|
||||||
getFileList(requestArgs: FileListInfoRequests): Promise < {
|
getFileList (requestArgs: FileListInfoRequests): Promise<{
|
||||||
rsp: FileListResponse;
|
rsp: FileListResponse;
|
||||||
} > ; // 1 arg 这个方法QQ有bug??? 并没有,是我参数有问题
|
}>; // 1 arg 这个方法QQ有bug??? 并没有,是我参数有问题
|
||||||
|
|
||||||
getDownloadedFileCount(...args: unknown[]): unknown; // 1 arg
|
getDownloadedFileCount (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
getLocalFileList(...args: unknown[]): unknown; // 3 arg
|
getLocalFileList (...args: unknown[]): unknown; // 3 arg
|
||||||
|
|
||||||
batchRemoveUserFileSetHistory(...args: unknown[]): unknown; // 1 arg
|
batchRemoveUserFileSetHistory (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取分享链接
|
* 获取分享链接
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
getShareLinkReq(fileSetId:string): Promise< GeneralCallResult & {
|
getShareLinkReq (fileSetId: string): Promise<GeneralCallResult & {
|
||||||
shareLink: string;
|
shareLink: string;
|
||||||
expireTimestamp: string;
|
expireTimestamp: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -68,235 +69,235 @@ export interface NodeIKernelFlashTransferService {
|
|||||||
* 由分享链接到fileSetId
|
* 由分享链接到fileSetId
|
||||||
* @param shareCode
|
* @param shareCode
|
||||||
*/
|
*/
|
||||||
getFileSetIdByCode(shareCode: string): Promise < GeneralCallResult & {
|
getFileSetIdByCode (shareCode: string): Promise<GeneralCallResult & {
|
||||||
fileSetId: string;
|
fileSetId: string;
|
||||||
} > ; // 1 arg code == share code
|
}>; // 1 arg code == share code
|
||||||
|
|
||||||
batchRemoveFile(...args: unknown[]): unknown; // 1 arg
|
batchRemoveFile (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
checkUploadPathValid(...args: unknown[]): unknown; // 1 arg
|
checkUploadPathValid (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
cleanFailedFiles(...args: unknown[]): unknown; // 2 arg
|
cleanFailedFiles (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停所有的任务
|
* 暂停所有的任务
|
||||||
*/
|
*/
|
||||||
resumeAllUnfinishedTasks(): unknown; // 0 arg !!
|
resumeAllUnfinishedTasks (): unknown; // 0 arg !!
|
||||||
|
|
||||||
addFileSetUploadListener(...args: unknown[]): unknown; // 1 arg
|
addFileSetUploadListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFileSetUploadListener(...args: unknown[]): unknown; // 1 arg
|
removeFileSetUploadListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始上传任务 适用于已暂停的
|
* 开始上传任务 适用于已暂停的
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
startFileSetUpload(fileSetId: string): void; // 1 arg 并不是新建任务,应该是暂停后的启动
|
startFileSetUpload (fileSetId: string): void; // 1 arg 并不是新建任务,应该是暂停后的启动
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结束,无法再次启动
|
* 结束,无法再次启动
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
stopFileSetUpload(fileSetId: string): void; // 1 arg stop 后start无效
|
stopFileSetUpload (fileSetId: string): void; // 1 arg stop 后start无效
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 暂停上传
|
* 暂停上传
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
*/
|
*/
|
||||||
pauseFileSetUpload(fileSetId: string): void; // 1 arg 暂停上传
|
pauseFileSetUpload (fileSetId: string): void; // 1 arg 暂停上传
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 继续上传
|
* 继续上传
|
||||||
* @param args
|
* @param args
|
||||||
*/
|
*/
|
||||||
resumeFileSetUpload(...args: unknown[]): unknown; // 1 arg 继续
|
resumeFileSetUpload (...args: unknown[]): unknown; // 1 arg 继续
|
||||||
|
|
||||||
pauseFileUpload(...args: unknown[]): unknown; // 1 arg
|
pauseFileUpload (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
resumeFileUpload(...args: unknown[]): unknown; // 1 arg
|
resumeFileUpload (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
stopFileUpload(...args: unknown[]): unknown; // 1 arg
|
stopFileUpload (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
asyncGetThumbnailPath(...args: unknown[]): unknown; // 2 arg
|
asyncGetThumbnailPath (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
setDownLoadDefaultFileDir(...args: unknown[]): unknown; // 1 arg
|
setDownLoadDefaultFileDir (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
setFileSetDownloadDir(...args: unknown[]): unknown; // 2 arg
|
setFileSetDownloadDir (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
getFileSetDownloadDir(...args: unknown[]): unknown; // 1 arg
|
getFileSetDownloadDir (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
setFlashTransferDir(...args: unknown[]): unknown; // 2 arg
|
setFlashTransferDir (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
addFileSetDownloadListener(...args: unknown[]): unknown; // 1 arg
|
addFileSetDownloadListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFileSetDownloadListener(...args: unknown[]): unknown; // 1 arg
|
removeFileSetDownloadListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始下载file set的函数 同开始上传
|
* 开始下载file set的函数 同开始上传
|
||||||
* @param fileSetId
|
* @param fileSetId
|
||||||
* @param chatType 聊天类型 //因为没有peer,其实可以硬编码为1 (好友私聊)
|
* @param downloadSceneType 下载类型 //因为没有peer,其实可以硬编码为1 (好友私聊)
|
||||||
* @param arg // 默认为false
|
* @param arg // 默认为false
|
||||||
*/
|
*/
|
||||||
startFileSetDownload(fileSetId:string, chatType: number, arg: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
startFileSetDownload (fileSetId: string, downloadSceneType: DownloadSceneType, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||||
extraInfo: 0
|
extraInfo: 0;
|
||||||
} >; // 3 arg
|
}>; // 3 arg
|
||||||
|
|
||||||
stopFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
stopFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||||
extraInfo: 0
|
extraInfo: 0;
|
||||||
} > ; // 2 arg 结束不可重启!!
|
}>; // 2 arg 结束不可重启!!
|
||||||
|
|
||||||
pauseFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
pauseFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||||
extraInfo: 0
|
extraInfo: 0;
|
||||||
} > ; // 2 arg
|
}>; // 2 arg
|
||||||
|
|
||||||
resumeFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
resumeFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||||
extraInfo: 0
|
extraInfo: 0;
|
||||||
} > ; // 2 arg
|
}>; // 2 arg
|
||||||
|
|
||||||
startFileListDownLoad(...args: unknown[]): unknown; // 4 arg // 大概率是选择set里面的部分文件进行下载,没必要,不想写
|
startFileListDownLoad (...args: unknown[]): unknown; // 4 arg // 大概率是选择set里面的部分文件进行下载,没必要,不想写
|
||||||
|
|
||||||
pauseFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
pauseFileListDownLoad (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
resumeFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
resumeFileListDownLoad (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
stopFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
stopFileListDownLoad (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
startThumbnailListDownload(fileSetId: string): Promise < GeneralCallResult >; // 1 arg // 缩略图下载
|
startThumbnailListDownload (fileSetId: string): Promise<GeneralCallResult>; // 1 arg // 缩略图下载
|
||||||
|
|
||||||
stopThumbnailListDownload(fileSetId: string): Promise < GeneralCallResult >; // 1 arg
|
stopThumbnailListDownload (fileSetId: string): Promise<GeneralCallResult>; // 1 arg
|
||||||
|
|
||||||
asyncRequestDownLoadStatus(fileSetId: string): Promise < DownloadStatusInfo >; // 1 arg
|
asyncRequestDownLoadStatus (fileSetId: string): Promise<DownloadStatusInfo>; // 1 arg
|
||||||
|
|
||||||
startFileTransferUrl(fileInfo: FlashOneFileInfo): Promise < {
|
startFileTransferUrl (fileInfo: FlashOneFileInfo): Promise<{
|
||||||
ret: number,
|
ret: number,
|
||||||
url: string,
|
url: string,
|
||||||
expireTimestampSeconds: string
|
expireTimestampSeconds: string;
|
||||||
} >; // 1 arg
|
}>; // 1 arg
|
||||||
|
|
||||||
startFileListDownLoadBySessionId(...args: unknown[]): unknown; // 2 arg
|
startFileListDownLoadBySessionId (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
addFileSetSimpleStatusListener(...args: unknown[]): unknown; // 2 arg
|
addFileSetSimpleStatusListener (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
addFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 2 arg
|
addFileSetSimpleStatusMonitoring (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
removeFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 2 arg
|
removeFileSetSimpleStatusMonitoring (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
removeFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
removeFileSetSimpleStatusListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addDesktopFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
addDesktopFileSetSimpleStatusListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addDesktopFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 1 arg
|
addDesktopFileSetSimpleStatusMonitoring (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeDesktopFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 1 arg
|
removeDesktopFileSetSimpleStatusMonitoring (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeDesktopFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
removeDesktopFileSetSimpleStatusListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addFileSetSimpleUploadInfoListener(...args: unknown[]): unknown; // 1 arg
|
addFileSetSimpleUploadInfoListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addFileSetSimpleUploadInfoMonitoring(...args: unknown[]): unknown; // 1 arg
|
addFileSetSimpleUploadInfoMonitoring (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFileSetSimpleUploadInfoMonitoring(...args: unknown[]): unknown; // 1 arg
|
removeFileSetSimpleUploadInfoMonitoring (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFileSetSimpleUploadInfoListener(...args: unknown[]): unknown; // 1 arg
|
removeFileSetSimpleUploadInfoListener (...args: unknown[]): unknown; // 1 arg
|
||||||
/**
|
/**
|
||||||
* 发送闪传消息
|
* 发送闪传消息
|
||||||
* @param sendArgs
|
* @param sendArgs
|
||||||
*/
|
*/
|
||||||
sendFlashTransferMsg(sendArgs: SendTargetRequests): Promise < {
|
sendFlashTransferMsg (sendArgs: SendTargetRequests): Promise<{
|
||||||
errCode: number,
|
errCode: number,
|
||||||
errMsg: string,
|
errMsg: string,
|
||||||
rsp: {
|
rsp: {
|
||||||
sendStatus: SendStatus[]
|
sendStatus: SendStatus[];
|
||||||
}
|
};
|
||||||
} >; // 1 arg 估计是file set id
|
}>; // 1 arg 估计是file set id
|
||||||
|
|
||||||
addFlashTransferTaskInfoListener(...args: unknown[]): unknown; // 1 arg
|
addFlashTransferTaskInfoListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFlashTransferTaskInfoListener(...args: unknown[]): unknown; // 1 arg
|
removeFlashTransferTaskInfoListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
retrieveLocalLastFailedSetTasksInfo(): unknown; // 0 arg
|
retrieveLocalLastFailedSetTasksInfo (): unknown; // 0 arg
|
||||||
|
|
||||||
getFailedFileList(fileSetId: string): Promise < {
|
getFailedFileList (fileSetId: string): Promise<{
|
||||||
rsp: {
|
rsp: {
|
||||||
seq: number;
|
seq: number;
|
||||||
result: number;
|
result: number;
|
||||||
errMs: string;
|
errMs: string;
|
||||||
fileSetId: string;
|
fileSetId: string;
|
||||||
fileList: []
|
fileList: [];
|
||||||
}
|
};
|
||||||
} >; // 1 arg
|
}>; // 1 arg
|
||||||
|
|
||||||
getLocalFileListByStatuses(...args: unknown[]): unknown; // 1 arg
|
getLocalFileListByStatuses (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addTransferStateListener(...args: unknown[]): unknown; // 1 arg
|
addTransferStateListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeTransferStateListener(...args: unknown[]): unknown; // 1 arg
|
removeTransferStateListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
getFileSetFirstClusteringList(...args: unknown[]): unknown; // 3 arg
|
getFileSetFirstClusteringList (...args: unknown[]): unknown; // 3 arg
|
||||||
|
|
||||||
getFileSetClusteringList(...args: unknown[]): unknown; // 1 arg
|
getFileSetClusteringList (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addFileSetClusteringListListener(...args: unknown[]): unknown; // 1 arg
|
addFileSetClusteringListListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFileSetClusteringListListener(...args: unknown[]): unknown; // 1 arg
|
removeFileSetClusteringListListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
getFileSetClusteringDetail(...args: unknown[]): unknown; // 1 arg
|
getFileSetClusteringDetail (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
doAIOFlashTransferBubbleActionWithStatus(...args: unknown[]): unknown; // 4 arg
|
doAIOFlashTransferBubbleActionWithStatus (...args: unknown[]): unknown; // 4 arg
|
||||||
|
|
||||||
getFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
getFilesTransferProgress (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
pollFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
pollFilesTransferProgress (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
cancelPollFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
cancelPollFilesTransferProgress (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
checkDownloadStatusBeforeLocalFileOper(...args: unknown[]): unknown; // 3 arg
|
checkDownloadStatusBeforeLocalFileOper (...args: unknown[]): unknown; // 3 arg
|
||||||
|
|
||||||
getCompressedFileFolder(...args: unknown[]): unknown; // 1 arg
|
getCompressedFileFolder (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
addFolderListener(...args: unknown[]): unknown; // 1 arg
|
addFolderListener (...args: unknown[]): unknown; // 1 arg
|
||||||
|
|
||||||
removeFolderListener(...args: unknown[]): unknown;
|
removeFolderListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
addCompressedFileListener(...args: unknown[]): unknown;
|
addCompressedFileListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
removeCompressedFileListener(...args: unknown[]): unknown;
|
removeCompressedFileListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
getFileCategoryList(...args: unknown[]): unknown;
|
getFileCategoryList (...args: unknown[]): unknown;
|
||||||
|
|
||||||
addDeviceStatusListener(...args: unknown[]): unknown;
|
addDeviceStatusListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
removeDeviceStatusListener(...args: unknown[]): unknown;
|
removeDeviceStatusListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
checkDeviceStatus(...args: unknown[]): unknown;
|
checkDeviceStatus (...args: unknown[]): unknown;
|
||||||
|
|
||||||
pauseAllTasks(...args: unknown[]): unknown; // 2 arg
|
pauseAllTasks (...args: unknown[]): unknown; // 2 arg
|
||||||
|
|
||||||
resumePausedTasksAfterDeviceStatus(...args: unknown[]): unknown;
|
resumePausedTasksAfterDeviceStatus (...args: unknown[]): unknown;
|
||||||
|
|
||||||
onSystemGoingToSleep(...args: unknown[]): unknown;
|
onSystemGoingToSleep (...args: unknown[]): unknown;
|
||||||
|
|
||||||
onSystemWokeUp(...args: unknown[]): unknown;
|
onSystemWokeUp (...args: unknown[]): unknown;
|
||||||
|
|
||||||
getFileMetas(...args: unknown[]): unknown;
|
getFileMetas (...args: unknown[]): unknown;
|
||||||
|
|
||||||
addDownloadCntStatisticsListener(...args: unknown[]): unknown;
|
addDownloadCntStatisticsListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
removeDownloadCntStatisticsListener(...args: unknown[]): unknown;
|
removeDownloadCntStatisticsListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
detectPrivacyInfoInPaths(...args: unknown[]): unknown;
|
detectPrivacyInfoInPaths (...args: unknown[]): unknown;
|
||||||
|
|
||||||
getFileThumbnailUrl(...args: unknown[]): unknown;
|
getFileThumbnailUrl (...args: unknown[]): unknown;
|
||||||
|
|
||||||
handleDownloadFinishAfterSaveToAlbum(...args: unknown[]): unknown;
|
handleDownloadFinishAfterSaveToAlbum (...args: unknown[]): unknown;
|
||||||
|
|
||||||
checkBatchFilesDownloadStatus(...args: unknown[]): unknown;
|
checkBatchFilesDownloadStatus (...args: unknown[]): unknown;
|
||||||
|
|
||||||
onCheckAlbumStorageStatusResult(...args: unknown[]): unknown;
|
onCheckAlbumStorageStatusResult (...args: unknown[]): unknown;
|
||||||
|
|
||||||
addFileAlbumStorageListener(...args: unknown[]): unknown;
|
addFileAlbumStorageListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
removeFileAlbumStorageListener(...args: unknown[]): unknown;
|
removeFileAlbumStorageListener (...args: unknown[]): unknown;
|
||||||
|
|
||||||
refreshFolderStatus(...args: unknown[]): unknown;
|
refreshFolderStatus (...args: unknown[]): unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ export interface NodeIKernelMsgService {
|
|||||||
|
|
||||||
assembleMobileQQRichMediaFilePath (...args: unknown[]): unknown;
|
assembleMobileQQRichMediaFilePath (...args: unknown[]): unknown;
|
||||||
|
|
||||||
getFileThumbSavePathForSend (...args: unknown[]): unknown;
|
getFileThumbSavePathForSend (thumbSize: number, createNeed: boolean): string;
|
||||||
|
|
||||||
getFileThumbSavePath (...args: unknown[]): unknown;
|
getFileThumbSavePath (...args: unknown[]): unknown;
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,7 @@ export * from './constant';
|
|||||||
export * from './graytip';
|
export * from './graytip';
|
||||||
export * from './emoji';
|
export * from './emoji';
|
||||||
export * from './service';
|
export * from './service';
|
||||||
|
export * from './adapter';
|
||||||
|
export * from './contact';
|
||||||
|
export * from './file';
|
||||||
|
export * from './flashfile';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NTGroupMemberRole } from '@/napcat-core/index';
|
import { NTGroupMemberRole } from './group';
|
||||||
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
|
import { ActionBarElement, ArkElement, AvRecordElement, CalendarElement, FaceBubbleElement, FaceElement, FileElement, GiphyElement, GrayTipElement, MarketFaceElement, PicElement, PttElement, RecommendedMsgElement, ReplyElement, ShareLocationElement, StructLongMsgElement, TaskTopMsgElement, TextElement, TofuRecordElement, VideoElement, YoloGameResultElement } from './element';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from './adapters';
|
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from './adapters';
|
||||||
import {
|
import {
|
||||||
|
GeneralCallResult,
|
||||||
NodeIKernelAvatarService,
|
NodeIKernelAvatarService,
|
||||||
NodeIKernelBuddyService,
|
NodeIKernelBuddyService,
|
||||||
NodeIKernelGroupService,
|
NodeIKernelGroupService,
|
||||||
@@ -27,78 +28,78 @@ import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
|
|||||||
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
||||||
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||||
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
||||||
import { NodeIKernelFlashTransferService } from "./services/NodeIKernelFlashTransferService";
|
import { NodeIKernelFlashTransferService } from './services/NodeIKernelFlashTransferService';
|
||||||
|
|
||||||
export interface NodeQQNTWrapperUtil {
|
export interface NodeQQNTWrapperUtil {
|
||||||
get(): NodeQQNTWrapperUtil;
|
get (): NodeQQNTWrapperUtil;
|
||||||
|
|
||||||
getNTUserDataInfoConfig(): string;
|
getNTUserDataInfoConfig (): string;
|
||||||
|
|
||||||
emptyWorkingSet(n: number): void;
|
emptyWorkingSet (n: number): void;
|
||||||
|
|
||||||
getSsoCmdOfOidbReq(arg1: number, arg2: number): unknown;
|
getSsoCmdOfOidbReq (arg1: number, arg2: number): unknown;
|
||||||
|
|
||||||
getSsoBufferOfOidbReq(...args: unknown[]): unknown; // 有点看不懂参数定义 待补充 好像是三个参数
|
getSsoBufferOfOidbReq (...args: unknown[]): unknown; // 有点看不懂参数定义 待补充 好像是三个参数
|
||||||
|
|
||||||
getOidbRspInfo(arg: string): unknown; // 可能是错的
|
getOidbRspInfo (arg: string): unknown; // 可能是错的
|
||||||
|
|
||||||
getFileSize(path: string): Promise<number>; // 直接的猜测
|
getFileSize (path: string): Promise<number>; // 直接的猜测
|
||||||
|
|
||||||
genFileMd5Buf(arg: string): unknown; // 可能是错的
|
genFileMd5Buf (arg: string): unknown; // 可能是错的
|
||||||
|
|
||||||
genFileMd5Hex(path: string): unknown; // 直接的猜测
|
genFileMd5Hex (path: string): unknown; // 直接的猜测
|
||||||
|
|
||||||
genFileShaBuf(path: string): unknown; // 直接的猜测
|
genFileShaBuf (path: string): unknown; // 直接的猜测
|
||||||
|
|
||||||
genFileCumulateSha1(path: string): unknown; // 直接的猜测
|
genFileCumulateSha1 (path: string): unknown; // 直接的猜测
|
||||||
|
|
||||||
genFileShaHex(path: string): unknown; // 直接的猜测
|
genFileShaHex (path: string): unknown; // 直接的猜测
|
||||||
|
|
||||||
fileIsExist(path: string): unknown;
|
fileIsExist (path: string): unknown;
|
||||||
|
|
||||||
startTrace(path: string): unknown; // 可能是错的
|
startTrace (path: string): unknown; // 可能是错的
|
||||||
|
|
||||||
copyFile(src: string, dst: string): unknown;
|
copyFile (src: string, dst: string): unknown;
|
||||||
|
|
||||||
genFileShaAndMd5Hex(path: string, unknown: number): unknown; // 可能是错的
|
genFileShaAndMd5Hex (path: string, unknown: number): unknown; // 可能是错的
|
||||||
|
|
||||||
setTraceInfo(unknown: unknown): unknown;
|
setTraceInfo (unknown: unknown): unknown;
|
||||||
|
|
||||||
encodeOffLine(unknown: unknown): unknown;
|
encodeOffLine (unknown: unknown): unknown;
|
||||||
|
|
||||||
decodeOffLine(arg: string): unknown; // 可能是错的 传递hex
|
decodeOffLine (arg: string): unknown; // 可能是错的 传递hex
|
||||||
|
|
||||||
DecoderRecentInfo(arg: string): unknown; // 可能是错的 传递hex
|
DecoderRecentInfo (arg: string): unknown; // 可能是错的 传递hex
|
||||||
|
|
||||||
getPinyin(arg0: string, arg1: boolean): unknown;
|
getPinyin (arg0: string, arg1: boolean): unknown;
|
||||||
|
|
||||||
matchInPinyin(arg0: unknown[], arg1: string): unknown; // 参数特复杂 arg0是个复杂数据类型
|
matchInPinyin (arg0: unknown[], arg1: string): unknown; // 参数特复杂 arg0是个复杂数据类型
|
||||||
|
|
||||||
makeDirByPath(arg0: string): unknown;
|
makeDirByPath (arg0: string): unknown;
|
||||||
|
|
||||||
emptyWorkingSet(arg0: number): unknown; // 参数是UINT32
|
emptyWorkingSet (arg0: number): unknown; // 参数是UINT32
|
||||||
|
|
||||||
runProcess(arg0: string, arg1: boolean): unknown;
|
runProcess (arg0: string, arg1: boolean): unknown;
|
||||||
|
|
||||||
runProcessArgs(arg0: string, arg1: { [key: string]: string }, arg2: boolean): unknown;
|
runProcessArgs (arg0: string, arg1: { [key: string]: string; }, arg2: boolean): unknown;
|
||||||
|
|
||||||
calcThumbSize(arg0: number, arg1: number, arg2: unknown): unknown;
|
calcThumbSize (arg0: number, arg1: number, arg2: unknown): unknown;
|
||||||
|
|
||||||
fullWordToHalfWord(word: string): unknown;
|
fullWordToHalfWord (word: string): unknown;
|
||||||
|
|
||||||
getNTUserDataInfoConfig(): unknown;
|
getNTUserDataInfoConfig (): unknown;
|
||||||
|
|
||||||
pathIsReadableAndWriteable(path: string): unknown; // 直接的猜测
|
pathIsReadableAndWriteable (path: string): unknown; // 直接的猜测
|
||||||
|
|
||||||
resetUserDataSavePathToDocument(): unknown;
|
resetUserDataSavePathToDocument (): unknown;
|
||||||
|
|
||||||
getSoBuildInfo(): unknown; // 例如 0[0]_d491dc01e0a_0
|
getSoBuildInfo (): unknown; // 例如 0[0]_d491dc01e0a_0
|
||||||
|
|
||||||
registerCountInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown;
|
registerCountInstruments (arg0: string, arg1: string[], arg2: number, arg3: number): unknown;
|
||||||
|
|
||||||
registerValueInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown;
|
registerValueInstruments (arg0: string, arg1: string[], arg2: number, arg3: number): unknown;
|
||||||
|
|
||||||
registerValueInstrumentsWithBoundary(
|
registerValueInstrumentsWithBoundary (
|
||||||
arg0: string,
|
arg0: string,
|
||||||
arg1: unknown,
|
arg1: unknown,
|
||||||
arg2: unknown,
|
arg2: unknown,
|
||||||
@@ -106,7 +107,7 @@ export interface NodeQQNTWrapperUtil {
|
|||||||
arg4: number,
|
arg4: number,
|
||||||
): unknown;
|
): unknown;
|
||||||
|
|
||||||
reportCountIndicators(
|
reportCountIndicators (
|
||||||
arg0: string,
|
arg0: string,
|
||||||
arg1: Map<unknown, unknown>,
|
arg1: Map<unknown, unknown>,
|
||||||
arg2: string,
|
arg2: string,
|
||||||
@@ -114,7 +115,7 @@ export interface NodeQQNTWrapperUtil {
|
|||||||
arg4: boolean,
|
arg4: boolean,
|
||||||
): unknown;
|
): unknown;
|
||||||
|
|
||||||
reportValueIndicators(
|
reportValueIndicators (
|
||||||
arg0: string,
|
arg0: string,
|
||||||
arg1: Map<unknown, unknown>,
|
arg1: Map<unknown, unknown>,
|
||||||
arg2: string,
|
arg2: string,
|
||||||
@@ -122,142 +123,154 @@ export interface NodeQQNTWrapperUtil {
|
|||||||
arg4: number,
|
arg4: number,
|
||||||
): unknown;
|
): unknown;
|
||||||
|
|
||||||
checkNewUserDataSaveDirAvailable(arg0: string): unknown;
|
checkNewUserDataSaveDirAvailable (arg0: string): unknown;
|
||||||
|
|
||||||
copyUserData(arg0: string, arg1: string): Promise<unknown>;
|
copyUserData (arg0: string, arg1: string): Promise<unknown>;
|
||||||
|
|
||||||
setUserDataSaveDirectory(arg0: string): Promise<unknown>;
|
setUserDataSaveDirectory (arg0: string): Promise<unknown>;
|
||||||
|
|
||||||
hasOtherRunningQQProcess(): boolean;
|
hasOtherRunningQQProcess (): boolean;
|
||||||
|
|
||||||
quitAllRunningQQProcess(arg: boolean): unknown;
|
quitAllRunningQQProcess (arg: boolean): unknown;
|
||||||
|
|
||||||
checkNvidiaConfig(): unknown;
|
checkNvidiaConfig (): unknown;
|
||||||
|
|
||||||
repairNvidiaConfig(): unknown;
|
repairNvidiaConfig (): unknown;
|
||||||
|
|
||||||
getNvidiaDriverVersion(): unknown;
|
getNvidiaDriverVersion (): unknown;
|
||||||
|
|
||||||
isNull(): unknown;
|
isNull (): unknown;
|
||||||
|
|
||||||
|
createThumbnailImage (
|
||||||
|
serviceName: string,
|
||||||
|
filePath: string,
|
||||||
|
targetPath: string,
|
||||||
|
imgSize: {
|
||||||
|
width: number,
|
||||||
|
height: number;
|
||||||
|
},
|
||||||
|
fileFormat: string,
|
||||||
|
arg: number | null | undefined, // null undefined都行
|
||||||
|
): Promise<GeneralCallResult & { targetPath?: string; }>;
|
||||||
}
|
}
|
||||||
export interface NodeIQQNTStartupSessionWrapper {
|
export interface NodeIQQNTStartupSessionWrapper {
|
||||||
create(): NodeIQQNTStartupSessionWrapper;
|
create (): NodeIQQNTStartupSessionWrapper;
|
||||||
stop(): void;
|
stop (): void;
|
||||||
start(): void;
|
start (): void;
|
||||||
createWithModuleList(uk: unknown): unknown;
|
createWithModuleList (uk: unknown): unknown;
|
||||||
getSessionIdList(): unknown;
|
getSessionIdList (): unknown;
|
||||||
}
|
}
|
||||||
export interface NodeIQQNTWrapperSession {
|
export interface NodeIQQNTWrapperSession {
|
||||||
getNTWrapperSession(str: string): NodeIQQNTWrapperSession;
|
getNTWrapperSession (str: string): NodeIQQNTWrapperSession;
|
||||||
|
|
||||||
get(): NodeIQQNTWrapperSession;
|
get (): NodeIQQNTWrapperSession;
|
||||||
|
|
||||||
new(): NodeIQQNTWrapperSession;
|
new(): NodeIQQNTWrapperSession;
|
||||||
|
|
||||||
create(): NodeIQQNTWrapperSession;
|
create (): NodeIQQNTWrapperSession;
|
||||||
|
|
||||||
init(
|
init (
|
||||||
wrapperSessionInitConfig: WrapperSessionInitConfig,
|
wrapperSessionInitConfig: WrapperSessionInitConfig,
|
||||||
nodeIDependsAdapter: NodeIDependsAdapter,
|
nodeIDependsAdapter: NodeIDependsAdapter,
|
||||||
nodeIDispatcherAdapter: NodeIDispatcherAdapter,
|
nodeIDispatcherAdapter: NodeIDispatcherAdapter,
|
||||||
nodeIKernelSessionListener: NodeIKernelSessionListener,
|
nodeIKernelSessionListener: NodeIKernelSessionListener,
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
startNT(session: number): void;
|
startNT (session: number): void;
|
||||||
|
|
||||||
startNT(): void;
|
startNT (): void;
|
||||||
|
|
||||||
getBdhUploadService(): unknown;
|
getBdhUploadService (): unknown;
|
||||||
|
|
||||||
getECDHService(): NodeIKernelECDHService;
|
getECDHService (): NodeIKernelECDHService;
|
||||||
|
|
||||||
getMsgService(): NodeIKernelMsgService;
|
getMsgService (): NodeIKernelMsgService;
|
||||||
|
|
||||||
getProfileService(): NodeIKernelProfileService;
|
getProfileService (): NodeIKernelProfileService;
|
||||||
|
|
||||||
getProfileLikeService(): NodeIKernelProfileLikeService;
|
getProfileLikeService (): NodeIKernelProfileLikeService;
|
||||||
|
|
||||||
getGroupService(): NodeIKernelGroupService;
|
getGroupService (): NodeIKernelGroupService;
|
||||||
|
|
||||||
getStorageCleanService(): NodeIKernelStorageCleanService;
|
getStorageCleanService (): NodeIKernelStorageCleanService;
|
||||||
|
|
||||||
getBuddyService(): NodeIKernelBuddyService;
|
getBuddyService (): NodeIKernelBuddyService;
|
||||||
|
|
||||||
getRobotService(): NodeIKernelRobotService;
|
getRobotService (): NodeIKernelRobotService;
|
||||||
|
|
||||||
getTicketService(): NodeIKernelTicketService;
|
getTicketService (): NodeIKernelTicketService;
|
||||||
|
|
||||||
getTipOffService(): NodeIKernelTipOffService;
|
getTipOffService (): NodeIKernelTipOffService;
|
||||||
|
|
||||||
getNodeMiscService(): NodeIKernelNodeMiscService;
|
getNodeMiscService (): NodeIKernelNodeMiscService;
|
||||||
|
|
||||||
getRichMediaService(): NodeIKernelRichMediaService;
|
getRichMediaService (): NodeIKernelRichMediaService;
|
||||||
|
|
||||||
getMsgBackupService(): NodeIKernelMsgBackupService;
|
getMsgBackupService (): NodeIKernelMsgBackupService;
|
||||||
|
|
||||||
getAlbumService(): NodeIKernelAlbumService;
|
getAlbumService (): NodeIKernelAlbumService;
|
||||||
|
|
||||||
getTianShuService(): NodeIKernelTianShuService;
|
getTianShuService (): NodeIKernelTianShuService;
|
||||||
|
|
||||||
getUnitedConfigService(): NodeIKernelUnitedConfigService;
|
getUnitedConfigService (): NodeIKernelUnitedConfigService;
|
||||||
|
|
||||||
getSearchService(): NodeIKernelSearchService;
|
getSearchService (): NodeIKernelSearchService;
|
||||||
|
|
||||||
getFlashTransferService(): NodeIKernelFlashTransferService;
|
getFlashTransferService (): NodeIKernelFlashTransferService;
|
||||||
|
|
||||||
getDirectSessionService(): unknown;
|
getDirectSessionService (): unknown;
|
||||||
|
|
||||||
getRDeliveryService(): unknown;
|
getRDeliveryService (): unknown;
|
||||||
|
|
||||||
getAvatarService(): NodeIKernelAvatarService;
|
getAvatarService (): NodeIKernelAvatarService;
|
||||||
|
|
||||||
getFeedChannelService(): unknown;
|
getFeedChannelService (): unknown;
|
||||||
|
|
||||||
getYellowFaceService(): unknown;
|
getYellowFaceService (): unknown;
|
||||||
|
|
||||||
getCollectionService(): NodeIKernelCollectionService;
|
getCollectionService (): NodeIKernelCollectionService;
|
||||||
|
|
||||||
getSettingService(): unknown;
|
getSettingService (): unknown;
|
||||||
|
|
||||||
getQiDianService(): unknown;
|
getQiDianService (): unknown;
|
||||||
|
|
||||||
getFileAssistantService(): unknown;
|
getFileAssistantService (): unknown;
|
||||||
|
|
||||||
getGuildService(): unknown;
|
getGuildService (): unknown;
|
||||||
|
|
||||||
getSkinService(): unknown;
|
getSkinService (): unknown;
|
||||||
|
|
||||||
getTestPerformanceService(): NodeIkernelTestPerformanceService;
|
getTestPerformanceService (): NodeIkernelTestPerformanceService;
|
||||||
|
|
||||||
getQQPlayService(): unknown;
|
getQQPlayService (): unknown;
|
||||||
|
|
||||||
getDbToolsService(): unknown;
|
getDbToolsService (): unknown;
|
||||||
|
|
||||||
getUixConvertService(): NodeIKernelUixConvertService;
|
getUixConvertService (): NodeIKernelUixConvertService;
|
||||||
|
|
||||||
getOnlineStatusService(): unknown;
|
getOnlineStatusService (): unknown;
|
||||||
|
|
||||||
getRemotingService(): unknown;
|
getRemotingService (): unknown;
|
||||||
|
|
||||||
getGroupTabService(): unknown;
|
getGroupTabService (): unknown;
|
||||||
|
|
||||||
getGroupSchoolService(): unknown;
|
getGroupSchoolService (): unknown;
|
||||||
|
|
||||||
getLiteBusinessService(): unknown;
|
getLiteBusinessService (): unknown;
|
||||||
|
|
||||||
getGuildMsgService(): unknown;
|
getGuildMsgService (): unknown;
|
||||||
|
|
||||||
getLockService(): unknown;
|
getLockService (): unknown;
|
||||||
|
|
||||||
getMSFService(): NodeIKernelMSFService;
|
getMSFService (): NodeIKernelMSFService;
|
||||||
|
|
||||||
getGuildHotUpdateService(): unknown;
|
getGuildHotUpdateService (): unknown;
|
||||||
|
|
||||||
getAVSDKService(): unknown;
|
getAVSDKService (): unknown;
|
||||||
|
|
||||||
getRecentContactService(): NodeIKernelRecentContactService;
|
getRecentContactService (): NodeIKernelRecentContactService;
|
||||||
|
|
||||||
getConfigMgrService(): unknown;
|
getConfigMgrService (): unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnginInitDesktopConfig {
|
export interface EnginInitDesktopConfig {
|
||||||
@@ -271,20 +284,20 @@ export interface EnginInitDesktopConfig {
|
|||||||
global_path_config: {
|
global_path_config: {
|
||||||
desktopGlobalPath: string;
|
desktopGlobalPath: string;
|
||||||
};
|
};
|
||||||
thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 };
|
thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2; };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeIQQNTWrapperEngine {
|
export interface NodeIQQNTWrapperEngine {
|
||||||
get(): NodeIQQNTWrapperEngine;
|
get (): NodeIQQNTWrapperEngine;
|
||||||
|
|
||||||
initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
|
initWithDeskTopConfig (config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WrapperNodeApi {
|
export interface WrapperNodeApi {
|
||||||
NodeIO3MiscService: NodeIO3MiscService;
|
NodeIO3MiscService: NodeIO3MiscService;
|
||||||
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
|
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
|
||||||
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
|
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
|
||||||
NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper
|
NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper;
|
||||||
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
|
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
|
||||||
NodeIKernelLoginService: NodeIKernelLoginService;
|
NodeIKernelLoginService: NodeIKernelLoginService;
|
||||||
|
|
||||||
|
|||||||
4
packages/napcat-develop/config/.env
Normal file
4
packages/napcat-develop/config/.env
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
NAPCAT_DISABLE_PIPE=1
|
||||||
|
NAPCAT_DISABLE_MULTI_PROCESS=1
|
||||||
|
NAPCAT_WEBUI_JWT_SECRET_KEY=napcat_dev_secret_key
|
||||||
|
NAPCAT_WEBUI_SECRET_KEY=napcatqq
|
||||||
39
packages/napcat-develop/config/onebot11.json
Normal file
39
packages/napcat-develop/config/onebot11.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"network": {
|
||||||
|
"httpServers": [
|
||||||
|
{
|
||||||
|
"enable": true,
|
||||||
|
"name": "HTTP",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 3000,
|
||||||
|
"enableCors": true,
|
||||||
|
"enableWebsocket": false,
|
||||||
|
"messagePostFormat": "array",
|
||||||
|
"token": "",
|
||||||
|
"debug": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"httpSseServers": [],
|
||||||
|
"httpClients": [],
|
||||||
|
"websocketServers": [
|
||||||
|
{
|
||||||
|
"enable": true,
|
||||||
|
"name": "WebSocket",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 3001,
|
||||||
|
"reportSelfMessage": false,
|
||||||
|
"enableForcePushEvent": true,
|
||||||
|
"messagePostFormat": "array",
|
||||||
|
"token": "",
|
||||||
|
"debug": false,
|
||||||
|
"heartInterval": 30000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"websocketClients": [],
|
||||||
|
"plugins": []
|
||||||
|
},
|
||||||
|
"musicSignUrl": "",
|
||||||
|
"enableLocalFile2Url": false,
|
||||||
|
"parseMultMsg": false,
|
||||||
|
"imageDownloadProxy": ""
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ async function copyAll () {
|
|||||||
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_JWT_SECRET_KEY = 'napcat_dev_secret_key';
|
||||||
process.env.NAPCAT_WEBUI_SECRET_KEY = 'napcat';
|
process.env.NAPCAT_WEBUI_SECRET_KEY = 'napcatqq';
|
||||||
console.log('Loading NapCat module...');
|
console.log('Loading NapCat module...');
|
||||||
await import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
await import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "napcat-develop",
|
"name": "napcat-develop",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "powershell ./nodeTest.ps1"
|
"dev": "powershell ./nodeTest.ps1",
|
||||||
|
"copy-env": "xcopy config ..\\napcat-shell\\dist\\config /E /I /Y"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"require": "./index.js"
|
||||||
},
|
},
|
||||||
"exports": {
|
"./*": {
|
||||||
".": {
|
"require": "./*"
|
||||||
"require": "./index.js"
|
|
||||||
},
|
|
||||||
"./*": {
|
|
||||||
"require": "./*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"fs-extra": "^11.3.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^22.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fs-extra": "^11.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NapCatPathWrapper } from 'napcat-common/src/path';
|
import { NapCatPathWrapper } from 'napcat-common/src/path';
|
||||||
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index';
|
import { InitWebUi, WebUiConfig, webUiRuntimePort } from 'napcat-webui-backend/index';
|
||||||
import { NapCatOneBot11Adapter } from 'napcat-onebot/index';
|
import { NapCatAdapterManager } from 'napcat-adapter';
|
||||||
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 { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg';
|
||||||
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
|
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
|
||||||
@@ -79,11 +79,14 @@ export async function NCoreInitFramework (
|
|||||||
// 启动WebUi
|
// 启动WebUi
|
||||||
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
|
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
|
||||||
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
|
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
|
||||||
// 初始化LLNC的Onebot实现
|
// 使用 NapCatAdapterManager 统一管理协议适配器
|
||||||
const oneBotAdapter = new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper);
|
const adapterManager = new NapCatAdapterManager(loaderObject.core, loaderObject.context, pathWrapper);
|
||||||
// 注册到 WebUiDataRuntime,供调试功能使用
|
await adapterManager.initAdapters();
|
||||||
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
// 注册 OneBot 适配器到 WebUiDataRuntime,供调试功能使用
|
||||||
await oneBotAdapter.InitOneBot();
|
const oneBotAdapter = adapterManager.getOneBotAdapter();
|
||||||
|
if (oneBotAdapter) {
|
||||||
|
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NapCatFramework {
|
export class NapCatFramework {
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
{
|
{
|
||||||
"name": "napcat-framework",
|
"name": "napcat-framework",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./index.ts"
|
||||||
},
|
},
|
||||||
"exports": {
|
"./*": {
|
||||||
".": {
|
"import": "./*"
|
||||||
"import": "./index.ts"
|
|
||||||
},
|
|
||||||
"./*": {
|
|
||||||
"import": "./*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"napcat-core": "workspace:*",
|
|
||||||
"napcat-common": "workspace:*",
|
|
||||||
"napcat-onebot": "workspace:*",
|
|
||||||
"napcat-webui-backend": "workspace:*",
|
|
||||||
"napcat-vite": "workspace:*",
|
|
||||||
"napcat-qrcode": "workspace:*"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^22.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"napcat-core": "workspace:*",
|
||||||
|
"napcat-common": "workspace:*",
|
||||||
|
"napcat-adapter": "workspace:*",
|
||||||
|
"napcat-webui-backend": "workspace:*",
|
||||||
|
"napcat-vite": "workspace:*",
|
||||||
|
"napcat-qrcode": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,9 @@ const FrameworkBaseConfig = () =>
|
|||||||
'@/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'),
|
'@/napcat-webui-backend': resolve(__dirname, '../napcat-webui-backend'),
|
||||||
'@/image-size': resolve(__dirname, '../image-size'),
|
'@/napcat-image-size': resolve(__dirname, '../napcat-image-size'),
|
||||||
|
'@/napcat-protocol': resolve(__dirname, '../napcat-protocol'),
|
||||||
|
'@/napcat-adapter': resolve(__dirname, '../napcat-adapter'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
BIN
packages/napcat-image-size/resource/test-20x20.jpg
Normal file
BIN
packages/napcat-image-size/resource/test-20x20.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/napcat-image-size/resource/test-20x20.png
Normal file
BIN
packages/napcat-image-size/resource/test-20x20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
packages/napcat-image-size/resource/test-20x20.tiff
Normal file
BIN
packages/napcat-image-size/resource/test-20x20.tiff
Normal file
Binary file not shown.
BIN
packages/napcat-image-size/resource/test-20x20.webp
Normal file
BIN
packages/napcat-image-size/resource/test-20x20.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 962 B |
BIN
packages/napcat-image-size/resource/test-490x498.gif
Normal file
BIN
packages/napcat-image-size/resource/test-490x498.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -1,5 +1,12 @@
|
|||||||
|
import { BmpParser } from '@/napcat-image-size/src/parser/BmpParser';
|
||||||
|
import { GifParser } from '@/napcat-image-size/src/parser/GifParser';
|
||||||
|
import { JpegParser } from '@/napcat-image-size/src/parser/JpegParser';
|
||||||
|
import { PngParser } from '@/napcat-image-size/src/parser/PngParser';
|
||||||
|
import { TiffParser } from '@/napcat-image-size/src/parser/TiffParser';
|
||||||
|
import { WebpParser } from '@/napcat-image-size/src/parser/WebpParser';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { ReadStream } from 'fs';
|
import { ReadStream } from 'fs';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
export interface ImageSize {
|
export interface ImageSize {
|
||||||
width: number;
|
width: number;
|
||||||
@@ -12,17 +19,18 @@ export enum ImageType {
|
|||||||
BMP = 'bmp',
|
BMP = 'bmp',
|
||||||
GIF = 'gif',
|
GIF = 'gif',
|
||||||
WEBP = 'webp',
|
WEBP = 'webp',
|
||||||
|
TIFF = 'tiff',
|
||||||
UNKNOWN = 'unknown',
|
UNKNOWN = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImageParser {
|
export interface ImageParser {
|
||||||
readonly type: ImageType;
|
readonly type: ImageType;
|
||||||
canParse(buffer: Buffer): boolean;
|
canParse (buffer: Buffer): boolean;
|
||||||
parseSize(stream: ReadStream): Promise<ImageSize | undefined>;
|
parseSize (stream: ReadStream): Promise<ImageSize | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 魔术匹配
|
// 魔术匹配
|
||||||
function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean {
|
export function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean {
|
||||||
if (buffer.length < offset + magic.length) {
|
if (buffer.length < offset + magic.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -35,316 +43,39 @@ function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PNG解析器
|
// 所有解析器实例
|
||||||
class PngParser implements ImageParser {
|
const parserInstances = {
|
||||||
readonly type = ImageType.PNG;
|
png: new PngParser(),
|
||||||
// PNG 魔术头:89 50 4E 47 0D 0A 1A 0A
|
jpeg: new JpegParser(),
|
||||||
private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
bmp: new BmpParser(),
|
||||||
|
gif: new GifParser(),
|
||||||
|
webp: new WebpParser(),
|
||||||
|
tiff: new TiffParser(),
|
||||||
|
};
|
||||||
|
|
||||||
canParse (buffer: Buffer): boolean {
|
// 首字节到可能的图片类型映射,用于快速筛选
|
||||||
return matchMagic(buffer, this.PNG_SIGNATURE);
|
const firstByteMap = new Map<number, ImageType[]>([
|
||||||
}
|
[0x42, [ImageType.BMP]], // 'B' - BMP
|
||||||
|
[0x47, [ImageType.GIF]], // 'G' - GIF
|
||||||
|
[0x49, [ImageType.TIFF]], // 'I' - TIFF (II - little endian)
|
||||||
|
[0x4D, [ImageType.TIFF]], // 'M' - TIFF (MM - big endian)
|
||||||
|
[0x52, [ImageType.WEBP]], // 'R' - RIFF (WebP)
|
||||||
|
[0x89, [ImageType.PNG]], // PNG signature
|
||||||
|
[0xFF, [ImageType.JPEG]], // JPEG SOI
|
||||||
|
]);
|
||||||
|
|
||||||
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
// 类型到解析器的映射
|
||||||
return new Promise((resolve, reject) => {
|
const typeToParser = new Map<ImageType, ImageParser>([
|
||||||
stream.once('error', reject);
|
[ImageType.PNG, parserInstances.png],
|
||||||
stream.once('readable', () => {
|
[ImageType.JPEG, parserInstances.jpeg],
|
||||||
const buf = stream.read(24) as Buffer;
|
[ImageType.BMP, parserInstances.bmp],
|
||||||
if (!buf || buf.length < 24) {
|
[ImageType.GIF, parserInstances.gif],
|
||||||
return resolve(undefined);
|
[ImageType.WEBP, parserInstances.webp],
|
||||||
}
|
[ImageType.TIFF, parserInstances.tiff],
|
||||||
if (this.canParse(buf)) {
|
]);
|
||||||
const width = buf.readUInt32BE(16);
|
|
||||||
const height = buf.readUInt32BE(20);
|
|
||||||
resolve({ width, height });
|
|
||||||
} else {
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JPEG解析器
|
// 所有解析器列表(用于回退)
|
||||||
class JpegParser implements ImageParser {
|
const parsers: ReadonlyArray<ImageParser> = Object.values(parserInstances);
|
||||||
readonly type = ImageType.JPEG;
|
|
||||||
// JPEG 魔术头:FF D8
|
|
||||||
private readonly JPEG_SIGNATURE = [0xFF, 0xD8];
|
|
||||||
|
|
||||||
// JPEG标记常量
|
|
||||||
private readonly SOF_MARKERS = {
|
|
||||||
SOF0: 0xC0, // 基线DCT
|
|
||||||
SOF1: 0xC1, // 扩展顺序DCT
|
|
||||||
SOF2: 0xC2, // 渐进式DCT
|
|
||||||
SOF3: 0xC3, // 无损
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// 非SOF标记
|
|
||||||
private readonly NON_SOF_MARKERS: number[] = [
|
|
||||||
0xC4, // DHT
|
|
||||||
0xC8, // JPEG扩展
|
|
||||||
0xCC, // DAC
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
canParse (buffer: Buffer): boolean {
|
|
||||||
return matchMagic(buffer, this.JPEG_SIGNATURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSOFMarker (marker: number): boolean {
|
|
||||||
return (
|
|
||||||
marker === this.SOF_MARKERS.SOF0 ||
|
|
||||||
marker === this.SOF_MARKERS.SOF1 ||
|
|
||||||
marker === this.SOF_MARKERS.SOF2 ||
|
|
||||||
marker === this.SOF_MARKERS.SOF3
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isNonSOFMarker (marker: number): boolean {
|
|
||||||
return this.NON_SOF_MARKERS.includes(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
|
||||||
return new Promise<ImageSize | undefined>((resolve, reject) => {
|
|
||||||
const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整
|
|
||||||
let buffer = Buffer.alloc(0);
|
|
||||||
let offset = 0;
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
// 处理错误
|
|
||||||
stream.on('error', (err) => {
|
|
||||||
stream.destroy();
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理数据块
|
|
||||||
stream.on('data', (chunk: Buffer | string) => {
|
|
||||||
// 追加新数据到缓冲区
|
|
||||||
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
||||||
buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]);
|
|
||||||
offset = 0;
|
|
||||||
|
|
||||||
// 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配
|
|
||||||
const bufferSize = buffer.length;
|
|
||||||
const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数
|
|
||||||
|
|
||||||
// 从JPEG头部后开始扫描
|
|
||||||
while (offset < bufferSize - MIN_REQUIRED_BYTES) {
|
|
||||||
// 寻找FF标记
|
|
||||||
if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) {
|
|
||||||
const marker = buffer[offset + 1];
|
|
||||||
if (!marker) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 跳过非SOF标记
|
|
||||||
if (this.isNonSOFMarker(marker)) {
|
|
||||||
offset += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理SOF标记 (包含尺寸信息)
|
|
||||||
if (this.isSOFMarker(marker)) {
|
|
||||||
// 确保缓冲区中有足够数据读取尺寸
|
|
||||||
if (offset + 9 < bufferSize) {
|
|
||||||
// 解析尺寸: FF XX YY YY PP HH HH WW WW ...
|
|
||||||
// XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽
|
|
||||||
const height = buffer.readUInt16BE(offset + 5);
|
|
||||||
const width = buffer.readUInt16BE(offset + 7);
|
|
||||||
|
|
||||||
found = true;
|
|
||||||
stream.destroy();
|
|
||||||
resolve({ width, height });
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// 如果缓冲区内数据不够,保留当前位置等待更多数据
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓冲区管理: 如果处理了许多数据但没找到标记,
|
|
||||||
// 保留最后N字节用于跨块匹配,丢弃之前的数据
|
|
||||||
if (offset > BUFFER_SIZE) {
|
|
||||||
const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况
|
|
||||||
if (offset > KEEP_BYTES) {
|
|
||||||
buffer = buffer.subarray(offset - KEEP_BYTES);
|
|
||||||
offset = KEEP_BYTES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理流结束
|
|
||||||
stream.on('end', () => {
|
|
||||||
if (!found) {
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BMP解析器
|
|
||||||
class BmpParser implements ImageParser {
|
|
||||||
readonly type = ImageType.BMP;
|
|
||||||
// BMP 魔术头:42 4D (BM)
|
|
||||||
private readonly BMP_SIGNATURE = [0x42, 0x4D];
|
|
||||||
|
|
||||||
canParse (buffer: Buffer): boolean {
|
|
||||||
return matchMagic(buffer, this.BMP_SIGNATURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stream.once('error', reject);
|
|
||||||
stream.once('readable', () => {
|
|
||||||
const buf = stream.read(26) as Buffer;
|
|
||||||
if (!buf || buf.length < 26) {
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
if (this.canParse(buf)) {
|
|
||||||
const width = buf.readUInt32LE(18);
|
|
||||||
const height = buf.readUInt32LE(22);
|
|
||||||
resolve({ width, height });
|
|
||||||
} else {
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GIF解析器
|
|
||||||
class GifParser implements ImageParser {
|
|
||||||
readonly type = ImageType.GIF;
|
|
||||||
// GIF87a 魔术头:47 49 46 38 37 61
|
|
||||||
private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];
|
|
||||||
// GIF89a 魔术头:47 49 46 38 39 61
|
|
||||||
private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
|
|
||||||
|
|
||||||
canParse (buffer: Buffer): boolean {
|
|
||||||
return (
|
|
||||||
matchMagic(buffer, this.GIF87A_SIGNATURE) ||
|
|
||||||
matchMagic(buffer, this.GIF89A_SIGNATURE)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stream.once('error', reject);
|
|
||||||
stream.once('readable', () => {
|
|
||||||
const buf = stream.read(10) as Buffer;
|
|
||||||
if (!buf || buf.length < 10) {
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
if (this.canParse(buf)) {
|
|
||||||
const width = buf.readUInt16LE(6);
|
|
||||||
const height = buf.readUInt16LE(8);
|
|
||||||
resolve({ width, height });
|
|
||||||
} else {
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEBP解析器 - 完整支持VP8, VP8L, VP8X格式
|
|
||||||
class WebpParser implements ImageParser {
|
|
||||||
readonly type = ImageType.WEBP;
|
|
||||||
// WEBP RIFF 头:52 49 46 46 (RIFF)
|
|
||||||
private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46];
|
|
||||||
// WEBP 魔术头:57 45 42 50 (WEBP)
|
|
||||||
private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50];
|
|
||||||
|
|
||||||
// WEBP 块头
|
|
||||||
private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 "
|
|
||||||
private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L"
|
|
||||||
private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X"
|
|
||||||
|
|
||||||
canParse (buffer: Buffer): boolean {
|
|
||||||
return (
|
|
||||||
buffer.length >= 12 &&
|
|
||||||
matchMagic(buffer, this.RIFF_SIGNATURE, 0) &&
|
|
||||||
matchMagic(buffer, this.WEBP_SIGNATURE, 8)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
isChunkType (buffer: Buffer, offset: number, chunkType: number[]): boolean {
|
|
||||||
return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 需要读取足够的字节来检测所有三种格式
|
|
||||||
const MAX_HEADER_SIZE = 32;
|
|
||||||
let totalBytes = 0;
|
|
||||||
let buffer = Buffer.alloc(0);
|
|
||||||
|
|
||||||
stream.on('error', reject);
|
|
||||||
|
|
||||||
stream.on('data', (chunk: Buffer | string) => {
|
|
||||||
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
||||||
buffer = Buffer.concat([buffer, chunkBuffer]);
|
|
||||||
totalBytes += chunk.length;
|
|
||||||
|
|
||||||
// 检查是否有足够的字节进行格式检测
|
|
||||||
if (totalBytes >= MAX_HEADER_SIZE) {
|
|
||||||
stream.destroy();
|
|
||||||
|
|
||||||
// 检查基本的WEBP签名
|
|
||||||
if (!this.canParse(buffer)) {
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查chunk头部,位于字节12-15
|
|
||||||
if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) {
|
|
||||||
// VP8格式 - 标准WebP
|
|
||||||
// 宽度和高度在帧头中
|
|
||||||
const width = buffer.readUInt16LE(26) & 0x3FFF;
|
|
||||||
const height = buffer.readUInt16LE(28) & 0x3FFF;
|
|
||||||
return resolve({ width, height });
|
|
||||||
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) {
|
|
||||||
// VP8L格式 - 无损WebP
|
|
||||||
// 1字节标记后是14位宽度和14位高度
|
|
||||||
const bits = buffer.readUInt32LE(21);
|
|
||||||
const width = 1 + (bits & 0x3FFF);
|
|
||||||
const height = 1 + ((bits >> 14) & 0x3FFF);
|
|
||||||
return resolve({ width, height });
|
|
||||||
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) {
|
|
||||||
// VP8X格式 - 扩展WebP
|
|
||||||
// 24位宽度和高度(减去1)
|
|
||||||
if (!buffer[24] || !buffer[25] || !buffer[26] || !buffer[27] || !buffer[28] || !buffer[29]) {
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
const width = 1 + ((buffer[24] | (buffer[25] << 8) | (buffer[26] << 16)) & 0xFFFFFF);
|
|
||||||
const height = 1 + ((buffer[27] | (buffer[28] << 8) | (buffer[29] << 16)) & 0xFFFFFF);
|
|
||||||
return resolve({ width, height });
|
|
||||||
} else {
|
|
||||||
// 未知的WebP子格式
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', () => {
|
|
||||||
// 如果没有读到足够的字节
|
|
||||||
if (totalBytes < MAX_HEADER_SIZE) {
|
|
||||||
resolve(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsers: ReadonlyArray<ImageParser> = [
|
|
||||||
new PngParser(),
|
|
||||||
new JpegParser(),
|
|
||||||
new BmpParser(),
|
|
||||||
new GifParser(),
|
|
||||||
new WebpParser(),
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function detectImageType (filePath: string): Promise<ImageType> {
|
export async function detectImageType (filePath: string): Promise<ImageType> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -354,18 +85,22 @@ export async function detectImageType (filePath: string): Promise<ImageType> {
|
|||||||
end: 63,
|
end: 63,
|
||||||
});
|
});
|
||||||
|
|
||||||
let buffer: Buffer | null = null;
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
stream.once('error', (err) => {
|
stream.on('error', (err) => {
|
||||||
stream.destroy();
|
stream.destroy();
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.once('readable', () => {
|
stream.on('data', (chunk: Buffer | string) => {
|
||||||
buffer = stream.read(64) as Buffer;
|
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||||
stream.destroy();
|
chunks.push(chunkBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
if (!buffer) {
|
stream.on('end', () => {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
|
||||||
|
if (buffer.length === 0) {
|
||||||
return resolve(ImageType.UNKNOWN);
|
return resolve(ImageType.UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,12 +112,6 @@ export async function detectImageType (filePath: string): Promise<ImageType> {
|
|||||||
|
|
||||||
resolve(ImageType.UNKNOWN);
|
resolve(ImageType.UNKNOWN);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.once('end', () => {
|
|
||||||
if (!buffer) {
|
|
||||||
resolve(ImageType.UNKNOWN);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,7 +119,7 @@ export async function imageSizeFromFile (filePath: string): Promise<ImageSize |
|
|||||||
try {
|
try {
|
||||||
// 先检测类型
|
// 先检测类型
|
||||||
const type = await detectImageType(filePath);
|
const type = await detectImageType(filePath);
|
||||||
const parser = parsers.find(p => p.type === type);
|
const parser = typeToParser.get(type);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -422,3 +151,71 @@ export async function imageSizeFallBack (
|
|||||||
): Promise<ImageSize> {
|
): Promise<ImageSize> {
|
||||||
return await imageSizeFromFile(filePath) ?? fallback;
|
return await imageSizeFromFile(filePath) ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 Buffer 创建可读流
|
||||||
|
function bufferToReadStream (buffer: Buffer): ReadStream {
|
||||||
|
const readable = new Readable({
|
||||||
|
read () {
|
||||||
|
this.push(buffer);
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return readable as unknown as ReadStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Buffer 检测图片类型(使用首字节快速筛选)
|
||||||
|
export function detectImageTypeFromBuffer (buffer: Buffer): ImageType {
|
||||||
|
if (buffer.length === 0) {
|
||||||
|
return ImageType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstByte = buffer[0]!;
|
||||||
|
const possibleTypes = firstByteMap.get(firstByte);
|
||||||
|
|
||||||
|
if (possibleTypes) {
|
||||||
|
// 根据首字节快速筛选可能的类型
|
||||||
|
for (const type of possibleTypes) {
|
||||||
|
const parser = typeToParser.get(type);
|
||||||
|
if (parser && parser.canParse(buffer)) {
|
||||||
|
return parser.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退:遍历所有解析器
|
||||||
|
for (const parser of parsers) {
|
||||||
|
if (parser.canParse(buffer)) {
|
||||||
|
return parser.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Buffer 解析图片尺寸
|
||||||
|
export async function imageSizeFromBuffer (buffer: Buffer): Promise<ImageSize | undefined> {
|
||||||
|
const type = detectImageTypeFromBuffer(buffer);
|
||||||
|
const parser = typeToParser.get(type);
|
||||||
|
if (!parser) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stream = bufferToReadStream(buffer);
|
||||||
|
return await parser.parseSize(stream);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`解析图片尺寸出错: ${err}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Buffer 解析图片尺寸,带回退值
|
||||||
|
export async function imageSizeFromBufferFallBack (
|
||||||
|
buffer: Buffer,
|
||||||
|
fallback: ImageSize = {
|
||||||
|
width: 1024,
|
||||||
|
height: 1024,
|
||||||
|
}
|
||||||
|
): Promise<ImageSize> {
|
||||||
|
return await imageSizeFromBuffer(buffer) ?? fallback;
|
||||||
|
}
|
||||||
|
|||||||
32
packages/napcat-image-size/src/parser/BmpParser.ts
Normal file
32
packages/napcat-image-size/src/parser/BmpParser.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// BMP解析器
|
||||||
|
export class BmpParser implements ImageParser {
|
||||||
|
readonly type = ImageType.BMP;
|
||||||
|
// BMP 魔术头:42 4D (BM)
|
||||||
|
private readonly BMP_SIGNATURE = [0x42, 0x4D];
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return matchMagic(buffer, this.BMP_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
stream.once('error', reject);
|
||||||
|
stream.once('readable', () => {
|
||||||
|
const buf = stream.read(26) as Buffer;
|
||||||
|
if (!buf || buf.length < 26) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
if (this.canParse(buf)) {
|
||||||
|
const width = buf.readUInt32LE(18);
|
||||||
|
const height = buf.readUInt32LE(22);
|
||||||
|
resolve({ width, height });
|
||||||
|
} else {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
37
packages/napcat-image-size/src/parser/GifParser.ts
Normal file
37
packages/napcat-image-size/src/parser/GifParser.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// GIF解析器
|
||||||
|
export class GifParser implements ImageParser {
|
||||||
|
readonly type = ImageType.GIF;
|
||||||
|
// GIF87a 魔术头:47 49 46 38 37 61
|
||||||
|
private readonly GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];
|
||||||
|
// GIF89a 魔术头:47 49 46 38 39 61
|
||||||
|
private readonly GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return (
|
||||||
|
matchMagic(buffer, this.GIF87A_SIGNATURE) ||
|
||||||
|
matchMagic(buffer, this.GIF89A_SIGNATURE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
stream.once('error', reject);
|
||||||
|
stream.once('readable', () => {
|
||||||
|
const buf = stream.read(10) as Buffer;
|
||||||
|
if (!buf || buf.length < 10) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
if (this.canParse(buf)) {
|
||||||
|
const width = buf.readUInt16LE(6);
|
||||||
|
const height = buf.readUInt16LE(8);
|
||||||
|
resolve({ width, height });
|
||||||
|
} else {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
123
packages/napcat-image-size/src/parser/JpegParser.ts
Normal file
123
packages/napcat-image-size/src/parser/JpegParser.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// JPEG解析器
|
||||||
|
export class JpegParser implements ImageParser {
|
||||||
|
readonly type = ImageType.JPEG;
|
||||||
|
// JPEG 魔术头:FF D8
|
||||||
|
private readonly JPEG_SIGNATURE = [0xFF, 0xD8];
|
||||||
|
|
||||||
|
// JPEG标记常量
|
||||||
|
private readonly SOF_MARKERS = {
|
||||||
|
SOF0: 0xC0, // 基线DCT
|
||||||
|
SOF1: 0xC1, // 扩展顺序DCT
|
||||||
|
SOF2: 0xC2, // 渐进式DCT
|
||||||
|
SOF3: 0xC3, // 无损
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// 非SOF标记
|
||||||
|
private readonly NON_SOF_MARKERS: number[] = [
|
||||||
|
0xC4, // DHT
|
||||||
|
0xC8, // JPEG扩展
|
||||||
|
0xCC, // DAC
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return matchMagic(buffer, this.JPEG_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSOFMarker (marker: number): boolean {
|
||||||
|
return (
|
||||||
|
marker === this.SOF_MARKERS.SOF0 ||
|
||||||
|
marker === this.SOF_MARKERS.SOF1 ||
|
||||||
|
marker === this.SOF_MARKERS.SOF2 ||
|
||||||
|
marker === this.SOF_MARKERS.SOF3
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isNonSOFMarker (marker: number): boolean {
|
||||||
|
return this.NON_SOF_MARKERS.includes(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise<ImageSize | undefined>((resolve, reject) => {
|
||||||
|
const BUFFER_SIZE = 1024; // 读取块大小,可以根据需要调整
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
let offset = 0;
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
// 处理错误
|
||||||
|
stream.on('error', (err) => {
|
||||||
|
stream.destroy();
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理数据块
|
||||||
|
stream.on('data', (chunk: Buffer | string) => {
|
||||||
|
// 追加新数据到缓冲区
|
||||||
|
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||||
|
buffer = Buffer.concat([buffer.subarray(offset), chunkBuffer]);
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
// 保持缓冲区在合理大小内,只保留最后的部分用于跨块匹配
|
||||||
|
const bufferSize = buffer.length;
|
||||||
|
const MIN_REQUIRED_BYTES = 10; // SOF段最低字节数
|
||||||
|
|
||||||
|
|
||||||
|
// 从JPEG头部后开始扫描
|
||||||
|
while (offset < bufferSize - MIN_REQUIRED_BYTES) {
|
||||||
|
// 寻找FF标记
|
||||||
|
if (buffer[offset] === 0xFF && buffer[offset + 1]! >= 0xC0 && buffer[offset + 1]! <= 0xCF) {
|
||||||
|
const marker = buffer[offset + 1];
|
||||||
|
if (!marker) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 跳过非SOF标记
|
||||||
|
if (this.isNonSOFMarker(marker)) {
|
||||||
|
offset += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理SOF标记 (包含尺寸信息)
|
||||||
|
if (this.isSOFMarker(marker)) {
|
||||||
|
// 确保缓冲区中有足够数据读取尺寸
|
||||||
|
if (offset + 9 < bufferSize) {
|
||||||
|
// 解析尺寸: FF XX YY YY PP HH HH WW WW ...
|
||||||
|
// XX = 标记, YY YY = 段长度, PP = 精度, HH HH = 高, WW WW = 宽
|
||||||
|
const height = buffer.readUInt16BE(offset + 5);
|
||||||
|
const width = buffer.readUInt16BE(offset + 7);
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
stream.destroy();
|
||||||
|
resolve({ width, height });
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// 如果缓冲区内数据不够,保留当前位置等待更多数据
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓冲区管理: 如果处理了许多数据但没找到标记,
|
||||||
|
// 保留最后N字节用于跨块匹配,丢弃之前的数据
|
||||||
|
if (offset > BUFFER_SIZE) {
|
||||||
|
const KEEP_BYTES = 20; // 保留足够数据以处理跨块边界的情况
|
||||||
|
if (offset > KEEP_BYTES) {
|
||||||
|
buffer = buffer.subarray(offset - KEEP_BYTES);
|
||||||
|
offset = KEEP_BYTES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理流结束
|
||||||
|
stream.on('end', () => {
|
||||||
|
if (!found) {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
32
packages/napcat-image-size/src/parser/PngParser.ts
Normal file
32
packages/napcat-image-size/src/parser/PngParser.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// PNG解析器
|
||||||
|
export class PngParser implements ImageParser {
|
||||||
|
readonly type = ImageType.PNG;
|
||||||
|
// PNG 魔术头:89 50 4E 47 0D 0A 1A 0A
|
||||||
|
private readonly PNG_SIGNATURE = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return matchMagic(buffer, this.PNG_SIGNATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
stream.once('error', reject);
|
||||||
|
stream.once('readable', () => {
|
||||||
|
const buf = stream.read(24) as Buffer;
|
||||||
|
if (!buf || buf.length < 24) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
if (this.canParse(buf)) {
|
||||||
|
const width = buf.readUInt32BE(16);
|
||||||
|
const height = buf.readUInt32BE(20);
|
||||||
|
resolve({ width, height });
|
||||||
|
} else {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
124
packages/napcat-image-size/src/parser/TiffParser.ts
Normal file
124
packages/napcat-image-size/src/parser/TiffParser.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// TIFF解析器
|
||||||
|
export class TiffParser implements ImageParser {
|
||||||
|
readonly type = ImageType.TIFF;
|
||||||
|
// TIFF Little Endian 魔术头:49 49 2A 00 (II)
|
||||||
|
private readonly TIFF_LE_SIGNATURE = [0x49, 0x49, 0x2A, 0x00];
|
||||||
|
// TIFF Big Endian 魔术头:4D 4D 00 2A (MM)
|
||||||
|
private readonly TIFF_BE_SIGNATURE = [0x4D, 0x4D, 0x00, 0x2A];
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return (
|
||||||
|
matchMagic(buffer, this.TIFF_LE_SIGNATURE) ||
|
||||||
|
matchMagic(buffer, this.TIFF_BE_SIGNATURE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let totalBytes = 0;
|
||||||
|
const MAX_BYTES = 64 * 1024; // 最多读取 64KB
|
||||||
|
|
||||||
|
stream.on('error', reject);
|
||||||
|
|
||||||
|
stream.on('data', (chunk: Buffer | string) => {
|
||||||
|
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||||
|
chunks.push(chunkBuffer);
|
||||||
|
totalBytes += chunkBuffer.length;
|
||||||
|
|
||||||
|
if (totalBytes >= MAX_BYTES) {
|
||||||
|
stream.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
const size = this.parseTiffSize(buffer);
|
||||||
|
resolve(size);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('close', () => {
|
||||||
|
if (chunks.length > 0) {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
const size = this.parseTiffSize(buffer);
|
||||||
|
resolve(size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseTiffSize (buffer: Buffer): ImageSize | undefined {
|
||||||
|
if (buffer.length < 8) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断字节序
|
||||||
|
const isLittleEndian = buffer[0] === 0x49; // 'I'
|
||||||
|
|
||||||
|
const readUInt16 = isLittleEndian
|
||||||
|
? (offset: number) => buffer.readUInt16LE(offset)
|
||||||
|
: (offset: number) => buffer.readUInt16BE(offset);
|
||||||
|
|
||||||
|
const readUInt32 = isLittleEndian
|
||||||
|
? (offset: number) => buffer.readUInt32LE(offset)
|
||||||
|
: (offset: number) => buffer.readUInt32BE(offset);
|
||||||
|
|
||||||
|
// 获取第一个 IFD 的偏移量
|
||||||
|
const ifdOffset = readUInt32(4);
|
||||||
|
if (ifdOffset + 2 > buffer.length) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 IFD 条目数量
|
||||||
|
const numEntries = readUInt16(ifdOffset);
|
||||||
|
let width: number | undefined;
|
||||||
|
let height: number | undefined;
|
||||||
|
|
||||||
|
// TIFF 标签
|
||||||
|
const TAG_IMAGE_WIDTH = 0x0100;
|
||||||
|
const TAG_IMAGE_HEIGHT = 0x0101;
|
||||||
|
|
||||||
|
// 遍历 IFD 条目
|
||||||
|
for (let i = 0; i < numEntries; i++) {
|
||||||
|
const entryOffset = ifdOffset + 2 + i * 12;
|
||||||
|
if (entryOffset + 12 > buffer.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = readUInt16(entryOffset);
|
||||||
|
const type = readUInt16(entryOffset + 2);
|
||||||
|
// const count = readUInt32(entryOffset + 4);
|
||||||
|
|
||||||
|
// 根据类型读取值
|
||||||
|
let value: number;
|
||||||
|
if (type === 3) {
|
||||||
|
// SHORT (2 bytes)
|
||||||
|
value = readUInt16(entryOffset + 8);
|
||||||
|
} else if (type === 4) {
|
||||||
|
// LONG (4 bytes)
|
||||||
|
value = readUInt32(entryOffset + 8);
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag === TAG_IMAGE_WIDTH) {
|
||||||
|
width = value;
|
||||||
|
} else if (tag === TAG_IMAGE_HEIGHT) {
|
||||||
|
height = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width !== undefined && height !== undefined) {
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width !== undefined && height !== undefined) {
|
||||||
|
return { width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
packages/napcat-image-size/src/parser/WebpParser.ts
Normal file
90
packages/napcat-image-size/src/parser/WebpParser.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { ImageParser, ImageType, matchMagic, ImageSize } from '@/napcat-image-size/src';
|
||||||
|
import { ReadStream } from 'fs';
|
||||||
|
|
||||||
|
// WEBP解析器 - 完整支持VP8, VP8L, VP8X格式
|
||||||
|
export class WebpParser implements ImageParser {
|
||||||
|
readonly type = ImageType.WEBP;
|
||||||
|
// WEBP RIFF 头:52 49 46 46 (RIFF)
|
||||||
|
private readonly RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46];
|
||||||
|
// WEBP 魔术头:57 45 42 50 (WEBP)
|
||||||
|
private readonly WEBP_SIGNATURE = [0x57, 0x45, 0x42, 0x50];
|
||||||
|
|
||||||
|
// WEBP 块头
|
||||||
|
private readonly CHUNK_VP8 = [0x56, 0x50, 0x38, 0x20]; // "VP8 "
|
||||||
|
private readonly CHUNK_VP8L = [0x56, 0x50, 0x38, 0x4C]; // "VP8L"
|
||||||
|
private readonly CHUNK_VP8X = [0x56, 0x50, 0x38, 0x58]; // "VP8X"
|
||||||
|
|
||||||
|
canParse (buffer: Buffer): boolean {
|
||||||
|
return (
|
||||||
|
buffer.length >= 12 &&
|
||||||
|
matchMagic(buffer, this.RIFF_SIGNATURE, 0) &&
|
||||||
|
matchMagic(buffer, this.WEBP_SIGNATURE, 8)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isChunkType (buffer: Buffer, offset: number, chunkType: number[]): boolean {
|
||||||
|
return buffer.length >= offset + 4 && matchMagic(buffer, chunkType, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseSize (stream: ReadStream): Promise<ImageSize | undefined> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 需要读取足够的字节来检测所有三种格式
|
||||||
|
const MAX_HEADER_SIZE = 32;
|
||||||
|
let totalBytes = 0;
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
|
||||||
|
stream.on('error', reject);
|
||||||
|
|
||||||
|
stream.on('data', (chunk: Buffer | string) => {
|
||||||
|
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||||
|
buffer = Buffer.concat([buffer, chunkBuffer]);
|
||||||
|
totalBytes += chunk.length;
|
||||||
|
|
||||||
|
// 检查是否有足够的字节进行格式检测
|
||||||
|
if (totalBytes >= MAX_HEADER_SIZE) {
|
||||||
|
stream.destroy();
|
||||||
|
|
||||||
|
// 检查基本的WEBP签名
|
||||||
|
if (!this.canParse(buffer)) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查chunk头部,位于字节12-15
|
||||||
|
if (this.isChunkType(buffer, 12, this.CHUNK_VP8)) {
|
||||||
|
// VP8格式 - 标准WebP
|
||||||
|
// 宽度和高度在帧头中
|
||||||
|
const width = buffer.readUInt16LE(26) & 0x3FFF;
|
||||||
|
const height = buffer.readUInt16LE(28) & 0x3FFF;
|
||||||
|
return resolve({ width, height });
|
||||||
|
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8L)) {
|
||||||
|
// VP8L格式 - 无损WebP
|
||||||
|
// 1字节标记后是14位宽度和14位高度
|
||||||
|
const bits = buffer.readUInt32LE(21);
|
||||||
|
const width = 1 + (bits & 0x3FFF);
|
||||||
|
const height = 1 + ((bits >> 14) & 0x3FFF);
|
||||||
|
return resolve({ width, height });
|
||||||
|
} else if (this.isChunkType(buffer, 12, this.CHUNK_VP8X)) {
|
||||||
|
// VP8X格式 - 扩展WebP
|
||||||
|
// 24位宽度和高度(减去1)
|
||||||
|
if (buffer.length < 30) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
const width = 1 + ((buffer[24]! | (buffer[25]! << 8) | (buffer[26]! << 16)) & 0xFFFFFF);
|
||||||
|
const height = 1 + ((buffer[27]! | (buffer[28]! << 8) | (buffer[29]! << 16)) & 0xFFFFFF);
|
||||||
|
return resolve({ width, height });
|
||||||
|
} else {
|
||||||
|
// 未知的WebP子格式
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on('end', () => {
|
||||||
|
// 如果没有读到足够的字节
|
||||||
|
if (totalBytes < MAX_HEADER_SIZE) {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,18 @@ import { NapCatOneBot11Adapter, OB11Return } from '@/napcat-onebot/index';
|
|||||||
import { NetworkAdapterConfig } from '../config/config';
|
import { NetworkAdapterConfig } from '../config/config';
|
||||||
import { TSchema } from '@sinclair/typebox';
|
import { TSchema } from '@sinclair/typebox';
|
||||||
import { StreamPacket, StreamPacketBasic, StreamStatus } from './stream/StreamBasic';
|
import { StreamPacket, StreamPacketBasic, StreamStatus } from './stream/StreamBasic';
|
||||||
|
export const ActionExamples = {
|
||||||
|
Common: {
|
||||||
|
errors: [
|
||||||
|
{ code: 1400, description: '请求参数错误或业务逻辑执行失败' },
|
||||||
|
{ code: 1401, description: '权限不足' },
|
||||||
|
{ code: 1404, description: '资源不存在' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export class OB11Response {
|
export class OB11Response {
|
||||||
private static createResponse<T>(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
private static createResponse<T> (data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
retcode,
|
retcode,
|
||||||
@@ -19,11 +28,11 @@ export class OB11Response {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static res<T>(data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
static res<T> (data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||||
return this.createResponse(data, status, retcode, message, echo, useStream);
|
return this.createResponse(data, status, retcode, message, echo, useStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ok<T>(data: T, echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
static ok<T> (data: T, echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||||
return this.createResponse(data, 'ok', 0, '', echo, useStream);
|
return this.createResponse(data, 'ok', 0, '', echo, useStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,15 +41,22 @@ export class OB11Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export abstract class OneBotRequestToolkit {
|
export abstract class OneBotRequestToolkit {
|
||||||
abstract send<T>(packet: StreamPacket<T>): Promise<void>;
|
abstract send<T> (packet: StreamPacket<T>): Promise<void>;
|
||||||
}
|
}
|
||||||
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
||||||
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
private validate?: ValidateFunction<unknown> = undefined;
|
private validate?: ValidateFunction<unknown> = undefined;
|
||||||
payloadSchema?: TSchema = undefined;
|
payloadSchema?: TSchema = undefined;
|
||||||
|
returnSchema?: TSchema = undefined;
|
||||||
|
payloadExample?: unknown = undefined;
|
||||||
|
returnExample?: unknown = undefined;
|
||||||
|
actionSummary: string = '';
|
||||||
|
actionDescription: string = '';
|
||||||
|
actionTags: string[] = [];
|
||||||
obContext: NapCatOneBot11Adapter;
|
obContext: NapCatOneBot11Adapter;
|
||||||
useStream: boolean = false;
|
useStream: boolean = false;
|
||||||
|
errorExamples: Array<{ code: number, description: string; }> = ActionExamples.Common.errors;
|
||||||
|
|
||||||
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||||
this.obContext = obContext;
|
this.obContext = obContext;
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
export const ExtendsActionsExamples = {
|
||||||
|
OCRImage: {
|
||||||
|
payload: { image: 'image_id_123' },
|
||||||
|
response: { texts: [{ text: '识别内容', coordinates: [] }] },
|
||||||
|
},
|
||||||
|
GetAiCharacters: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
characters: [
|
||||||
|
{ character_id: 'id', character_name: 'name', preview_url: 'url' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
GetClientkey: {
|
||||||
|
payload: {},
|
||||||
|
response: { clientkey: 'abcdef123456' },
|
||||||
|
},
|
||||||
|
SetQQAvatar: {
|
||||||
|
payload: { file: 'base64://...' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupKickMembers: {
|
||||||
|
payload: { group_id: '123456', user_id: ['123456789'], reject_add_request: false },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
TranslateEnWordToZn: {
|
||||||
|
payload: { words: ['hello'] },
|
||||||
|
response: { words: ['你好'] },
|
||||||
|
},
|
||||||
|
GetRkey: {
|
||||||
|
payload: {},
|
||||||
|
response: { rkey: '...' },
|
||||||
|
},
|
||||||
|
SetLongNick: {
|
||||||
|
payload: { longNick: '个性签名' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetSpecialTitle: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789', special_title: '头衔' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
22
packages/napcat-onebot/action/example/FileActionsExamples.ts
Normal file
22
packages/napcat-onebot/action/example/FileActionsExamples.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export const FileActionsExamples = {
|
||||||
|
GetFile: {
|
||||||
|
payload: { file: 'file_id_123' },
|
||||||
|
response: { file: '/path/to/file', url: 'http://...', file_size: 1024, file_name: 'test.jpg' },
|
||||||
|
},
|
||||||
|
GetGroupFileUrl: {
|
||||||
|
payload: { group_id: '123456', file_id: 'file_id_123', busid: 102 },
|
||||||
|
response: { url: 'http://...' },
|
||||||
|
},
|
||||||
|
GetImage: {
|
||||||
|
payload: { file: 'image_id_123' },
|
||||||
|
response: { file: '/path/to/image', url: 'http://...' },
|
||||||
|
},
|
||||||
|
GetPrivateFileUrl: {
|
||||||
|
payload: { user_id: '123456789', file_id: 'file_id_123' },
|
||||||
|
response: { url: 'http://...' },
|
||||||
|
},
|
||||||
|
GetRecord: {
|
||||||
|
payload: { file: 'record_id_123', out_format: 'mp3' },
|
||||||
|
response: { file: '/path/to/record', url: 'http://...' },
|
||||||
|
},
|
||||||
|
};
|
||||||
102
packages/napcat-onebot/action/example/GoCQHTTPActionsExamples.ts
Normal file
102
packages/napcat-onebot/action/example/GoCQHTTPActionsExamples.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
export const GoCQHTTPActionsExamples = {
|
||||||
|
GetStrangerInfo: {
|
||||||
|
payload: { user_id: '123456789' },
|
||||||
|
response: { user_id: 123456789, nickname: '昵称', sex: 'unknown' },
|
||||||
|
},
|
||||||
|
GetGroupHonorInfo: {
|
||||||
|
payload: { group_id: '123456', type: 'all' },
|
||||||
|
response: { group_id: 123456, current_talkative: {}, talkative_list: [] },
|
||||||
|
},
|
||||||
|
GetForwardMsg: {
|
||||||
|
payload: { message_id: '123456' },
|
||||||
|
response: { messages: [] },
|
||||||
|
},
|
||||||
|
SendForwardMsg: {
|
||||||
|
payload: { group_id: '123456', messages: [] },
|
||||||
|
response: { message_id: 123456 },
|
||||||
|
},
|
||||||
|
GetGroupAtAllRemain: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: { can_at_all: true, remain_at_all_count_for_group: 10, remain_at_all_count_for_self: 10 },
|
||||||
|
},
|
||||||
|
CreateGroupFileFolder: {
|
||||||
|
payload: { group_id: '123456', name: '测试目录' },
|
||||||
|
response: { result: {}, groupItem: {} },
|
||||||
|
},
|
||||||
|
DeleteGroupFile: {
|
||||||
|
payload: { group_id: '123456', file_id: 'file_uuid_123' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
DeleteGroupFileFolder: {
|
||||||
|
payload: { group_id: '123456', folder_id: 'folder_uuid_123' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
DownloadFile: {
|
||||||
|
payload: { url: 'https://example.com/file.png', thread_count: 1, headers: 'User-Agent: NapCat' },
|
||||||
|
response: { file: '/path/to/downloaded/file' },
|
||||||
|
},
|
||||||
|
GetFriendMsgHistory: {
|
||||||
|
payload: { user_id: '123456789', message_seq: 0, count: 20 },
|
||||||
|
response: { messages: [] },
|
||||||
|
},
|
||||||
|
GetGroupFilesByFolder: {
|
||||||
|
payload: { group_id: '123456', folder_id: 'folder_id' },
|
||||||
|
response: { files: [], folders: [] },
|
||||||
|
},
|
||||||
|
GetGroupFileSystemInfo: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: { file_count: 10, limit_count: 10000, used_space: 1024, total_space: 10737418240 },
|
||||||
|
},
|
||||||
|
GetGroupMsgHistory: {
|
||||||
|
payload: { group_id: '123456', message_seq: 0, count: 20 },
|
||||||
|
response: { messages: [] },
|
||||||
|
},
|
||||||
|
GetGroupRootFiles: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: { files: [], folders: [] },
|
||||||
|
},
|
||||||
|
GetOnlineClient: {
|
||||||
|
payload: { no_cache: false },
|
||||||
|
response: [],
|
||||||
|
},
|
||||||
|
GoCQHTTPCheckUrlSafely: {
|
||||||
|
payload: { url: 'https://example.com' },
|
||||||
|
response: { level: 1 },
|
||||||
|
},
|
||||||
|
GoCQHTTPDeleteFriend: {
|
||||||
|
payload: { user_id: '123456789' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
GoCQHTTPGetModelShow: {
|
||||||
|
payload: { model: 'iPhone 13' },
|
||||||
|
response: { variants: [] },
|
||||||
|
},
|
||||||
|
GoCQHTTPSetModelShow: {
|
||||||
|
payload: { model: 'iPhone 13', model_show: 'iPhone 13' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
QuickAction: {
|
||||||
|
payload: { context: {}, operation: {} },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
SendGroupNotice: {
|
||||||
|
payload: { group_id: '123456', content: '公告内容', image: 'base64://...' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
SetGroupPortrait: {
|
||||||
|
payload: { group_id: '123456', file: 'base64://...' },
|
||||||
|
response: { result: 0, errMsg: '' },
|
||||||
|
},
|
||||||
|
SetQQProfile: {
|
||||||
|
payload: { nickname: '新昵称', personal_note: '个性签名' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
UploadGroupFile: {
|
||||||
|
payload: { group_id: '123456', file: '/path/to/file', name: 'test.txt' },
|
||||||
|
response: { file_id: 'file_uuid_123' },
|
||||||
|
},
|
||||||
|
UploadPrivateFile: {
|
||||||
|
payload: { user_id: '123456789', file: '/path/to/file', name: 'test.txt' },
|
||||||
|
response: { file_id: 'file_uuid_123' },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
export const GroupActionsExamples = {
|
||||||
|
DelEssenceMsg: {
|
||||||
|
payload: { message_id: 123456 },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
DelGroupNotice: {
|
||||||
|
payload: { group_id: '123456', notice_id: 'notice_123' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
GetGroupDetailInfo: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: { group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 },
|
||||||
|
},
|
||||||
|
GetGroupEssence: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: [{ message_id: 123456, sender_id: 123456, sender_nick: '昵称', operator_id: 123456, operator_nick: '昵称', operator_time: 1710000000, content: '精华内容' }],
|
||||||
|
},
|
||||||
|
GetGroupInfo: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: { group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 },
|
||||||
|
},
|
||||||
|
GetGroupList: {
|
||||||
|
payload: {},
|
||||||
|
response: [{ group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 }],
|
||||||
|
},
|
||||||
|
GetGroupMemberInfo: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789' },
|
||||||
|
response: { group_id: 123456, user_id: 123456789, nickname: '昵称', card: '名片', role: 'member' },
|
||||||
|
},
|
||||||
|
GetGroupMemberList: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: [{ group_id: 123456, user_id: 123456789, nickname: '昵称', card: '名片', role: 'member' }],
|
||||||
|
},
|
||||||
|
GetGroupNotice: {
|
||||||
|
payload: { group_id: '123456' },
|
||||||
|
response: [{ notice_id: 'notice_123', sender_id: 123456, publish_time: 1710000000, message: { text: '公告内容', image: [] } }],
|
||||||
|
},
|
||||||
|
SendGroupMsg: {
|
||||||
|
payload: { group_id: '123456', message: 'hello' },
|
||||||
|
response: { message_id: 123456 },
|
||||||
|
},
|
||||||
|
SetEssenceMsg: {
|
||||||
|
payload: { message_id: 123456 },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupAddRequest: {
|
||||||
|
payload: { flag: 'flag_123', sub_type: 'add', approve: true },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupAdmin: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789', enable: true },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupBan: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789', duration: 1800 },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupCard: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789', card: '新名片' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupKick: {
|
||||||
|
payload: { group_id: '123456', user_id: '123456789', reject_add_request: false },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupLeave: {
|
||||||
|
payload: { group_id: '123456', is_dismiss: false },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupName: {
|
||||||
|
payload: { group_id: '123456', group_name: '新群名' },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
SetGroupWholeBan: {
|
||||||
|
payload: { group_id: '123456', enable: true },
|
||||||
|
response: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export const GuildActionsExamples = {
|
||||||
|
GetGuildList: {
|
||||||
|
payload: {},
|
||||||
|
response: [{ guild_id: '123456', guild_name: '测试频道' }],
|
||||||
|
},
|
||||||
|
GetGuildProfile: {
|
||||||
|
payload: { guild_id: '123456' },
|
||||||
|
response: { guild_id: '123456', guild_name: '测试频道', guild_display_id: '123' },
|
||||||
|
},
|
||||||
|
};
|
||||||
10
packages/napcat-onebot/action/example/NewActionsExamples.ts
Normal file
10
packages/napcat-onebot/action/example/NewActionsExamples.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export const NewActionsExamples = {
|
||||||
|
GetDoubtFriendsAddRequest: {
|
||||||
|
payload: { count: 10 },
|
||||||
|
response: [{ user_id: 123456789, nickname: '昵称', age: 20, sex: 'male', reason: '申请理由', flag: 'flag_123' }],
|
||||||
|
},
|
||||||
|
SetDoubtFriendsAddRequest: {
|
||||||
|
payload: { flag: 'flag_123', approve: true },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const PacketActionsExamples = {
|
||||||
|
GetPacketStatus: {
|
||||||
|
payload: {},
|
||||||
|
response: { status: 'ok' },
|
||||||
|
},
|
||||||
|
SendPoke: {
|
||||||
|
payload: { user_id: '123456789' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
SetGroupTodo: {
|
||||||
|
payload: { group_id: '123456', message_id: '123456789' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
export const SystemActionsExamples = {
|
||||||
|
CanSendImage: {
|
||||||
|
payload: {},
|
||||||
|
response: { yes: true },
|
||||||
|
},
|
||||||
|
CanSendRecord: {
|
||||||
|
payload: {},
|
||||||
|
response: { yes: true },
|
||||||
|
},
|
||||||
|
CleanCache: {
|
||||||
|
payload: {},
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
GetCredentials: {
|
||||||
|
payload: {},
|
||||||
|
response: { cookies: '...', csrf_token: 123456789 },
|
||||||
|
},
|
||||||
|
GetCSRF: {
|
||||||
|
payload: {},
|
||||||
|
response: { token: 123456789 },
|
||||||
|
},
|
||||||
|
GetLoginInfo: {
|
||||||
|
payload: {},
|
||||||
|
response: { user_id: 123456789, nickname: '机器人' },
|
||||||
|
},
|
||||||
|
GetStatus: {
|
||||||
|
payload: {},
|
||||||
|
response: { online: true, good: true },
|
||||||
|
},
|
||||||
|
GetSystemMsg: {
|
||||||
|
payload: {},
|
||||||
|
response: { invited_requests: [], join_requests: [] },
|
||||||
|
},
|
||||||
|
GetVersionInfo: {
|
||||||
|
payload: {},
|
||||||
|
response: { app_name: 'NapCatQQ', app_version: '1.0.0', protocol_version: 'v11' },
|
||||||
|
},
|
||||||
|
SetRestart: {
|
||||||
|
payload: { delay: 0 },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
38
packages/napcat-onebot/action/example/UserActionsExamples.ts
Normal file
38
packages/napcat-onebot/action/example/UserActionsExamples.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export const UserActionsExamples = {
|
||||||
|
GetCookies: {
|
||||||
|
payload: { domain: 'qun.qq.com' },
|
||||||
|
response: { cookies: 'p_skey=xxx; p_uin=o0123456789;' },
|
||||||
|
},
|
||||||
|
GetFriendList: {
|
||||||
|
payload: {},
|
||||||
|
response: [{ user_id: 123456789, nickname: '昵称', remark: '备注' }],
|
||||||
|
},
|
||||||
|
GetRecentContact: {
|
||||||
|
payload: { count: 10 },
|
||||||
|
response: [
|
||||||
|
{
|
||||||
|
lastestMsg: 'hello',
|
||||||
|
peerUin: '123456789',
|
||||||
|
remark: 'remark',
|
||||||
|
msgTime: '1710000000',
|
||||||
|
chatType: 1,
|
||||||
|
msgId: '12345',
|
||||||
|
sendNickName: 'nick',
|
||||||
|
sendMemberName: 'card',
|
||||||
|
peerName: 'name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SendLike: {
|
||||||
|
payload: { user_id: '123456789', times: 10 },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
SetFriendAddRequest: {
|
||||||
|
payload: { flag: 'flag_123', approve: true, remark: '好友' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
SetFriendRemark: {
|
||||||
|
payload: { user_id: '123456789', remark: '新备注' },
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { OneBotAction } from '../OneBotAction';
|
import { OneBotAction } from '../OneBotAction';
|
||||||
|
import { Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
export class BotExit extends OneBotAction<void, void> {
|
export class BotExit extends OneBotAction<void, void> {
|
||||||
override actionName = ActionName.Exit;
|
override actionName = ActionName.Exit;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = Type.Object({});
|
||||||
|
override actionSummary = '退出登录';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = null;
|
||||||
|
|
||||||
async _handle () {
|
async _handle () {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
@@ -2,21 +2,36 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
|||||||
import { OneBotAction } from '../OneBotAction';
|
import { OneBotAction } from '../OneBotAction';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
bot_appid: Type.String(),
|
bot_appid: Type.String({ description: '机器人AppID' }),
|
||||||
button_id: Type.String({ default: '' }),
|
button_id: Type.String({ default: '', description: '按钮ID' }),
|
||||||
callback_data: Type.String({ default: '' }),
|
callback_data: Type.String({ default: '', description: '回调数据' }),
|
||||||
msg_seq: Type.String({ default: '10086' }),
|
msg_seq: Type.String({ default: '10086', description: '消息序列号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class ClickInlineKeyboardButton extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '点击结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class ClickInlineKeyboardButton extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.ClickInlineKeyboardButton;
|
override actionName = ActionName.ClickInlineKeyboardButton;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
async _handle (payload: Payload) {
|
override actionSummary = '点击内联键盘按钮';
|
||||||
|
override actionTags = ['消息扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
bot_appid: '1234567890',
|
||||||
|
button_id: 'btn_1',
|
||||||
|
callback_data: '',
|
||||||
|
msg_seq: '10086'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
};
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.MsgApi.clickInlineKeyboardButton({
|
return await this.core.apis.MsgApi.clickInlineKeyboardButton({
|
||||||
buttonId: payload.button_id,
|
buttonId: payload.button_id,
|
||||||
peerId: payload.group_id.toString(),
|
peerId: payload.group_id.toString(),
|
||||||
|
|||||||
@@ -2,18 +2,33 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Type, Static } from '@sinclair/typebox';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
rawData: Type.String(),
|
rawData: Type.String({ description: '原始数据' }),
|
||||||
brief: Type.String(),
|
brief: Type.String({ description: '简要描述' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class CreateCollection extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '创建结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class CreateCollection extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.CreateCollection;
|
override actionName = ActionName.CreateCollection;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '创建收藏';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = {
|
||||||
|
rawData: '收藏内容',
|
||||||
|
brief: '收藏标题'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
result: 0,
|
||||||
|
errMsg: ''
|
||||||
|
};
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.CollectionApi.createCollection(
|
return await this.core.apis.CollectionApi.createCollection(
|
||||||
this.core.selfInfo.uin,
|
this.core.selfInfo.uin,
|
||||||
this.core.selfInfo.uid,
|
this.core.selfInfo.uid,
|
||||||
|
|||||||
@@ -2,19 +2,34 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
album_id: Type.String(),
|
album_id: Type.String({ description: '相册ID' }),
|
||||||
lloc: Type.String(),
|
lloc: Type.String({ description: '媒体ID (lloc)' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class DelGroupAlbumMedia extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '删除结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class DelGroupAlbumMedia extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.DelGroupAlbumMedia;
|
override actionName = ActionName.DelGroupAlbumMedia;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '删除群相册媒体';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
album_id: 'album_id_1',
|
||||||
|
lloc: 'media_id_1',
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
result: {}
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ(
|
return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ(
|
||||||
payload.group_id,
|
payload.group_id,
|
||||||
payload.album_id,
|
payload.album_id,
|
||||||
|
|||||||
@@ -2,20 +2,32 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
export const DoGroupAlbumCommentPayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
album_id: Type.String(),
|
album_id: Type.String({ description: '相册 ID' }),
|
||||||
lloc: Type.String(),
|
lloc: Type.String({ description: '图片 ID' }),
|
||||||
content: Type.String(),
|
content: Type.String({ description: '评论内容' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
export type DoGroupAlbumCommentPayload = Static<typeof DoGroupAlbumCommentPayloadSchema>;
|
||||||
|
|
||||||
export class DoGroupAlbumComment extends OneBotAction<Payload, unknown> {
|
export class DoGroupAlbumComment extends OneBotAction<DoGroupAlbumCommentPayload, any> {
|
||||||
override actionName = ActionName.DoGroupAlbumComment;
|
override actionName = ActionName.DoGroupAlbumComment;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '发表群相册评论';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
album_id: 'album_id_1',
|
||||||
|
lloc: 'media_id_1',
|
||||||
|
content: '很有意思'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
result: {}
|
||||||
|
};
|
||||||
|
override payloadSchema = DoGroupAlbumCommentPayloadSchema;
|
||||||
|
override returnSchema = Type.Any({ description: '评论结果' });
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: DoGroupAlbumCommentPayload) {
|
||||||
return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ(
|
return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ(
|
||||||
payload.group_id,
|
payload.group_id,
|
||||||
payload.album_id,
|
payload.album_id,
|
||||||
|
|||||||
@@ -2,18 +2,32 @@ import { Type, Static } from '@sinclair/typebox';
|
|||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
count: Type.Union([Type.Number(), Type.String()], { default: 48 }),
|
count: Type.Union([Type.Number(), Type.String()], { default: 48, description: '获取数量' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class FetchCustomFace extends OneBotAction<Payload, string[]> {
|
const ReturnSchema = Type.Array(Type.String(), { description: '表情URL列表' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class FetchCustomFace extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.FetchCustomFace;
|
override actionName = ActionName.FetchCustomFace;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取自定义表情';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
count: 10
|
||||||
|
};
|
||||||
|
override returnExample = [
|
||||||
|
'http://example.com/face1.png'
|
||||||
|
];
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count);
|
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count);
|
||||||
return ret.emojiInfoList.map(e => e.url);
|
return ret.emojiInfoList.map(e => e.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,68 @@ import { Type, Static } from '@sinclair/typebox';
|
|||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { MessageUnique } from 'napcat-common/src/message-unique';
|
import { MessageUnique } from 'napcat-common/src/message-unique';
|
||||||
import { type NTQQMsgApi } from 'napcat-core/apis';
|
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
message_id: Type.Union([Type.Number(), Type.String()]),
|
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||||
emojiId: Type.Union([Type.Number(), Type.String()]),
|
emojiId: Type.Union([Type.Number(), Type.String()], { description: '表情ID' }),
|
||||||
emojiType: Type.Union([Type.Number(), Type.String()]),
|
emojiType: Type.Union([Type.Number(), Type.String()], { description: '表情类型' }),
|
||||||
count: Type.Union([Type.Number(), Type.String()], { default: 20 }),
|
count: Type.Union([Type.Number(), Type.String()], { default: 20, description: '获取数量' }),
|
||||||
cookie: Type.String({ default: '' })
|
cookie: Type.String({ default: '', description: '分页Cookie' })
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class FetchEmojiLike extends OneBotAction<Payload, Awaited<ReturnType<NTQQMsgApi['getMsgEmojiLikesList']>>> {
|
const ReturnSchema = Type.Object({
|
||||||
|
emojiLikesList: Type.Array(Type.Object({
|
||||||
|
tinyId: Type.String({ description: 'TinyID' }),
|
||||||
|
nickName: Type.String({ description: '昵称' }),
|
||||||
|
headUrl: Type.String({ description: '头像URL' }),
|
||||||
|
}), { description: '表情回应列表' }),
|
||||||
|
cookie: Type.String({ description: '分页Cookie' }),
|
||||||
|
isLastPage: Type.Boolean({ description: '是否最后一页' }),
|
||||||
|
isFirstPage: Type.Boolean({ description: '是否第一页' }),
|
||||||
|
result: Type.Number({ description: '结果状态码' }),
|
||||||
|
errMsg: Type.String({ description: '错 误信息' }),
|
||||||
|
}, { description: '表情回应详情' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class FetchEmojiLike extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.FetchEmojiLike;
|
override actionName = ActionName.FetchEmojiLike;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '获取表情点赞详情';
|
||||||
|
override actionTags = ['消息扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
message_id: 12345,
|
||||||
|
emojiId: '123',
|
||||||
|
emojiType: 1,
|
||||||
|
count: 10,
|
||||||
|
cookie: ''
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
emojiLikesList: [
|
||||||
|
{
|
||||||
|
tinyId: '123456',
|
||||||
|
nickName: '测试用户',
|
||||||
|
headUrl: 'http://example.com/avatar.png'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cookie: '',
|
||||||
|
isLastPage: true,
|
||||||
|
isFirstPage: true,
|
||||||
|
result: 0,
|
||||||
|
errMsg: ''
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||||
if (!msgIdPeer) throw new Error('消息不存在');
|
if (!msgIdPeer) throw new Error('消息不存在');
|
||||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
||||||
if (!msg) throw new Error('消息不存在');
|
if (!msg) throw new Error('消息不存在');
|
||||||
return await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||||
msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), payload.cookie, +payload.count
|
msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), payload.cookie, +payload.count
|
||||||
);
|
);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,46 @@
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { AIVoiceChatType } from 'napcat-core/packet/entities/aiChat';
|
|
||||||
import { Type, Static } from '@sinclair/typebox';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
|
||||||
chat_type: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
const PayloadSchema = Type.Object({
|
||||||
|
group_id: Type.String({ description: '群号' }),
|
||||||
|
chat_type: Type.Union([Type.Number(), Type.String()], { default: 1, description: '聊天类型' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
interface GetAiCharactersResponse {
|
const ReturnSchema = Type.Array(
|
||||||
type: string;
|
Type.Object({
|
||||||
characters: {
|
type: Type.String({ description: '角色类型' }),
|
||||||
character_id: string;
|
characters: Type.Array(
|
||||||
character_name: string;
|
Type.Object({
|
||||||
preview_url: string;
|
character_id: Type.String({ description: '角色ID' }),
|
||||||
}[];
|
character_name: Type.String({ description: '角色名称' }),
|
||||||
}
|
preview_url: Type.String({ description: '预览URL' }),
|
||||||
|
}),
|
||||||
|
{ description: '角色列表' }
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
{ description: 'AI角色列表' }
|
||||||
|
);
|
||||||
|
|
||||||
export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharactersResponse[]> {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetAiCharacters extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetAiCharacters;
|
override actionName = ActionName.GetAiCharacters;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取AI角色列表';
|
||||||
|
override actionDescription = '获取群聊中的AI角色列表';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.GetAiCharacters.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.GetAiCharacters.response;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +payload.chat_type as AIVoiceChatType);
|
const chatTypeNum = Number(payload.chat_type);
|
||||||
|
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, chatTypeNum);
|
||||||
return rawList?.map((item) => ({
|
return rawList?.map((item) => ({
|
||||||
type: item.category,
|
type: item.category,
|
||||||
characters: item.voices.map((voice) => ({
|
characters: item.voices.map((voice) => ({
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { OneBotAction } from '../OneBotAction';
|
import { OneBotAction } from '../OneBotAction';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface GetClientkeyResponse {
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
clientkey?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetClientkey extends OneBotAction<void, GetClientkeyResponse> {
|
const ReturnSchema = Type.Object({
|
||||||
|
clientkey: Type.Optional(Type.String({ description: '客户端Key' })),
|
||||||
|
}, { description: '获取ClientKey结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetClientkey extends OneBotAction<void, ReturnType> {
|
||||||
override actionName = ActionName.GetClientkey;
|
override actionName = ActionName.GetClientkey;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取ClientKey';
|
||||||
|
override actionDescription = '获取当前登录帐号的ClientKey';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.GetClientkey.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.GetClientkey.response;
|
||||||
|
|
||||||
async _handle () {
|
async _handle () {
|
||||||
return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey };
|
return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey };
|
||||||
|
|||||||
@@ -1,20 +1,81 @@
|
|||||||
import { type NTQQCollectionApi } from 'napcat-core/apis/collection';
|
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Type, Static } from '@sinclair/typebox';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
category: Type.Union([Type.Number(), Type.String()]),
|
category: Type.String({ description: '分类ID' }),
|
||||||
count: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
count: Type.String({ default: '50', description: '获取数量' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetCollectionList extends OneBotAction<Payload, Awaited<ReturnType<NTQQCollectionApi['getAllCollection']>>> {
|
const ReturnSchema = Type.Any({ description: '收藏列表' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetCollectionList extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetCollectionList;
|
override actionName = ActionName.GetCollectionList;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取收藏列表';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
category: '0',
|
||||||
|
count: '50'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
errCode: 0,
|
||||||
|
errMsg: "",
|
||||||
|
collectionSearchList: {
|
||||||
|
collectionItemList: [
|
||||||
|
{
|
||||||
|
cid: "123456",
|
||||||
|
type: 8,
|
||||||
|
status: 1,
|
||||||
|
author: {
|
||||||
|
type: 2,
|
||||||
|
numId: "123456",
|
||||||
|
strId: "昵称",
|
||||||
|
groupId: "123456",
|
||||||
|
groupName: "群名",
|
||||||
|
uid: "123456"
|
||||||
|
},
|
||||||
|
bid: 1,
|
||||||
|
category: 1,
|
||||||
|
createTime: "1769169157000",
|
||||||
|
collectTime: "1769413477691",
|
||||||
|
modifyTime: "1769413477691",
|
||||||
|
sequence: "1769413476735",
|
||||||
|
shareUrl: "",
|
||||||
|
customGroupId: 0,
|
||||||
|
securityBeat: false,
|
||||||
|
summary: {
|
||||||
|
textSummary: null,
|
||||||
|
linkSummary: null,
|
||||||
|
gallerySummary: null,
|
||||||
|
audioSummary: null,
|
||||||
|
videoSummary: null,
|
||||||
|
fileSummary: null,
|
||||||
|
locationSummary: null,
|
||||||
|
richMediaSummary: {
|
||||||
|
title: "",
|
||||||
|
subTitle: "",
|
||||||
|
brief: "text",
|
||||||
|
picList: [],
|
||||||
|
contentType: 1,
|
||||||
|
originalUri: "",
|
||||||
|
publisher: "",
|
||||||
|
richMediaVersion: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
hasMore: false,
|
||||||
|
bottomTimeStamp: "1769413477691"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count);
|
return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
88
packages/napcat-onebot/action/extends/GetEmojiLikes.ts
Normal file
88
packages/napcat-onebot/action/extends/GetEmojiLikes.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
|
import { MessageUnique } from 'napcat-common/src/message-unique';
|
||||||
|
import { Peer, ChatType } from '@/napcat-core';
|
||||||
|
|
||||||
|
const PayloadSchema = Type.Object({
|
||||||
|
group_id: Type.Optional(Type.String({ description: '群号,短ID可不传' })),
|
||||||
|
message_id: Type.String({ description: '消息ID,可以传递长ID或短ID' }),
|
||||||
|
emoji_id: Type.String({ description: '表情ID' }),
|
||||||
|
emoji_type: Type.Optional(Type.String({ description: '表情类型' })),
|
||||||
|
count: Type.Number({ default: 0, description: '数量,0代表全部' }),
|
||||||
|
});
|
||||||
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
|
const ReturnSchema = Type.Object({
|
||||||
|
emoji_like_list: Type.Array(
|
||||||
|
Type.Object({
|
||||||
|
user_id: Type.String({ description: '点击者QQ号' }),
|
||||||
|
nick_name: Type.String({ description: '昵称?' }),
|
||||||
|
}),
|
||||||
|
{ description: '表情回应列表' }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetEmojiLikes extends OneBotAction<PayloadType, ReturnType> {
|
||||||
|
override actionName = ActionName.GetEmojiLikes;
|
||||||
|
override actionSummary = '获取消息表情点赞列表';
|
||||||
|
override actionTags = ['消息扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
message_id: '12345',
|
||||||
|
emoji_id: '123'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
emoji_like_list: [
|
||||||
|
{
|
||||||
|
user_id: '654321',
|
||||||
|
nick_name: '测试用户'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
|
let peer: Peer;
|
||||||
|
let msgId: string;
|
||||||
|
|
||||||
|
if (MessageUnique.isShortId(payload.message_id)) {
|
||||||
|
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||||
|
if (!msgIdPeer) throw new Error('消息不存在');
|
||||||
|
peer = msgIdPeer.Peer;
|
||||||
|
msgId = msgIdPeer.MsgId;
|
||||||
|
} else {
|
||||||
|
if (!payload.group_id) throw new Error('长ID模式下必须提供群号');
|
||||||
|
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id };
|
||||||
|
msgId = payload.message_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId])).msgList[0];
|
||||||
|
if (!msg) throw new Error('消息不存在');
|
||||||
|
|
||||||
|
const emojiType = payload.emoji_type ?? (payload.emoji_id.length > 3 ? '2' : '1');
|
||||||
|
const emojiLikeList: Array<{ user_id: string; nick_name: string; }> = [];
|
||||||
|
let cookie = '';
|
||||||
|
let needFetchCount = payload.count == 0 ? 200 : Math.ceil(payload.count / 15);
|
||||||
|
for (let page = 0; page < needFetchCount; page++) {
|
||||||
|
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||||
|
peer, msg.msgSeq, payload.emoji_id.toString(), emojiType, cookie, 15
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Array.isArray(res.emojiLikesList)) {
|
||||||
|
for (const like of res.emojiLikesList) {
|
||||||
|
emojiLikeList.push({ user_id: like.tinyId, nick_name: like.nickName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.isLastPage || !res.cookie) break;
|
||||||
|
cookie = res.cookie;
|
||||||
|
}
|
||||||
|
// 切断多余部分
|
||||||
|
if (payload.count > 0) {
|
||||||
|
emojiLikeList.splice(payload.count);
|
||||||
|
}
|
||||||
|
return { emoji_like_list: emojiLikeList };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,40 @@
|
|||||||
import { OB11Construct } from '@/napcat-onebot/helper/data';
|
import { OB11Construct } from '@/napcat-onebot/helper/data';
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
import { OB11UserSchema } from '../schemas';
|
||||||
|
|
||||||
export class GetFriendWithCategory extends OneBotAction<void, unknown> {
|
const ReturnSchema = Type.Array(
|
||||||
|
Type.Object({
|
||||||
|
categoryId: Type.Number({ description: '分组ID' }),
|
||||||
|
categoryName: Type.String({ description: '分组名称' }),
|
||||||
|
categoryMbCount: Type.Number({ description: '分组内好友数量' }),
|
||||||
|
buddyList: Type.Array(OB11UserSchema, { description: '好友列表' }),
|
||||||
|
}),
|
||||||
|
{ description: '带分组的好友列表' }
|
||||||
|
);
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetFriendWithCategory extends OneBotAction<void, ReturnType> {
|
||||||
override actionName = ActionName.GetFriendsWithCategory;
|
override actionName = ActionName.GetFriendsWithCategory;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取带分组的好友列表';
|
||||||
|
override actionTags = ['用户扩展'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = [
|
||||||
|
{
|
||||||
|
categoryId: 1,
|
||||||
|
categoryName: '我的好友',
|
||||||
|
categoryMbCount: 1,
|
||||||
|
buddyList: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
async _handle () {
|
async _handle () {
|
||||||
return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({
|
const categories = await this.core.apis.FriendApi.getBuddyV2ExWithCate();
|
||||||
|
return categories.map(category => ({
|
||||||
...category,
|
...category,
|
||||||
buddyList: OB11Construct.friends(category.buddyList),
|
buddyList: OB11Construct.friends(category.buddyList),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,16 +1,51 @@
|
|||||||
import { GroupNotifyMsgStatus } from 'napcat-core';
|
import { GroupNotifyMsgStatus } from 'napcat-core';
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Notify } from '@/napcat-onebot/types';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
export default class GetGroupAddRequest extends OneBotAction<null, Notify[] | null> {
|
const ReturnSchema = Type.Array(
|
||||||
|
Type.Object({
|
||||||
|
request_id: Type.Number({ description: '请求ID' }),
|
||||||
|
invitor_uin: Type.Number({ description: '邀请者QQ' }),
|
||||||
|
invitor_nick: Type.Optional(Type.String({ description: '邀请者昵称' })),
|
||||||
|
group_id: Type.Number({ description: '群号' }),
|
||||||
|
message: Type.Optional(Type.String({ description: '验证信息' })),
|
||||||
|
group_name: Type.Optional(Type.String({ description: '群名称' })),
|
||||||
|
checked: Type.Boolean({ description: '是否已处理' }),
|
||||||
|
actor: Type.Number({ description: '处理者QQ' }),
|
||||||
|
requester_nick: Type.Optional(Type.String({ description: '请求者昵称' })),
|
||||||
|
}),
|
||||||
|
{ description: '群通知列表' }
|
||||||
|
);
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class GetGroupAddRequest extends OneBotAction<void, ReturnType> {
|
||||||
override actionName = ActionName.GetGroupIgnoreAddRequest;
|
override actionName = ActionName.GetGroupIgnoreAddRequest;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取群被忽略的加群请求';
|
||||||
|
override actionTags = ['群组接口'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = [
|
||||||
|
{
|
||||||
|
request_id: 12345,
|
||||||
|
invitor_uin: 123456789,
|
||||||
|
invitor_nick: '邀请者',
|
||||||
|
group_id: 123456789,
|
||||||
|
message: '加群请求',
|
||||||
|
group_name: '群名称',
|
||||||
|
checked: false,
|
||||||
|
actor: 0,
|
||||||
|
requester_nick: '请求者'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
async _handle (): Promise<Notify[] | null> {
|
async _handle (): Promise<ReturnType> {
|
||||||
const NTQQUserApi = this.core.apis.UserApi;
|
const NTQQUserApi = this.core.apis.UserApi;
|
||||||
const NTQQGroupApi = this.core.apis.GroupApi;
|
const NTQQGroupApi = this.core.apis.GroupApi;
|
||||||
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
|
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
|
||||||
const retData: Notify[] = [];
|
const retData: ReturnType = [];
|
||||||
|
|
||||||
const notifyPromises = ignoredNotifies
|
const notifyPromises = ignoredNotifies
|
||||||
.filter(notify => notify.type === 7)
|
.filter(notify => notify.type === 7)
|
||||||
|
|||||||
@@ -2,19 +2,35 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
album_id: Type.String(),
|
album_id: Type.String({ description: '相册ID' }),
|
||||||
attach_info: Type.String({ default: '' }),
|
attach_info: Type.String({ default: '', description: '附加信息(用于分页)' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetGroupAlbumMediaList extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '相册媒体列表' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetGroupAlbumMediaList extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetGroupAlbumMediaList;
|
override actionName = ActionName.GetGroupAlbumMediaList;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '获取群相册媒体列表';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
album_id: 'album_id_1',
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
media_list: [
|
||||||
|
{ media_id: 'media_id_1', url: 'http://example.com/1.jpg' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.WebApi.getAlbumMediaListByNTQQ(
|
return await this.core.apis.WebApi.getAlbumMediaListByNTQQ(
|
||||||
payload.group_id,
|
payload.group_id,
|
||||||
payload.album_id,
|
payload.album_id,
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Type, Static } from '@sinclair/typebox';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetGroupInfoEx extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '群扩展信息' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetGroupInfoEx extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetGroupInfoEx;
|
override actionName = ActionName.GetGroupInfoEx;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '获取群详细信息 (扩展)';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return (await this.core.apis.GroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString());
|
return (await this.core.apis.GroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,79 @@
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { MiniAppInfo, MiniAppInfoHelper } from 'napcat-core/packet/utils/helper/miniAppHelper';
|
import { MiniAppInfo, MiniAppInfoHelper } from 'napcat-core/packet/utils/helper/miniAppHelper';
|
||||||
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from 'napcat-core/packet/entities/miniApp';
|
import { MiniAppReqCustomParams, MiniAppReqParams } from 'napcat-core/packet/entities/miniApp';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Union([
|
const PayloadSchema = Type.Union([
|
||||||
Type.Object({
|
Type.Object({
|
||||||
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')]),
|
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')], { description: '模板类型' }),
|
||||||
title: Type.String(),
|
title: Type.String({ description: '标题' }),
|
||||||
desc: Type.String(),
|
desc: Type.String({ description: '描述' }),
|
||||||
picUrl: Type.String(),
|
picUrl: Type.String({ description: '图片URL' }),
|
||||||
jumpUrl: Type.String(),
|
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||||
webUrl: Type.Optional(Type.String()),
|
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||||
rawArkData: Type.Optional(Type.Union([Type.String()])),
|
rawArkData: Type.Optional(Type.Union([Type.String()], { description: '是否返回原始Ark数据' })),
|
||||||
}),
|
}),
|
||||||
Type.Object({
|
Type.Object({
|
||||||
title: Type.String(),
|
title: Type.String({ description: '标题' }),
|
||||||
desc: Type.String(),
|
desc: Type.String({ description: '描述' }),
|
||||||
picUrl: Type.String(),
|
picUrl: Type.String({ description: '图片URL' }),
|
||||||
jumpUrl: Type.String(),
|
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||||
iconUrl: Type.String(),
|
iconUrl: Type.String({ description: '图标URL' }),
|
||||||
webUrl: Type.Optional(Type.String()),
|
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||||
appId: Type.String(),
|
appId: Type.String({ description: '小程序AppID' }),
|
||||||
scene: Type.Union([Type.Number(), Type.String()]),
|
scene: Type.String({ description: '场景ID' }),
|
||||||
templateType: Type.Union([Type.Number(), Type.String()]),
|
templateType: Type.String({ description: '模板类型' }),
|
||||||
businessType: Type.Union([Type.Number(), Type.String()]),
|
businessType: Type.String({ description: '业务类型' }),
|
||||||
verType: Type.Union([Type.Number(), Type.String()]),
|
verType: Type.String({ description: '版本类型' }),
|
||||||
shareType: Type.Union([Type.Number(), Type.String()]),
|
shareType: Type.String({ description: '分享类型' }),
|
||||||
versionId: Type.String(),
|
versionId: Type.String({ description: '版本ID' }),
|
||||||
sdkId: Type.String(),
|
sdkId: Type.String({ description: 'SDK ID' }),
|
||||||
withShareTicket: Type.Union([Type.Number(), Type.String()]),
|
withShareTicket: Type.String({ description: '是否携带分享票据' }),
|
||||||
rawArkData: Type.Optional(Type.Union([Type.String()])),
|
rawArkData: Type.Optional(Type.String({ description: '是否返回原始Ark数据' })),
|
||||||
}),
|
}),
|
||||||
]);
|
], { description: '小程序Ark参数' });
|
||||||
type Payload = Static<typeof SchemaData>;
|
|
||||||
|
|
||||||
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
data: MiniAppData | MiniAppRawData
|
|
||||||
}> {
|
const ReturnSchema = Type.Object({
|
||||||
|
data: Type.Any({ description: 'Ark数据' }),
|
||||||
|
}, { description: '获取小程序Ark结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetMiniAppArk extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetMiniAppArk;
|
override actionName = ActionName.GetMiniAppArk;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema; override actionSummary = '获取小程序 Ark';
|
||||||
async _handle (payload: Payload) {
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
type: 'bili',
|
||||||
|
title: '测试标题',
|
||||||
|
desc: '测试描述',
|
||||||
|
picUrl: 'http://example.com/pic.jpg',
|
||||||
|
jumpUrl: 'http://example.com'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
data: {
|
||||||
|
ark: 'ark_content'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
let reqParam: MiniAppReqParams;
|
let reqParam: MiniAppReqParams;
|
||||||
const customParams = {
|
const customParams: MiniAppReqCustomParams = {
|
||||||
title: payload.title,
|
title: payload.title,
|
||||||
desc: payload.desc,
|
desc: payload.desc,
|
||||||
picUrl: payload.picUrl,
|
picUrl: payload.picUrl,
|
||||||
jumpUrl: payload.jumpUrl,
|
jumpUrl: payload.jumpUrl,
|
||||||
webUrl: payload.webUrl,
|
webUrl: payload.webUrl ?? '',
|
||||||
} as MiniAppReqCustomParams;
|
};
|
||||||
if ('type' in payload) {
|
if ('type' in payload) {
|
||||||
reqParam = MiniAppInfoHelper.generateReq(customParams, MiniAppInfo.get(payload.type)!.template);
|
const template = MiniAppInfo.get(payload.type)?.template;
|
||||||
|
if (!template) {
|
||||||
|
throw new Error('未知的模板类型');
|
||||||
|
}
|
||||||
|
reqParam = MiniAppInfoHelper.generateReq(customParams, template);
|
||||||
} else {
|
} else {
|
||||||
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
||||||
reqParam = MiniAppInfoHelper.generateReq(
|
reqParam = MiniAppInfoHelper.generateReq(
|
||||||
|
|||||||
@@ -1,36 +1,65 @@
|
|||||||
import { NTVoteInfo } from 'napcat-core';
|
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Type, Static } from '@sinclair/typebox';
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
user_id: Type.Optional(Type.String({ description: 'QQ号' })),
|
||||||
start: Type.Union([Type.Number(), Type.String()], { default: 0 }),
|
start: Type.Union([Type.Number(), Type.String()], { default: 0, description: '起始位置' }),
|
||||||
count: Type.Union([Type.Number(), Type.String()], { default: 10 }),
|
count: Type.Union([Type.Number(), Type.String()], { default: 10, description: '获取数量' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetProfileLike extends OneBotAction<Payload, {
|
const ReturnSchema = Type.Object({
|
||||||
uid: string;
|
uid: Type.String({ description: '用户UID' }),
|
||||||
time: string;
|
time: Type.String({ description: '时间' }),
|
||||||
favoriteInfo: {
|
favoriteInfo: Type.Object({
|
||||||
userInfos: Array<NTVoteInfo>;
|
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||||
total_count: number;
|
total_count: Type.Number({ description: '总点赞数' }),
|
||||||
last_time: number;
|
last_time: Type.Number({ description: '最后点赞时间' }),
|
||||||
today_count: number;
|
today_count: Type.Number({ description: '今日点赞数' }),
|
||||||
};
|
}),
|
||||||
voteInfo: {
|
voteInfo: Type.Object({
|
||||||
total_count: number;
|
total_count: Type.Number({ description: '总点赞数' }),
|
||||||
new_count: number;
|
new_count: Type.Number({ description: '新增点赞数' }),
|
||||||
new_nearby_count: number;
|
new_nearby_count: Type.Number({ description: '新增附近点赞数' }),
|
||||||
last_visit_time: number;
|
last_visit_time: Type.Number({ description: '最后访问时间' }),
|
||||||
userInfos: Array<NTVoteInfo>;
|
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||||
};
|
}),
|
||||||
}> {
|
}, { description: '点赞详情' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetProfileLike extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetProfileLike;
|
override actionName = ActionName.GetProfileLike;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
async _handle (payload: Payload) {
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取资料点赞';
|
||||||
|
override actionTags = ['用户扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
user_id: '123456789',
|
||||||
|
start: 0,
|
||||||
|
count: 10
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
uid: 'u_123',
|
||||||
|
time: '1734567890',
|
||||||
|
favoriteInfo: {
|
||||||
|
userInfos: [],
|
||||||
|
total_count: 10,
|
||||||
|
last_time: 1734567890,
|
||||||
|
today_count: 5
|
||||||
|
},
|
||||||
|
voteInfo: {
|
||||||
|
total_count: 100,
|
||||||
|
new_count: 2,
|
||||||
|
new_nearby_count: 0,
|
||||||
|
last_visit_time: 1734567890,
|
||||||
|
userInfos: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const isSelf = this.core.selfInfo.uin === payload.user_id || !payload.user_id;
|
const isSelf = this.core.selfInfo.uin === payload.user_id || !payload.user_id;
|
||||||
const userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
const type = isSelf ? 2 : 1;
|
const type = isSelf ? 2 : 1;
|
||||||
|
|||||||
@@ -1,18 +1,37 @@
|
|||||||
import { NTQQWebApi } from 'napcat-core/apis';
|
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
const SchemaData = Type.Object({
|
import { NTQQWebApi } from 'napcat-core/apis';
|
||||||
group_id: Type.String(),
|
|
||||||
|
const PayloadSchema = Type.Object({
|
||||||
|
group_id: Type.String({ description: '群号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetQunAlbumList extends OneBotAction<Payload, Awaited<ReturnType<NTQQWebApi['getAlbumListByNTQQ']>>['response']['album_list']> {
|
const ReturnSchema = Type.Array(Type.Any(), { description: '群相册列表' });
|
||||||
|
|
||||||
|
type GetQunAlbumListReturn = Awaited<globalThis.ReturnType<NTQQWebApi['getAlbumListByNTQQ']>>['response']['album_list'];
|
||||||
|
|
||||||
|
export class GetQunAlbumList extends OneBotAction<PayloadType, GetQunAlbumListReturn> {
|
||||||
override actionName = ActionName.GetQunAlbumList;
|
override actionName = ActionName.GetQunAlbumList;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '获取群相册列表';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
};
|
||||||
|
override returnExample = [
|
||||||
|
{
|
||||||
|
album_id: 'album_1',
|
||||||
|
album_name: '测试相册',
|
||||||
|
cover_url: 'http://example.com/cover.jpg',
|
||||||
|
create_time: 1734567890
|
||||||
|
}
|
||||||
|
];
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType): Promise<GetQunAlbumListReturn> {
|
||||||
return (await this.core.apis.WebApi.getAlbumListByNTQQ(payload.group_id)).response.album_list;
|
return (await this.core.apis.WebApi.getAlbumListByNTQQ(payload.group_id)).response.album_list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
export class GetRkey extends GetPacketStatusDepends<void, Array<unknown>> {
|
const ReturnSchema = Type.Array(Type.Any(), { description: 'Rkey列表' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetRkey extends GetPacketStatusDepends<void, ReturnType> {
|
||||||
override actionName = ActionName.GetRkey;
|
override actionName = ActionName.GetRkey;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取 RKey';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = [
|
||||||
|
{
|
||||||
|
"key": "rkey_value",
|
||||||
|
"expired": 1734567890
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
async _handle () {
|
async _handle () {
|
||||||
return await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
return await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
export class GetRobotUinRange extends OneBotAction<void, Array<unknown>> {
|
const ReturnSchema = Type.Array(Type.Any(), { description: '机器人Uin范围列表' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetRobotUinRange extends OneBotAction<void, ReturnType> {
|
||||||
override actionName = ActionName.GetRobotUinRange;
|
override actionName = ActionName.GetRobotUinRange;
|
||||||
|
override actionSummary = '获取机器人 UIN 范围';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = [
|
||||||
|
{ minUin: '12345678', maxUin: '87654321' }
|
||||||
|
];
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle () {
|
async _handle () {
|
||||||
return await this.core.apis.UserApi.getRobotUinRange();
|
return await this.core.apis.UserApi.getRobotUinRange();
|
||||||
|
|||||||
@@ -2,26 +2,37 @@ import { PacketBuf } from 'napcat-core/packet/transformer/base';
|
|||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
|
import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
|
||||||
|
import { Type, Static } from '@sinclair/typebox';
|
||||||
|
|
||||||
interface Friend {
|
const ReturnSchema = Type.Array(
|
||||||
uin: number;
|
Type.Object({
|
||||||
uid: string;
|
uin: Type.Number({ description: 'QQ号' }),
|
||||||
nick_name: string;
|
uid: Type.String({ description: '用户UID' }),
|
||||||
age: number;
|
nick_name: Type.String({ description: '昵称' }),
|
||||||
source: string;
|
age: Type.Number({ description: '年龄' }),
|
||||||
}
|
source: Type.String({ description: '来源' }),
|
||||||
|
}),
|
||||||
|
{ description: '单向好友列表' }
|
||||||
|
);
|
||||||
|
|
||||||
interface Block {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
str_uid: string;
|
|
||||||
bytes_source: string;
|
|
||||||
uint32_sex: number;
|
|
||||||
uint32_age: number;
|
|
||||||
bytes_nick: string;
|
|
||||||
uint64_uin: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
export class GetUnidirectionalFriendList extends OneBotAction<void, ReturnType> {
|
||||||
override actionName = ActionName.GetUnidirectionalFriendList;
|
override actionName = ActionName.GetUnidirectionalFriendList;
|
||||||
|
override payloadSchema = Type.Object({});
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取单向好友列表';
|
||||||
|
override actionTags = ['用户扩展'];
|
||||||
|
override payloadExample = {};
|
||||||
|
override returnExample = [
|
||||||
|
{
|
||||||
|
uin: 123456789,
|
||||||
|
uid: 'u_123',
|
||||||
|
nick_name: '单向好友',
|
||||||
|
age: 20,
|
||||||
|
source: '来源'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
async pack_data (data: string): Promise<Uint8Array> {
|
async pack_data (data: string): Promise<Uint8Array> {
|
||||||
return ProtoBuf(class extends ProtoBufBase {
|
return ProtoBuf(class extends ProtoBufBase {
|
||||||
@@ -30,7 +41,7 @@ export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
|||||||
}).encode();
|
}).encode();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handle (): Promise<Friend[]> {
|
async _handle (): Promise<ReturnType> {
|
||||||
const self_id = this.core.selfInfo.uin;
|
const self_id = this.core.selfInfo.uin;
|
||||||
const req_json = {
|
const req_json = {
|
||||||
uint64_uin: self_id,
|
uint64_uin: self_id,
|
||||||
@@ -40,10 +51,18 @@ export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
|||||||
};
|
};
|
||||||
const packed_data = await this.pack_data(JSON.stringify(req_json));
|
const packed_data = await this.pack_data(JSON.stringify(req_json));
|
||||||
const data = Buffer.from(packed_data);
|
const data = Buffer.from(packed_data);
|
||||||
const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as PacketBuf };
|
const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as unknown as PacketBuf };
|
||||||
const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true);
|
const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true);
|
||||||
const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data);
|
const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data);
|
||||||
const block_list: Block[] = JSON.parse(block_json.data).rpt_block_list;
|
interface BlockItem {
|
||||||
|
uint64_uin: number;
|
||||||
|
str_uid: string;
|
||||||
|
bytes_nick: string;
|
||||||
|
uint32_age: number;
|
||||||
|
bytes_source: string;
|
||||||
|
}
|
||||||
|
const block_data: { rpt_block_list: BlockItem[]; } = JSON.parse(block_json.data);
|
||||||
|
const block_list = block_data.rpt_block_list;
|
||||||
|
|
||||||
return block_list.map((block) => ({
|
return block_list.map((block) => ({
|
||||||
uin: block.uint64_uin,
|
uin: block.uint64_uin,
|
||||||
|
|||||||
@@ -2,17 +2,38 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
|||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
user_id: Type.String({ description: 'QQ号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: number; ext_status: number; } | undefined> {
|
const ReturnSchema = Type.Object({
|
||||||
|
status: Type.Number({ description: '在线状态' }),
|
||||||
|
ext_status: Type.Number({ description: '扩展状态' }),
|
||||||
|
}, { description: '用户状态' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class GetUserStatus extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.GetUserStatus;
|
override actionName = ActionName.GetUserStatus;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '获取用户在线状态';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
user_id: '123456789'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
status: 10,
|
||||||
|
ext_status: 0
|
||||||
|
};
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id);
|
const res = await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id);
|
||||||
|
if (!res) {
|
||||||
|
throw new Error('无法获取用户状态');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,38 @@ import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid';
|
|||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
file_id: Type.String(),
|
file_id: Type.String({ description: '文件ID' }),
|
||||||
current_parent_directory: Type.String(),
|
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||||
target_parent_directory: Type.String(),
|
target_parent_directory: Type.String({ description: '目标父目录' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
interface MoveGroupFileResponse {
|
const ReturnSchema = Type.Object({
|
||||||
ok: boolean;
|
ok: Type.Boolean({ description: '是否成功' }),
|
||||||
}
|
}, { description: '移动文件结果' });
|
||||||
|
|
||||||
export class MoveGroupFile extends GetPacketStatusDepends<Payload, MoveGroupFileResponse> {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class MoveGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.MoveGroupFile;
|
override actionName = ActionName.MoveGroupFile;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '移动群文件';
|
||||||
|
override actionTags = ['文件扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
file_id: '/file_id',
|
||||||
|
current_parent_directory: '/current_folder_id',
|
||||||
|
target_parent_directory: '/target_folder_id',
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
ok: true
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||||
if (contextMsgFile?.fileUUID) {
|
if (contextMsgFile?.fileUUID) {
|
||||||
await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory);
|
await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory);
|
||||||
|
|||||||
@@ -3,18 +3,29 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
|||||||
import { checkFileExist, uriToLocalFile } from 'napcat-common/src/file';
|
import { checkFileExist, uriToLocalFile } from 'napcat-common/src/file';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
import { GeneralCallResultStatus } from 'napcat-core';
|
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
image: Type.String(),
|
|
||||||
|
const PayloadSchema = Type.Object({
|
||||||
|
image: Type.String({ description: '图片路径、URL或Base64' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
class OCRImageBase extends OneBotAction<Payload, GeneralCallResultStatus> {
|
const ReturnSchema = Type.Any({ description: 'OCR结果' });
|
||||||
override payloadSchema = SchemaData;
|
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
class OCRImageBase extends OneBotAction<PayloadType, ReturnType> {
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '图片 OCR 识别';
|
||||||
|
override actionDescription = '识别图片中的文字内容(仅Windows端支持)';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.OCRImage.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.OCRImage.response;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image);
|
const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`);
|
throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`);
|
||||||
@@ -37,8 +48,10 @@ class OCRImageBase extends OneBotAction<Payload, GeneralCallResultStatus> {
|
|||||||
|
|
||||||
export class OCRImage extends OCRImageBase {
|
export class OCRImage extends OCRImageBase {
|
||||||
override actionName = ActionName.OCRImage;
|
override actionName = ActionName.OCRImage;
|
||||||
|
override actionSummary = '图片 OCR 识别';
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IOCRImage extends OCRImageBase {
|
export class IOCRImage extends OCRImageBase {
|
||||||
override actionName = ActionName.IOCRImage;
|
override actionName = ActionName.IOCRImage;
|
||||||
|
override actionSummary = '图片 OCR 识别 (内部)';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,38 @@ import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid';
|
|||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
file_id: Type.String(),
|
file_id: Type.String({ description: '文件ID' }),
|
||||||
current_parent_directory: Type.String(),
|
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||||
new_name: Type.String(),
|
new_name: Type.String({ description: '新文件名' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
interface RenameGroupFileResponse {
|
const ReturnSchema = Type.Object({
|
||||||
ok: boolean;
|
ok: Type.Boolean({ description: '是否成功' }),
|
||||||
}
|
}, { description: '重命名文件结果' });
|
||||||
|
|
||||||
export class RenameGroupFile extends GetPacketStatusDepends<Payload, RenameGroupFileResponse> {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class RenameGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.RenameGroupFile;
|
override actionName = ActionName.RenameGroupFile;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '重命名群文件';
|
||||||
|
override actionTags = ['文件扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
file_id: '/file_id',
|
||||||
|
current_parent_directory: '/',
|
||||||
|
new_name: 'new_name.jpg'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
ok: true
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||||
if (contextMsgFile?.fileUUID) {
|
if (contextMsgFile?.fileUUID) {
|
||||||
await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name);
|
await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name);
|
||||||
|
|||||||
@@ -3,20 +3,35 @@ import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketS
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
cmd: Type.String(),
|
cmd: Type.String({ description: '命令字' }),
|
||||||
data: Type.String(),
|
data: Type.String({ description: '十六进制数据' }),
|
||||||
rsp: Type.Union([Type.String(), Type.Boolean()], { default: true }),
|
rsp: Type.Union([Type.String(), Type.Boolean()], { default: true, description: '是否等待响应' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SendPacket extends GetPacketStatusDepends<Payload, string | undefined> {
|
const ReturnSchema = Type.Union([Type.String({ description: '响应十六进制数据' }), Type.Undefined()], { description: '发包结果' });
|
||||||
override payloadSchema = SchemaData;
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SendPacket extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
override actionName = ActionName.SendPacket;
|
override actionName = ActionName.SendPacket;
|
||||||
async _handle (payload: Payload) {
|
override actionSummary = '发送原始数据包';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
cmd: 'Example.Cmd',
|
||||||
|
data: '123456',
|
||||||
|
rsp: true
|
||||||
|
};
|
||||||
|
override returnExample = '123456';
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true';
|
const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true';
|
||||||
const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: Buffer.from(payload.data, 'hex') as PacketBuf }, rsp);
|
const packetData = Buffer.from(payload.data, 'hex') as unknown as PacketBuf;
|
||||||
|
const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: packetData }, rsp);
|
||||||
return typeof data === 'object' ? data.toString('hex') : undefined;
|
return typeof data === 'object' ? data.toString('hex') : undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
face_id: Type.Union([Type.Number(), Type.String()]), // 参考 face_config.json 的 QSid
|
face_id: Type.Union([Type.Number(), Type.String()], { description: '图标ID' }), // 参考 face_config.json 的 QSid
|
||||||
face_type: Type.Union([Type.Number(), Type.String()], { default: '1' }),
|
face_type: Type.Union([Type.Number(), Type.String()], { default: '1', description: '图标类型' }),
|
||||||
wording: Type.String({ default: ' ' }),
|
wording: Type.String({ default: ' ', description: '状态文字内容' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetDiyOnlineStatus extends OneBotAction<Payload, string> {
|
const ReturnSchema = Type.String({ description: '错误信息(如果有)' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetDiyOnlineStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetDiyOnlineStatus;
|
override actionName = ActionName.SetDiyOnlineStatus;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema; override actionSummary = '设置自定义在线状态';
|
||||||
async _handle (payload: Payload) {
|
override actionDescription = '设置自定义在线状态';
|
||||||
|
override actionTags = ['用户扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
face_id: '123',
|
||||||
|
face_type: '1',
|
||||||
|
wording: '自定义状态'
|
||||||
|
};
|
||||||
|
override returnExample = '';
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
const ret = await this.core.apis.UserApi.setDiySelfOnlineStatus(
|
const ret = await this.core.apis.UserApi.setDiySelfOnlineStatus(
|
||||||
payload.face_id.toString(),
|
payload.face_id.toString(),
|
||||||
payload.wording,
|
payload.wording,
|
||||||
|
|||||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
add_type: Type.Number(),
|
add_type: Type.Number({ description: '加群方式' }),
|
||||||
group_question: Type.Optional(Type.String()),
|
group_question: Type.Optional(Type.String({ description: '加群问题' })),
|
||||||
group_answer: Type.Optional(Type.String()),
|
group_answer: Type.Optional(Type.String({ description: '加群答案' })),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetGroupAddOption extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetGroupAddOption extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupAddOption;
|
override actionName = ActionName.SetGroupAddOption;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '设置群加群选项';
|
||||||
async _handle (payload: Payload): Promise<null> {
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
add_type: 1,
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, {
|
const ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, {
|
||||||
addOption: payload.add_type,
|
addOption: payload.add_type,
|
||||||
groupQuestion: payload.group_question,
|
groupQuestion: payload.group_question,
|
||||||
|
|||||||
@@ -2,21 +2,37 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
album_id: Type.String(),
|
album_id: Type.String({ description: '相册ID' }),
|
||||||
lloc: Type.String(),
|
lloc: Type.String({ description: '媒体ID (lloc)' }),
|
||||||
id: Type.String(), // 421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1
|
id: Type.String({ description: '点赞ID' }), // 421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1
|
||||||
set: Type.Boolean({ default: true }), // true=点赞 false=取消点赞 未实现
|
set: Type.Boolean({ default: true, description: '是否点赞' }), // true=点赞 false=取消点赞 未实现
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetGroupAlbumMediaLike extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '操作结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetGroupAlbumMediaLike extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupAlbumMediaLike;
|
override actionName = ActionName.SetGroupAlbumMediaLike;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '点赞群相册媒体';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
album_id: 'album_id_1',
|
||||||
|
lloc: 'media_id_1',
|
||||||
|
id: '123456',
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
result: {}
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ(
|
return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ(
|
||||||
payload.group_id,
|
payload.group_id,
|
||||||
payload.album_id,
|
payload.album_id,
|
||||||
|
|||||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
group_id: Type.String(),
|
|
||||||
user_id: Type.Array(Type.String()),
|
const PayloadSchema = Type.Object({
|
||||||
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])),
|
group_id: Type.String({ description: '群号' }),
|
||||||
|
user_id: Type.Array(Type.String(), { description: 'QQ号列表' }),
|
||||||
|
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { description: '是否拒绝加群请求' })),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetGroupKickMembers extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetGroupKickMembers extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupKickMembers;
|
override actionName = ActionName.SetGroupKickMembers;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '批量踢出群成员';
|
||||||
|
override actionDescription = '从指定群聊中批量踢出多个成员';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.SetGroupKickMembers.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.SetGroupKickMembers.response;
|
||||||
|
|
||||||
async _handle (payload: Payload): Promise<null> {
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const rejectReq = payload.reject_add_request?.toString() === 'true';
|
const rejectReq = payload.reject_add_request?.toString() === 'true';
|
||||||
const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin)));
|
const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin)));
|
||||||
await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq);
|
await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq);
|
||||||
|
|||||||
@@ -2,17 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
remark: Type.String(),
|
remark: Type.String({ description: '备注' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetGroupRemark extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetGroupRemark extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupRemark;
|
override actionName = ActionName.SetGroupRemark;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
async _handle (payload: Payload): Promise<null> {
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置群备注';
|
||||||
|
override actionDescription = '设置群备注';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
remark: '测试群备注'
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
|
const ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
|
||||||
if (ret.result !== 0) {
|
if (ret.result !== 0) {
|
||||||
throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
|
throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
|
||||||
|
|||||||
@@ -2,18 +2,29 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
robot_member_switch: Type.Optional(Type.Number()),
|
robot_member_switch: Type.Optional(Type.Number({ description: '机器人成员开关' })),
|
||||||
robot_member_examine: Type.Optional(Type.Number()),
|
robot_member_examine: Type.Optional(Type.Number({ description: '机器人成员审核' })),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetGroupRobotAddOption extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetGroupRobotAddOption extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupRobotAddOption;
|
override actionName = ActionName.SetGroupRobotAddOption;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '设置群机器人加群选项';
|
||||||
async _handle (payload: Payload): Promise<null> {
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456'
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const ret = await this.core.apis.GroupApi.setGroupRobotAddOption(
|
const ret = await this.core.apis.GroupApi.setGroupRobotAddOption(
|
||||||
payload.group_id,
|
payload.group_id,
|
||||||
payload.robot_member_switch,
|
payload.robot_member_switch,
|
||||||
|
|||||||
@@ -2,18 +2,29 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.String(),
|
group_id: Type.String({ description: '群号' }),
|
||||||
no_code_finger_open: Type.Optional(Type.Number()),
|
no_code_finger_open: Type.Optional(Type.Number({ description: '未知' })),
|
||||||
no_finger_open: Type.Optional(Type.Number()),
|
no_finger_open: Type.Optional(Type.Number({ description: '未知' })),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetGroupSearch extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetGroupSearch extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetGroupSearch;
|
override actionName = ActionName.SetGroupSearch;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '设置群搜索选项';
|
||||||
async _handle (payload: Payload): Promise<null> {
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456'
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, {
|
const ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, {
|
||||||
noCodeFingerOpenFlag: payload.no_code_finger_open,
|
noCodeFingerOpenFlag: payload.no_code_finger_open,
|
||||||
noFingerOpenFlag: payload.no_finger_open,
|
noFingerOpenFlag: payload.no_finger_open,
|
||||||
|
|||||||
@@ -2,16 +2,27 @@ import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketS
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
class SetGroupSignBase extends GetPacketStatusDepends<Payload, void> {
|
const ReturnSchema = Type.Void({ description: '打卡结果' });
|
||||||
override payloadSchema = SchemaData;
|
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
class SetGroupSignBase extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '群打卡';
|
||||||
|
override actionTags = ['群组扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456789'
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,30 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
|||||||
import { ChatType } from 'napcat-core';
|
import { ChatType } from 'napcat-core';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
user_id: Type.String({ description: 'QQ号' }),
|
||||||
event_type: Type.Number(),
|
event_type: Type.Number({ description: '事件类型' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetInputStatus extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '设置结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetInputStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetInputStatus;
|
override actionName = ActionName.SetInputStatus;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
async _handle (payload: Payload) {
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置输入状态';
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
user_id: '123456789',
|
||||||
|
event_type: 1
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
if (!uid) throw new Error('uid is empty');
|
if (!uid) throw new Error('uid is empty');
|
||||||
const peer = {
|
const peer = {
|
||||||
|
|||||||
@@ -2,17 +2,29 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
longNick: Type.String(),
|
|
||||||
|
const PayloadSchema = Type.Object({
|
||||||
|
longNick: Type.String({ description: '签名内容' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetLongNick extends OneBotAction<Payload, unknown> {
|
const ReturnSchema = Type.Any({ description: '设置结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetLongNick extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetLongNick;
|
override actionName = ActionName.SetLongNick;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置个性签名';
|
||||||
|
override actionDescription = '修改当前登录帐号的个性签名';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.SetLongNick.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.SetLongNick.response;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
return await this.core.apis.UserApi.setLongNick(payload.longNick);
|
return await this.core.apis.UserApi.setLongNick(payload.longNick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,33 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
|||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
status: Type.Union([Type.Number(), Type.String()]),
|
status: Type.Union([Type.Number(), Type.String()], { description: '在线状态' }),
|
||||||
ext_status: Type.Union([Type.Number(), Type.String()]),
|
ext_status: Type.Union([Type.Number(), Type.String()], { description: '扩展状态' }),
|
||||||
battery_status: Type.Union([Type.Number(), Type.String()]),
|
battery_status: Type.Union([Type.Number(), Type.String()], { description: '电量状态' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetOnlineStatus extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '设置结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetOnlineStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetOnlineStatus;
|
override actionName = ActionName.SetOnlineStatus;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置在线状态';
|
||||||
|
override actionDescription = statusText;
|
||||||
|
override actionTags = ['系统扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
status: 11,
|
||||||
|
ext_status: 0,
|
||||||
|
battery_status: 100
|
||||||
|
};
|
||||||
|
override returnExample = null;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const ret = await this.core.apis.UserApi.setSelfOnlineStatus(
|
const ret = await this.core.apis.UserApi.setSelfOnlineStatus(
|
||||||
+payload.status,
|
+payload.status,
|
||||||
+payload.ext_status,
|
+payload.ext_status,
|
||||||
@@ -26,3 +40,216 @@ export class SetOnlineStatus extends OneBotAction<Payload, null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusText = `
|
||||||
|
## 状态列表
|
||||||
|
|
||||||
|
### 在线
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Q我吧
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 60, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 离开
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 30, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 忙碌
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 50, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 请勿打扰
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 70, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 隐身
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 40, "ext_status": 0, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 听歌中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1028, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 春日限定
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2037, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 一起元梦
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2025, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 求星搭子
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2026, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 被掏空
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2014, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 今日天气
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1030, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 我crash了
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2019, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 爱你
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2006, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 恋爱中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1051, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 好运锦鲤
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1071, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 水逆退散
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1201, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 嗨到飞起
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1056, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 元气满满
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1058, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 宝宝认证
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1070, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 一言难尽
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1063, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 难得糊涂
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2001, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### emo中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1401, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 我太难了
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1062, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 我想开了
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2013, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 我没事
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1052, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 想静静
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1061, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 悠哉哉
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1059, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 去旅行
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2015, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 信号弱
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1011, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 出去浪
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2003, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 肝作业
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2012, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 学习中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1018, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 搬砖中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 2023, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 摸鱼中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1300, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 无聊中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1060, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### timi中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1027, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 睡觉中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1016, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 熬夜中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1032, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 追剧中
|
||||||
|
\`\`\`json5;
|
||||||
|
{ "status": 10, "ext_status": 1021, "battery_status": 0; }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### 我的电量
|
||||||
|
\`\`\`json5;
|
||||||
|
{
|
||||||
|
"status": 10,
|
||||||
|
"ext_status": 1000,
|
||||||
|
"battery_status": 0;
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
`;
|
||||||
@@ -4,16 +4,29 @@ import fs from 'node:fs/promises';
|
|||||||
import { checkFileExist, uriToLocalFile } from 'napcat-common/src/file';
|
import { checkFileExist, uriToLocalFile } from 'napcat-common/src/file';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
file: Type.String(),
|
|
||||||
|
const PayloadSchema = Type.Object({
|
||||||
|
file: Type.String({ description: '图片路径、URL或Base64' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export default class SetAvatar extends OneBotAction<Payload, null> {
|
const ReturnSchema = Type.Null({ description: '设置结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export default class SetAvatar extends OneBotAction<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetQQAvatar;
|
override actionName = ActionName.SetQQAvatar;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
async _handle (payload: Payload): Promise<null> {
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置QQ头像';
|
||||||
|
override actionDescription = '修改当前账号的QQ头像';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.SetQQAvatar.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.SetQQAvatar.response;
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||||
const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file));
|
const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.file));
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
|
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
|
||||||
@@ -26,7 +39,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
|||||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||||
}
|
}
|
||||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||||
if (ret.result as number === 1004022) {
|
if (Number(ret.result) === 1004022) {
|
||||||
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`);
|
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`);
|
||||||
} else if (ret.result !== 0) {
|
} else if (ret.result !== 0) {
|
||||||
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
||||||
|
|||||||
@@ -2,19 +2,31 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
|||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
|
||||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
const PayloadSchema = Type.Object({
|
||||||
special_title: Type.String({ default: '' }),
|
group_id: Type.String({ description: '群号' }),
|
||||||
|
user_id: Type.String({ description: 'QQ号' }),
|
||||||
|
special_title: Type.String({ default: '', description: '专属头衔' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SetSpecialTitle extends GetPacketStatusDepends<Payload, void> {
|
const ReturnSchema = Type.Void({ description: '设置结果' });
|
||||||
|
|
||||||
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class SetSpecialTitle extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.SetSpecialTitle;
|
override actionName = ActionName.SetSpecialTitle;
|
||||||
override payloadSchema = SchemaData;
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '设置专属头衔';
|
||||||
|
override actionDescription = '设置群聊中指定成员的专属头衔';
|
||||||
|
override actionTags = ['扩展接口'];
|
||||||
|
override payloadExample = ExtendsActionsExamples.SetSpecialTitle.payload;
|
||||||
|
override returnExample = ExtendsActionsExamples.SetSpecialTitle.response;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
if (!uid) throw new Error('User not found');
|
if (!uid) throw new Error('User not found');
|
||||||
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
|
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
import { GeneralCallResult } from 'napcat-core';
|
|
||||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||||
import { ActionName } from '@/napcat-onebot/action/router';
|
import { ActionName } from '@/napcat-onebot/action/router';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
user_id: Type.Optional(Type.String({ description: 'QQ号' })),
|
||||||
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
group_id: Type.Optional(Type.String({ description: '群号' })),
|
||||||
phone_number: Type.String({ default: '' }),
|
phone_number: Type.String({ default: '', description: '手机号' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
export class SharePeerBase extends OneBotAction<Payload, GeneralCallResult & {
|
const ReturnSchema = Type.Any({ description: '分享结果' });
|
||||||
arkMsg?: string;
|
|
||||||
arkJson?: string;
|
|
||||||
}> {
|
|
||||||
|
|
||||||
override payloadSchema = SchemaData;
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
export class SharePeerBase extends OneBotAction<PayloadType, ReturnType> {
|
||||||
|
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
override actionSummary = '分享用户 (Ark)';
|
||||||
|
override actionDescription = '获取用户推荐的 Ark 内容';
|
||||||
|
override actionTags = ['消息扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
user_id: '123456',
|
||||||
|
phone_number: ''
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
ark: '...'
|
||||||
|
};
|
||||||
|
|
||||||
|
async _handle (payload: PayloadType) {
|
||||||
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) {
|
||||||
@@ -28,18 +39,30 @@ export class SharePeerBase extends OneBotAction<Payload, GeneralCallResult & {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SchemaDataGroupEx = Type.Object({
|
const PayloadSchemaGroupEx = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
});
|
});
|
||||||
export class SharePeer extends SharePeerBase {
|
export class SharePeer extends SharePeerBase {
|
||||||
override actionName = ActionName.SharePeer;
|
override actionName = ActionName.SharePeer;
|
||||||
}
|
}
|
||||||
type PayloadGroupEx = Static<typeof SchemaDataGroupEx>;
|
type PayloadTypeGroupEx = Static<typeof PayloadSchemaGroupEx>;
|
||||||
|
|
||||||
export class ShareGroupExBase extends OneBotAction<PayloadGroupEx, string> {
|
const ReturnSchemaGroupEx = Type.String({ description: 'Ark Json内容' });
|
||||||
override payloadSchema = SchemaDataGroupEx;
|
|
||||||
|
|
||||||
async _handle (payload: PayloadGroupEx) {
|
type ReturnTypeGroupEx = Static<typeof ReturnSchemaGroupEx>;
|
||||||
|
|
||||||
|
export class ShareGroupExBase extends OneBotAction<PayloadTypeGroupEx, ReturnTypeGroupEx> {
|
||||||
|
override payloadSchema = PayloadSchemaGroupEx;
|
||||||
|
override returnSchema = ReturnSchemaGroupEx;
|
||||||
|
override actionSummary = '分享群 (Ark)';
|
||||||
|
override actionDescription = '获取群分享的 Ark 内容';
|
||||||
|
override actionTags = ['消息扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456'
|
||||||
|
};
|
||||||
|
override returnExample = '{"app": "com.tencent.structmsg", ...}';
|
||||||
|
|
||||||
|
async _handle (payload: PayloadTypeGroupEx) {
|
||||||
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
|
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,34 @@ import { FileNapCatOneBotUUID } from 'napcat-common/src/file-uuid';
|
|||||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||||
import { Static, Type } from '@sinclair/typebox';
|
import { Static, Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
const SchemaData = Type.Object({
|
const PayloadSchema = Type.Object({
|
||||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
group_id: Type.String({ description: '群号' }),
|
||||||
file_id: Type.String(),
|
file_id: Type.String({ description: '文件ID' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Payload = Static<typeof SchemaData>;
|
type PayloadType = Static<typeof PayloadSchema>;
|
||||||
|
|
||||||
interface TransGroupFileResponse {
|
const ReturnSchema = Type.Object({
|
||||||
ok: boolean;
|
ok: Type.Boolean({ description: '是否成功' }),
|
||||||
}
|
}, { description: '转发文件结果' });
|
||||||
|
|
||||||
export class TransGroupFile extends GetPacketStatusDepends<Payload, TransGroupFileResponse> {
|
type ReturnType = Static<typeof ReturnSchema>;
|
||||||
|
|
||||||
|
export class TransGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||||
override actionName = ActionName.TransGroupFile;
|
override actionName = ActionName.TransGroupFile;
|
||||||
override payloadSchema = SchemaData;
|
override actionSummary = '传输群文件';
|
||||||
|
override actionTags = ['文件扩展'];
|
||||||
|
override payloadExample = {
|
||||||
|
group_id: '123456',
|
||||||
|
file_id: '/file_id'
|
||||||
|
};
|
||||||
|
override returnExample = {
|
||||||
|
ok: true
|
||||||
|
};
|
||||||
|
override payloadSchema = PayloadSchema;
|
||||||
|
override returnSchema = ReturnSchema;
|
||||||
|
|
||||||
async _handle (payload: Payload) {
|
async _handle (payload: PayloadType) {
|
||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||||
if (contextMsgFile?.fileUUID) {
|
if (contextMsgFile?.fileUUID) {
|
||||||
const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID);
|
const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user