mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-28 07:40:27 +00:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9297c1e10 | ||
|
|
94f07ab98b | ||
|
|
01a6594707 | ||
|
|
82a7154b92 | ||
|
|
9b385ac9c9 | ||
|
|
e3d4cee416 | ||
|
|
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 | ||
|
|
3c24d6b700 | ||
|
|
679c980683 | ||
|
|
19766002ae | ||
|
|
c2d3a8034d | ||
|
|
58220d3fbc |
2
.github/prompt/default.md
vendored
2
.github/prompt/default.md
vendored
@@ -2,7 +2,7 @@
|
||||
[使用文档](https://napneko.github.io/)
|
||||
|
||||
## Windows 一键包
|
||||
我们为提供了的轻量化一键部署方案
|
||||
我们提供了轻量化的一键部署方案
|
||||
相对于普通需要安装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/)
|
||||
|
||||
## Windows 一键包
|
||||
我们为提供了的轻量化一键部署方案
|
||||
我们提供了轻量化的一键部署方案
|
||||
相对于普通需要安装QQ的方案,下面已内置QQ和Napcat 阅读使用文档参考
|
||||
|
||||
你可以下载
|
||||
|
||||
57
.github/workflows/auto-release.yml
vendored
57
.github/workflows/auto-release.yml
vendored
@@ -5,6 +5,63 @@ on:
|
||||
types: [published]
|
||||
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,4 +16,5 @@ checkVersion.sh
|
||||
bun.lockb
|
||||
tests/run/
|
||||
guild1.db-wal
|
||||
guild1.db-shm
|
||||
guild1.db-shm
|
||||
packages/napcat-develop/config/.env
|
||||
|
||||
@@ -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.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) |
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"build:shell": "pnpm --filter napcat-shell run build || 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:webui": "pnpm --filter napcat-webui-frontend 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",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
"name": "napcat-common",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"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"
|
||||
"./src/*": {
|
||||
"import": "./src/*"
|
||||
}
|
||||
},
|
||||
"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 crypto, { randomUUID } from 'crypto';
|
||||
import path from 'node:path';
|
||||
import http from 'node:http';
|
||||
import tls from 'node:tls';
|
||||
import { solveProblem } from '@/napcat-common/src/helper';
|
||||
|
||||
export interface HttpDownloadOptions {
|
||||
url: string;
|
||||
headers?: Record<string, string> | string;
|
||||
proxy?: string;
|
||||
}
|
||||
|
||||
type Uri2LocalRes = {
|
||||
@@ -96,6 +99,7 @@ export function calculateFileMD5 (filePath: string): Promise<string> {
|
||||
|
||||
async function tryDownload (options: string | HttpDownloadOptions, useReferer: boolean = false): Promise<Response> {
|
||||
let url: string;
|
||||
let proxy: string | undefined;
|
||||
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',
|
||||
};
|
||||
@@ -104,6 +108,7 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
||||
headers['Host'] = new URL(url).hostname;
|
||||
} else {
|
||||
url = options.url;
|
||||
proxy = options.proxy;
|
||||
if (options.headers) {
|
||||
if (typeof options.headers === 'string') {
|
||||
headers = JSON.parse(options.headers);
|
||||
@@ -115,6 +120,18 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
||||
if (useReferer && !headers['Referer']) {
|
||||
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) => {
|
||||
if (err.cause) {
|
||||
throw err.cause;
|
||||
@@ -124,6 +141,220 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b
|
||||
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> {
|
||||
const useReferer = typeof options === 'string';
|
||||
let resp = await tryDownload(options);
|
||||
@@ -176,7 +407,7 @@ export async function checkUriType (Uri: string) {
|
||||
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 filePath = path.join(dir, filename);
|
||||
@@ -191,7 +422,7 @@ export async function uriToLocalFile (dir: string, uri: string, filename: string
|
||||
}
|
||||
|
||||
case FileUriType.Remote: {
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} });
|
||||
const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {}, proxy });
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
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();
|
||||
if (keyList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const result: { key: K; value: V }[] = [];
|
||||
const result: { key: K; value: V; }[] = [];
|
||||
const listSize = Math.min(size, keyList.length);
|
||||
for (let i = 0; i < listSize; i++) {
|
||||
const key = keyList[listSize - i];
|
||||
@@ -108,7 +108,7 @@ class MessageUniqueWrapper {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer } | undefined {
|
||||
getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer; } | undefined {
|
||||
const data = this.msgDataMap.getKey(shortId);
|
||||
if (data) {
|
||||
const [msgId, chatTypeStr, peerUid] = data.split('|');
|
||||
@@ -136,6 +136,12 @@ class MessageUniqueWrapper {
|
||||
this.msgIdMap.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();
|
||||
|
||||
@@ -383,17 +383,14 @@ export async function testUrlHead (url: string, timeout: number = 5000): Promise
|
||||
}, (res) => {
|
||||
const statusCode = res.statusCode || 0;
|
||||
const contentType = (res.headers['content-type'] as string) || '';
|
||||
const contentLength = parseInt((res.headers['content-length'] as string) || '0', 10);
|
||||
|
||||
// 验证条件:
|
||||
// 简化验证条件:
|
||||
// 1. 状态码 2xx 或 3xx
|
||||
// 2. Content-Type 不应该是 text/html(表示错误页面)
|
||||
// 3. 对于 .zip 文件,Content-Length 应该 > 1MB(避免获取到错误页面)
|
||||
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
||||
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));
|
||||
@@ -437,10 +434,9 @@ export async function validateUrl (url: string, timeout: number = 5000): Promise
|
||||
const contentType = (res.headers['content-type'] as string) || '';
|
||||
const contentLength = parseInt((res.headers['content-length'] as string) || '0', 10);
|
||||
|
||||
// 验证条件
|
||||
// 简化验证条件
|
||||
const isValidStatus = statusCode >= 200 && statusCode < 400;
|
||||
const isNotHtmlError = !contentType.includes('text/html');
|
||||
const isValidSize = url.endsWith('.zip') ? contentLength > 1024 * 1024 : true;
|
||||
|
||||
if (!isValidStatus) {
|
||||
resolve({
|
||||
@@ -458,14 +454,6 @@ export async function validateUrl (url: string, timeout: number = 5000): Promise
|
||||
contentLength,
|
||||
error: '返回了 HTML 页面而非文件',
|
||||
});
|
||||
} else if (!isValidSize) {
|
||||
resolve({
|
||||
valid: false,
|
||||
statusCode,
|
||||
contentType,
|
||||
contentLength,
|
||||
error: `文件过小 (${contentLength} bytes),可能是错误页面`,
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
valid: true,
|
||||
@@ -542,21 +530,21 @@ export async function findAvailableDownloadUrl (
|
||||
const testWithValidation = async (url: string): Promise<boolean> => {
|
||||
if (validateContent) {
|
||||
const result = await validateUrl(url, timeout);
|
||||
// 额外检查文件大小
|
||||
// 额外检查文件大小(仅当指定了 minFileSize 时)
|
||||
if (result.valid && minFileSize && result.contentLength && result.contentLength < minFileSize) {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
const customUrl = buildMirrorUrl(originalUrl, customMirror);
|
||||
if (await testWithValidation(customUrl)) {
|
||||
return customUrl;
|
||||
}
|
||||
return customUrl;
|
||||
}
|
||||
|
||||
// 2. 先测试原始 URL
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
FileListResponse,
|
||||
FlashFileSetInfo,
|
||||
SendStatus,
|
||||
UploadSceneType,
|
||||
} from '@/napcat-core/data/flash';
|
||||
import { Peer } from '@/napcat-core/types';
|
||||
|
||||
@@ -19,25 +20,44 @@ export class NTQQFlashApi {
|
||||
/**
|
||||
* 发起闪传上传任务
|
||||
* @param fileListToUpload 上传文件绝对路径的列表,可以是文件夹!!
|
||||
* @param thumbnailPath
|
||||
* @param filesetName
|
||||
*/
|
||||
async createFlashTransferUploadTask (fileListToUpload: string[]): Promise < GeneralCallResult & {
|
||||
async createFlashTransferUploadTask (fileListToUpload: string[], thumbnailPath: string, filesetName: string): Promise<GeneralCallResult & {
|
||||
createFlashTransferResult: createFlashTransferResult;
|
||||
seq: number;
|
||||
} > {
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const timestamp : number = Date.now();
|
||||
const timestamp: number = Date.now();
|
||||
const selfInfo = this.core.selfInfo;
|
||||
|
||||
const fileUploadArg = {
|
||||
screen: 1, // 1
|
||||
name: filesetName,
|
||||
uploaders: [{
|
||||
uin: selfInfo.uin,
|
||||
uid: selfInfo.uid,
|
||||
sendEntrance: '',
|
||||
nickname: selfInfo.nick,
|
||||
}],
|
||||
coverPath: thumbnailPath,
|
||||
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);
|
||||
@@ -54,9 +74,9 @@ export class NTQQFlashApi {
|
||||
* 下载闪传文件集
|
||||
* @param fileSetId
|
||||
*/
|
||||
async downloadFileSetBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
||||
extraInfo: unknown
|
||||
} > {
|
||||
async downloadFileSetBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||
extraInfo: unknown;
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const result = await flashService.startFileSetDownload(fileSetId, 1, { isIncludeCompressInnerFiles: false }); // 为了方便,暂时硬编码
|
||||
@@ -72,7 +92,7 @@ export class NTQQFlashApi {
|
||||
* 获取闪传的外链分享
|
||||
* @param fileSetId
|
||||
*/
|
||||
async getShareLinkBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
||||
async getShareLinkBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||
shareLink: string;
|
||||
expireTimestamp: string;
|
||||
}> {
|
||||
@@ -91,9 +111,9 @@ export class NTQQFlashApi {
|
||||
* 从分享外链获取文件集id
|
||||
* @param shareCode
|
||||
*/
|
||||
async fromShareLinkFindSetId (shareCode: string): Promise < GeneralCallResult & {
|
||||
async fromShareLinkFindSetId (shareCode: string): Promise<GeneralCallResult & {
|
||||
fileSetId: string;
|
||||
} > {
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const result = await flashService.getFileSetIdByCode(shareCode);
|
||||
@@ -110,7 +130,7 @@ export class NTQQFlashApi {
|
||||
* == 注意返回结构和其它的不同,没有GeneralCallResult!!! ==
|
||||
* @param fileSetId
|
||||
*/
|
||||
async getFileListBySetId (fileSetId: string): Promise < FileListResponse > {
|
||||
async getFileListBySetId (fileSetId: string): Promise<FileListResponse> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const requestArg = {
|
||||
@@ -153,11 +173,11 @@ export class NTQQFlashApi {
|
||||
* 获取闪传文件集合信息
|
||||
* @param fileSetId
|
||||
*/
|
||||
async getFileSetIndoBySetId (fileSetId: string): Promise < GeneralCallResult & {
|
||||
async getFileSetIndoBySetId (fileSetId: string): Promise<GeneralCallResult & {
|
||||
seq: number;
|
||||
isCache: boolean;
|
||||
fileSet: FlashFileSetInfo;
|
||||
} > {
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const requestArg = {
|
||||
@@ -178,13 +198,13 @@ export class NTQQFlashApi {
|
||||
* @param fileSetId
|
||||
* @param peer
|
||||
*/
|
||||
async sendFlashMessage (fileSetId: string, peer:Peer): Promise < {
|
||||
async sendFlashMessage (fileSetId: string, peer: Peer): Promise<{
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
rsp: {
|
||||
sendStatus: SendStatus[]
|
||||
}
|
||||
} > {
|
||||
sendStatus: SendStatus[];
|
||||
};
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
|
||||
const target = {
|
||||
@@ -212,9 +232,9 @@ export class NTQQFlashApi {
|
||||
* @param fileSetId
|
||||
* @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;
|
||||
} > {
|
||||
}> {
|
||||
const flashService = this.context.session.getFlashTransferService();
|
||||
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;
|
||||
}
|
||||
|
||||
async delBuudy (uid: string, tempBlock = false, tempBothDel = false) {
|
||||
async delBuddy (uid: string, tempBlock = false, tempBothDel = false) {
|
||||
return this.context.session.getBuddyService().delBuddy({
|
||||
friendUid: uid,
|
||||
tempBlock,
|
||||
|
||||
@@ -32,9 +32,9 @@ export class NTQQMsgApi {
|
||||
return this.context.session.getMsgService().getSourceOfReplyMsgV2(peer, clientSeq, time);
|
||||
}
|
||||
|
||||
async getMsgEmojiLikesList (peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number = 20) {
|
||||
async getMsgEmojiLikesList (peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string = '', count: number = 20) {
|
||||
// 注意此处emojiType 可选值一般为1-2 2好像是unicode表情dec值 大部分情况 Taged Mlikiowa
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, '', false, count);
|
||||
return this.context.session.getMsgService().getMsgEmojiLikesList(peer, msgSeq, emojiId, emojiType, cookie, false, count);
|
||||
}
|
||||
|
||||
async setEmojiLike (peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) {
|
||||
|
||||
@@ -114,8 +114,9 @@ export class NTQQOnlineApi {
|
||||
fileElement: {
|
||||
fileName: actualFolderName,
|
||||
filePath: folderPath,
|
||||
fileSize: "",
|
||||
},
|
||||
} as any;
|
||||
};
|
||||
|
||||
const msgService = this.context.session.getMsgService();
|
||||
const startTime = Math.floor(Date.now() / 1000) - 2;
|
||||
@@ -173,7 +174,7 @@ export class NTQQOnlineApi {
|
||||
* 获取好友的在线文件消息
|
||||
* @param peer
|
||||
*/
|
||||
async getOnlineFileMsg (peer: Peer) : Promise<any> {
|
||||
async getOnlineFileMsg (peer: Peer): Promise<any> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
return await msgService.getOnlineFileMsgs(peer);
|
||||
}
|
||||
@@ -183,7 +184,7 @@ export class NTQQOnlineApi {
|
||||
* @param peer
|
||||
* @param msgId
|
||||
*/
|
||||
async cancelMyOnlineFileMsg (peer: Peer, msgId: string) : Promise<void> {
|
||||
async cancelMyOnlineFileMsg (peer: Peer, msgId: string): Promise<void> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
await msgService.cancelSendMsg(peer, msgId);
|
||||
}
|
||||
@@ -194,7 +195,7 @@ export class NTQQOnlineApi {
|
||||
* @param msgId
|
||||
* @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 arrToSend = {
|
||||
msgId,
|
||||
@@ -215,7 +216,7 @@ export class NTQQOnlineApi {
|
||||
* @param elementId
|
||||
* @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 arrToSend = {
|
||||
msgId,
|
||||
@@ -233,7 +234,7 @@ export class NTQQOnlineApi {
|
||||
* @param peer
|
||||
* @param msgId
|
||||
*/
|
||||
async switchFileToOffline (peer: Peer, msgId: string) : Promise<void> {
|
||||
async switchFileToOffline (peer: Peer, msgId: string): Promise<void> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
await msgService.switchToOfflineSendMsg(peer, msgId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface FlashBaseRequest {
|
||||
fileSetId: string
|
||||
fileSetId: string;
|
||||
}
|
||||
|
||||
export interface UploaderInfo {
|
||||
@@ -19,14 +19,14 @@ export interface thumbnailInfo {
|
||||
}
|
||||
|
||||
export interface SendTarget {
|
||||
destType: number // 1私聊
|
||||
destType: number; // 1私聊
|
||||
destUin?: string,
|
||||
destUid: string,
|
||||
}
|
||||
|
||||
export interface SendTargetRequests {
|
||||
fileSetId: string
|
||||
targets: SendTarget[]
|
||||
fileSetId: string;
|
||||
targets: SendTarget[];
|
||||
}
|
||||
|
||||
export interface DownloadStatusInfo {
|
||||
@@ -53,13 +53,13 @@ export interface DownloadStatusInfo {
|
||||
isAllFileAlreadyDownloaded: boolean,
|
||||
saveFileSetDir: string,
|
||||
allWaitingStatusTask: boolean,
|
||||
downloadSceneType: number,
|
||||
downloadSceneType: DownloadSceneType,
|
||||
retryCount: number,
|
||||
statisticInfo: {
|
||||
downloadTaskId: string,
|
||||
downloadFilesetName: string,
|
||||
downloadFileTypeDistribution: string,
|
||||
downloadFileSizeDistribution: string
|
||||
downloadFileSizeDistribution: string;
|
||||
},
|
||||
albumStorageFailImageNum: number,
|
||||
albumStorageFailVideoNum: number,
|
||||
@@ -67,8 +67,8 @@ export interface DownloadStatusInfo {
|
||||
albumStorageSucImageNum: number,
|
||||
albumStorageSucVideoNum: number,
|
||||
albumStorageSucFileIdList: [],
|
||||
albumStorageFileNum: number
|
||||
}
|
||||
albumStorageFileNum: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface physicalInfo {
|
||||
@@ -95,94 +95,94 @@ export interface uploadInfo {
|
||||
svrRrrCode: number,
|
||||
errMsg: string,
|
||||
isNeedDelDeviceInfo: boolean,
|
||||
thumbnailUploadState: number
|
||||
thumbnailUploadState: number;
|
||||
isSecondHit: boolean,
|
||||
hasModifiedErr: boolean,
|
||||
}
|
||||
|
||||
export interface folderUploadInfo {
|
||||
totalUploadedFileSize: string
|
||||
successCount: number
|
||||
failedCount: number
|
||||
totalUploadedFileSize: string;
|
||||
successCount: number;
|
||||
failedCount: number;
|
||||
}
|
||||
|
||||
export interface folderDownloadInfo {
|
||||
totalDownloadedFileSize: string
|
||||
totalFileSize: string
|
||||
totalDownloadFileCount: number
|
||||
successCount: number
|
||||
failedCount: number
|
||||
pausedCount: number
|
||||
cancelCount: number
|
||||
downloadingCount: number
|
||||
partialDownloadCount: number
|
||||
curLevelDownloadedFileCount: number
|
||||
curLevelUnDownloadedFileCount: number
|
||||
totalDownloadedFileSize: string;
|
||||
totalFileSize: string;
|
||||
totalDownloadFileCount: number;
|
||||
successCount: number;
|
||||
failedCount: number;
|
||||
pausedCount: number;
|
||||
cancelCount: number;
|
||||
downloadingCount: number;
|
||||
partialDownloadCount: number;
|
||||
curLevelDownloadedFileCount: number;
|
||||
curLevelUnDownloadedFileCount: number;
|
||||
}
|
||||
|
||||
export interface compressFileFolderInfo {
|
||||
downloadStatus: number
|
||||
saveFileDirPath: string
|
||||
totalFileCount: string
|
||||
totalFileSize: string
|
||||
downloadStatus: number;
|
||||
saveFileDirPath: string;
|
||||
totalFileCount: string;
|
||||
totalFileSize: string;
|
||||
}
|
||||
|
||||
export interface albumStorgeInfo {
|
||||
status: number
|
||||
localIdentifier: string
|
||||
errorCode: number
|
||||
timeCost: number
|
||||
status: number;
|
||||
localIdentifier: string;
|
||||
errorCode: number;
|
||||
timeCost: number;
|
||||
}
|
||||
|
||||
export interface FlashOneFileInfo {
|
||||
fileSetId: string
|
||||
cliFileId: string // client?? 或许可以换取url
|
||||
compressedFileFolderId: string
|
||||
archiveIndex: 0
|
||||
indexPath: string
|
||||
isDir: boolean // 文件或者文件夹!!
|
||||
parentId: string
|
||||
depth: number // 1
|
||||
cliFileIndex: number
|
||||
fileType: number // 枚举!! 已完成枚举!!
|
||||
name: string
|
||||
namePinyin: string
|
||||
isCover: boolean
|
||||
isCoverOriginal: boolean
|
||||
fileSize: string
|
||||
fileCount: number
|
||||
thumbnail: thumbnailInfo
|
||||
physical: physicalInfo
|
||||
srvFileId: string // service?? 服务器上面的id吗?
|
||||
srvParentFileId: string
|
||||
svrLastUpdateTimestamp: string
|
||||
downloadInfo: downloadInfo
|
||||
saveFilePath: string
|
||||
search_relative_path: string
|
||||
disk_relative_path: string
|
||||
uploadInfo: uploadInfo
|
||||
status: number
|
||||
uploadStatus: number // 3已上传成功
|
||||
downloadStatus: number // 0未下载
|
||||
folderUploadInfo: folderUploadInfo
|
||||
folderDownloadInfo: folderDownloadInfo
|
||||
sha1: string
|
||||
bookmark: string
|
||||
compressFileFolderInfo: compressFileFolderInfo
|
||||
uploadPauseReason: string
|
||||
downloadPauseReason: string
|
||||
filePhysicalSize: string
|
||||
thumbnail_sha1: string | null
|
||||
thumbnail_size: string | null
|
||||
needAlbumStorage: boolean
|
||||
albumStorageInfo: albumStorgeInfo
|
||||
fileSetId: string;
|
||||
cliFileId: string; // client?? 或许可以换取url
|
||||
compressedFileFolderId: string;
|
||||
archiveIndex: 0;
|
||||
indexPath: string;
|
||||
isDir: boolean; // 文件或者文件夹!!
|
||||
parentId: string;
|
||||
depth: number; // 1
|
||||
cliFileIndex: number;
|
||||
fileType: number; // 枚举!! 已完成枚举!!
|
||||
name: string;
|
||||
namePinyin: string;
|
||||
isCover: boolean;
|
||||
isCoverOriginal: boolean;
|
||||
fileSize: string;
|
||||
fileCount: number;
|
||||
thumbnail: thumbnailInfo;
|
||||
physical: physicalInfo;
|
||||
srvFileId: string; // service?? 服务器上面的id吗?
|
||||
srvParentFileId: string;
|
||||
svrLastUpdateTimestamp: string;
|
||||
downloadInfo: downloadInfo;
|
||||
saveFilePath: string;
|
||||
search_relative_path: string;
|
||||
disk_relative_path: string;
|
||||
uploadInfo: uploadInfo;
|
||||
status: number;
|
||||
uploadStatus: number; // 3已上传成功
|
||||
downloadStatus: number; // 0未下载
|
||||
folderUploadInfo: folderUploadInfo;
|
||||
folderDownloadInfo: folderDownloadInfo;
|
||||
sha1: string;
|
||||
bookmark: string;
|
||||
compressFileFolderInfo: compressFileFolderInfo;
|
||||
uploadPauseReason: string;
|
||||
downloadPauseReason: string;
|
||||
filePhysicalSize: string;
|
||||
thumbnail_sha1: string | null;
|
||||
thumbnail_size: string | null;
|
||||
needAlbumStorage: boolean;
|
||||
albumStorageInfo: albumStorgeInfo;
|
||||
}
|
||||
|
||||
export interface fileListsInfo {
|
||||
parentId: string,
|
||||
depth: number, // 1
|
||||
fileList: FlashOneFileInfo[],
|
||||
paginationInfo: {}
|
||||
paginationInfo: {};
|
||||
isEnd: boolean,
|
||||
isCache: boolean,
|
||||
}
|
||||
@@ -200,30 +200,50 @@ export interface createFlashTransferResult {
|
||||
expireTime: string,
|
||||
expireLeftTime: string,
|
||||
}
|
||||
|
||||
export enum UploadSceneType {
|
||||
KUPLOADSCENEUNKNOWN,
|
||||
KUPLOADSCENEFLOATWINDOWRIGHTCLICKMENU,
|
||||
KUPLOADSCENEFLOATWINDOWDRAG,
|
||||
KUPLOADSCENEFLOATWINDOWFILESELECTOR,
|
||||
KUPLOADSCENEFLOATWINDOWSHORTCUTKEYCTRLCV,
|
||||
KUPLOADSCENEH5LAUNCHCLIENTRIGHTCLICKMENU,
|
||||
KUPLOADSCENEH5LAUNCHCLIENTDRAG,
|
||||
KUPLOADSCENEH5LAUNCHCLIENTFILESELECTOR,
|
||||
KUPLOADSCENEH5LAUNCHCLIENTSHORTCUTKEYCTRLCV,
|
||||
KUPLOADSCENEAIODRAG,
|
||||
KUPLOADSCENEAIOFILESELECTOR,
|
||||
KUPLOADSCENEAIOSHORTCUTKEYCTRLCV
|
||||
}
|
||||
export interface StartFlashTaskRequests {
|
||||
screen?: number; // 1 PC-QQ
|
||||
screen: number; // 1 PC-QQ
|
||||
name?: string;
|
||||
uploaders: UploaderInfo[];
|
||||
permission?: {};
|
||||
coverPath?: string;
|
||||
paths: string[]; // 文件的绝对路径,可以是文件夹
|
||||
// excludePaths: [];
|
||||
// expireLeftTime: 0,
|
||||
// isNeedDelDeviceInfo: boolean,
|
||||
// isNeedDelLocation: boolean,
|
||||
// coverOriginalInfos: [],
|
||||
// uploadSceneType: 10, // 不知道怎么枚举 先硬编码吧
|
||||
// detectPrivacyInfoResult: {
|
||||
// exists: boolean,
|
||||
// allDetectResults: {}
|
||||
// }
|
||||
excludePaths?: string[];
|
||||
expireLeftTime?: number, // 0
|
||||
isNeedDelDeviceInfo: boolean,
|
||||
isNeedDelLocation: boolean,
|
||||
coverOriginalInfos?: {
|
||||
path: string,
|
||||
thumbnailPath: string,
|
||||
}[],
|
||||
uploadSceneType: UploadSceneType, // 不知道怎么枚举 先硬编码吧 (PC QQ 10)
|
||||
detectPrivacyInfoResult: {
|
||||
exists: boolean,
|
||||
allDetectResults: {};
|
||||
};
|
||||
}
|
||||
export enum BusiScene {
|
||||
KBUSISCENEINVALID,
|
||||
KBUSISCENEFLASHSCENE
|
||||
}
|
||||
|
||||
export interface FileListInfoRequests {
|
||||
seq: number, // 0
|
||||
fileSetId: string,
|
||||
isUseCache: boolean,
|
||||
sceneType: number, // 1
|
||||
sceneType: BusiScene, // 1
|
||||
reqInfos: {
|
||||
count: number, // 18 ?? 硬编码吧 不懂
|
||||
paginationInfo: {},
|
||||
@@ -238,10 +258,24 @@ export interface FileListInfoRequests {
|
||||
sortField: 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 {
|
||||
fileSetId: string,
|
||||
name: string,
|
||||
@@ -258,23 +292,23 @@ export interface FlashFileSetInfo {
|
||||
urls: [
|
||||
{
|
||||
spec: number, // 2
|
||||
url: string
|
||||
url: string;
|
||||
}
|
||||
],
|
||||
localCachePath: string
|
||||
localCachePath: string;
|
||||
},
|
||||
uploaders: [
|
||||
{
|
||||
uin: string,
|
||||
nickname: string,
|
||||
uid: string,
|
||||
sendEntrance: string
|
||||
sendEntrance: string;
|
||||
}
|
||||
],
|
||||
expireLeftTime: number,
|
||||
aiClusteringStatus: {
|
||||
firstClusteringList: [],
|
||||
shouldPull: boolean
|
||||
shouldPull: boolean;
|
||||
},
|
||||
createTime: number,
|
||||
expireTime: number,
|
||||
@@ -284,7 +318,7 @@ export interface FlashFileSetInfo {
|
||||
uploadInfo: {
|
||||
totalUploadedFileSize: number,
|
||||
successCount: number,
|
||||
failedCount: number
|
||||
failedCount: number;
|
||||
},
|
||||
downloadInfo: {
|
||||
totalDownloadedFileSize: 0,
|
||||
@@ -296,7 +330,7 @@ export interface FlashFileSetInfo {
|
||||
cancelCount: 0,
|
||||
status: 0,
|
||||
curLevelDownloadedFileCount: number,
|
||||
curLevelUnDownloadedFileCount: 0
|
||||
curLevelUnDownloadedFileCount: 0;
|
||||
},
|
||||
transferType: number,
|
||||
isLocalCreate: true,
|
||||
@@ -306,12 +340,12 @@ export interface FlashFileSetInfo {
|
||||
downloadStatus: 0,
|
||||
downloadPauseReason: 0,
|
||||
saveFileSetDir: string,
|
||||
uploadSceneType: 10,
|
||||
downloadSceneType: 0, // 0 PC-QQ 103 web
|
||||
uploadSceneType: UploadSceneType,
|
||||
downloadSceneType: DownloadSceneType, // 0 PC-QQ 103 web
|
||||
retryCount: number,
|
||||
isMergeShareUpload: 0,
|
||||
isRemoveDeviceInfo: boolean,
|
||||
isRemoveLocation: boolean
|
||||
isRemoveLocation: boolean;
|
||||
}
|
||||
|
||||
export interface SendStatus {
|
||||
@@ -320,5 +354,5 @@ export interface SendStatus {
|
||||
target: {
|
||||
destType: number,
|
||||
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": {
|
||||
"appid": 537337569,
|
||||
"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": {
|
||||
"send": "0A18D0C",
|
||||
"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": {
|
||||
"send": "2CEBB20",
|
||||
"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';
|
||||
|
||||
interface ForwardMsgJson {
|
||||
app: string
|
||||
app: string;
|
||||
config: ForwardMsgJsonConfig,
|
||||
desc: string,
|
||||
extra: ForwardMsgJsonExtra,
|
||||
meta: ForwardMsgJsonMeta,
|
||||
prompt: string,
|
||||
ver: string,
|
||||
view: string
|
||||
view: string;
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonConfig {
|
||||
@@ -17,7 +17,7 @@ interface ForwardMsgJsonConfig {
|
||||
forward: number,
|
||||
round: number,
|
||||
type: string,
|
||||
width: number
|
||||
width: number;
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonExtra {
|
||||
@@ -26,17 +26,17 @@ interface ForwardMsgJsonExtra {
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonMeta {
|
||||
detail: ForwardMsgJsonMetaDetail
|
||||
detail: ForwardMsgJsonMetaDetail;
|
||||
}
|
||||
|
||||
interface ForwardMsgJsonMetaDetail {
|
||||
news: {
|
||||
text: string
|
||||
text: string;
|
||||
}[],
|
||||
resid: string,
|
||||
source: string,
|
||||
summary: string,
|
||||
uniseq: string
|
||||
uniseq: string;
|
||||
}
|
||||
|
||||
interface ForwardAdaptMsg {
|
||||
@@ -50,8 +50,8 @@ interface ForwardAdaptMsgElement {
|
||||
}
|
||||
|
||||
export class ForwardMsgBuilder {
|
||||
private static build (resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string): ForwardMsgJson {
|
||||
const id = crypto.randomUUID();
|
||||
private static build (resId: string, msg: ForwardAdaptMsg[], source?: string, news?: ForwardMsgJsonMetaDetail['news'], summary?: string, prompt?: string, uuid?: string): ForwardMsgJson {
|
||||
const id = uuid ?? crypto.randomUUID();
|
||||
const isGroupMsg = msg.some(m => m.isGroupMsg);
|
||||
if (!source) {
|
||||
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, []);
|
||||
}
|
||||
|
||||
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 => ({
|
||||
senderName: msg.senderName,
|
||||
isGroupMsg: msg.groupId !== undefined,
|
||||
msg: msg.msg.map(m => ({
|
||||
preview: m.valid ? m.toPreview() : '[该消息类型暂不支持查看]',
|
||||
})),
|
||||
})), source, news, summary, prompt);
|
||||
})),
|
||||
source,
|
||||
news,
|
||||
summary,
|
||||
prompt,
|
||||
uuid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,8 +294,7 @@ function msgElementToText (element: MessageElement, msg: RawMessage, recursiveLe
|
||||
}
|
||||
|
||||
if (element.markdownElement) {
|
||||
// console.log(element.markdownElement);
|
||||
if (element.markdownElement.mdSummary !== undefined && element.markdownElement.mdExtInfo !== undefined && element.markdownElement.mdExtInfo.flashTransferInfo) {
|
||||
if (element.markdownElement?.mdSummary) {
|
||||
return element.markdownElement.mdSummary;
|
||||
} else {
|
||||
return '[Markdown 消息]';
|
||||
|
||||
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 { hostname, systemName, systemVersion } from 'napcat-common/src/system';
|
||||
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 { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config';
|
||||
import os from 'node:os';
|
||||
@@ -39,6 +40,14 @@ export * from './wrapper';
|
||||
export * from './types/index';
|
||||
export * from './services/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 {
|
||||
Unknown = 0,
|
||||
@@ -74,6 +83,7 @@ export function loadQQWrapper (execPath: string | undefined, QQVersion: string):
|
||||
}
|
||||
const nativemodule: { exports: WrapperNodeApi; } = { exports: {} as WrapperNodeApi };
|
||||
process.dlopen(nativemodule, wrapperNodePath);
|
||||
process.env['NAPCAT_WRAPPER_PATH'] = wrapperNodePath;
|
||||
return nativemodule.exports;
|
||||
}
|
||||
export function getMajorPath (execPath: string, QQVersion: string): string {
|
||||
@@ -111,9 +121,19 @@ export class NapCatCore {
|
||||
// 通过构造器递过去的 runtime info 应该尽量少
|
||||
constructor (context: InstanceContext, selfInfo: SelfInfo) {
|
||||
this.selfInfo = selfInfo;
|
||||
this.context = context;
|
||||
this.util = this.context.wrapper.NodeQQNTWrapperUtil;
|
||||
// 先用原始 session 创建 eventWrapper
|
||||
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.apis = {
|
||||
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 { gunzipSync } from 'zlib';
|
||||
import { PacketMsgConverter } from '@/napcat-core/packet/message/converter';
|
||||
import { UploadForwardMsgParams } from '@/napcat-core/packet/transformer/message/UploadForwardMsgV2';
|
||||
|
||||
export class PacketOperationContext {
|
||||
private readonly context: PacketContext;
|
||||
@@ -26,7 +27,7 @@ export class PacketOperationContext {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -94,12 +95,15 @@ export class PacketOperationContext {
|
||||
.filter(Boolean)
|
||||
);
|
||||
const res = await Promise.allSettled(reqList);
|
||||
this.context.logger.info(`上传资源${res.length}个,失败${res.filter((r) => r.status === 'rejected').length}个`);
|
||||
res.forEach((result, index) => {
|
||||
if (result.status === 'rejected') {
|
||||
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
|
||||
}
|
||||
});
|
||||
const failedCount = res.filter((r) => r.status === 'rejected').length;
|
||||
if (failedCount > 0) {
|
||||
this.context.logger.warn(`上传资源${res.length}个,失败${failedCount}个`);
|
||||
res.forEach((result, index) => {
|
||||
if (result.status === 'rejected') {
|
||||
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async UploadImage (img: PacketMsgPicElement) {
|
||||
@@ -224,7 +228,15 @@ export class PacketOperationContext {
|
||||
const res = trans.UploadForwardMsg.parse(resp);
|
||||
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 (
|
||||
groupUin: number,
|
||||
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 FetchC2CMessage } from './FetchC2CMessage';
|
||||
export { default as DownloadForwardMsg } from './DownloadForwardMsg';
|
||||
export { default as UploadForwardMsgV2 } from './UploadForwardMsgV2';
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DownloadStatusInfo,
|
||||
SendTargetRequests,
|
||||
FlashOneFileInfo,
|
||||
DownloadSceneType,
|
||||
} from '../data/flash';
|
||||
|
||||
export interface NodeIKernelFlashTransferService {
|
||||
@@ -18,48 +19,48 @@ export interface NodeIKernelFlashTransferService {
|
||||
* @param timestamp
|
||||
* @param fileInfo
|
||||
*/
|
||||
createFlashTransferUploadTask(timestamp: number, fileInfo: StartFlashTaskRequests): Promise < GeneralCallResult & {
|
||||
createFlashTransferUploadTask (timestamp: number, fileInfo: StartFlashTaskRequests): Promise<GeneralCallResult & {
|
||||
createFlashTransferResult: createFlashTransferResult;
|
||||
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 的信息
|
||||
* @param fileSetIdDict
|
||||
*/
|
||||
getFileSet(fileSetIdDict: FlashBaseRequest): Promise < GeneralCallResult & {
|
||||
getFileSet (fileSetIdDict: FlashBaseRequest): Promise<GeneralCallResult & {
|
||||
seq: number;
|
||||
isCache: boolean;
|
||||
fileSet: FlashFileSetInfo;
|
||||
} >; // 1 arg
|
||||
}>; // 1 arg
|
||||
|
||||
/**
|
||||
* 获取file set 里面的文件信息(文件夹结构)
|
||||
* @param requestArgs
|
||||
*/
|
||||
getFileList(requestArgs: FileListInfoRequests): Promise < {
|
||||
getFileList (requestArgs: FileListInfoRequests): Promise<{
|
||||
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
|
||||
*/
|
||||
getShareLinkReq(fileSetId:string): Promise< GeneralCallResult & {
|
||||
getShareLinkReq (fileSetId: string): Promise<GeneralCallResult & {
|
||||
shareLink: string;
|
||||
expireTimestamp: string;
|
||||
}>;
|
||||
@@ -68,235 +69,235 @@ export interface NodeIKernelFlashTransferService {
|
||||
* 由分享链接到fileSetId
|
||||
* @param shareCode
|
||||
*/
|
||||
getFileSetIdByCode(shareCode: string): Promise < GeneralCallResult & {
|
||||
getFileSetIdByCode (shareCode: string): Promise<GeneralCallResult & {
|
||||
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
|
||||
*/
|
||||
startFileSetUpload(fileSetId: string): void; // 1 arg 并不是新建任务,应该是暂停后的启动
|
||||
startFileSetUpload (fileSetId: string): void; // 1 arg 并不是新建任务,应该是暂停后的启动
|
||||
|
||||
/**
|
||||
* 结束,无法再次启动
|
||||
* @param fileSetId
|
||||
*/
|
||||
stopFileSetUpload(fileSetId: string): void; // 1 arg stop 后start无效
|
||||
stopFileSetUpload (fileSetId: string): void; // 1 arg stop 后start无效
|
||||
|
||||
/**
|
||||
* 暂停上传
|
||||
* @param fileSetId
|
||||
*/
|
||||
pauseFileSetUpload(fileSetId: string): void; // 1 arg 暂停上传
|
||||
pauseFileSetUpload (fileSetId: string): void; // 1 arg 暂停上传
|
||||
|
||||
/**
|
||||
* 继续上传
|
||||
* @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的函数 同开始上传
|
||||
* @param fileSetId
|
||||
* @param chatType 聊天类型 //因为没有peer,其实可以硬编码为1 (好友私聊)
|
||||
* @param downloadSceneType 下载类型 //因为没有peer,其实可以硬编码为1 (好友私聊)
|
||||
* @param arg // 默认为false
|
||||
*/
|
||||
startFileSetDownload(fileSetId:string, chatType: number, arg: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} >; // 3 arg
|
||||
startFileSetDownload (fileSetId: string, downloadSceneType: DownloadSceneType, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||
extraInfo: 0;
|
||||
}>; // 3 arg
|
||||
|
||||
stopFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg 结束不可重启!!
|
||||
stopFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||
extraInfo: 0;
|
||||
}>; // 2 arg 结束不可重启!!
|
||||
|
||||
pauseFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg
|
||||
pauseFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||
extraInfo: 0;
|
||||
}>; // 2 arg
|
||||
|
||||
resumeFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg
|
||||
resumeFileSetDownload (fileSetId: string, downloadOptionParams: { isIncludeCompressInnerFiles: boolean; }): Promise<GeneralCallResult & {
|
||||
extraInfo: 0;
|
||||
}>; // 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,
|
||||
url: string,
|
||||
expireTimestampSeconds: string
|
||||
} >; // 1 arg
|
||||
expireTimestampSeconds: string;
|
||||
}>; // 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
|
||||
*/
|
||||
sendFlashTransferMsg(sendArgs: SendTargetRequests): Promise < {
|
||||
sendFlashTransferMsg (sendArgs: SendTargetRequests): Promise<{
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
rsp: {
|
||||
sendStatus: SendStatus[]
|
||||
}
|
||||
} >; // 1 arg 估计是file set id
|
||||
sendStatus: SendStatus[];
|
||||
};
|
||||
}>; // 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: {
|
||||
seq: number;
|
||||
result: number;
|
||||
errMs: string;
|
||||
fileSetId: string;
|
||||
fileList: []
|
||||
}
|
||||
} >; // 1 arg
|
||||
fileList: [];
|
||||
};
|
||||
}>; // 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;
|
||||
|
||||
getFileThumbSavePathForSend (...args: unknown[]): unknown;
|
||||
getFileThumbSavePathForSend (thumbSize: number, createNeed: boolean): string;
|
||||
|
||||
getFileThumbSavePath (...args: unknown[]): unknown;
|
||||
|
||||
|
||||
@@ -11,3 +11,7 @@ export * from './constant';
|
||||
export * from './graytip';
|
||||
export * from './emoji';
|
||||
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';
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from './adapters';
|
||||
import {
|
||||
GeneralCallResult,
|
||||
NodeIKernelAvatarService,
|
||||
NodeIKernelBuddyService,
|
||||
NodeIKernelGroupService,
|
||||
@@ -27,78 +28,78 @@ import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
|
||||
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
||||
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
||||
import { NodeIKernelFlashTransferService } from "./services/NodeIKernelFlashTransferService";
|
||||
import { NodeIKernelFlashTransferService } from './services/NodeIKernelFlashTransferService';
|
||||
|
||||
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,
|
||||
arg1: unknown,
|
||||
arg2: unknown,
|
||||
@@ -106,7 +107,7 @@ export interface NodeQQNTWrapperUtil {
|
||||
arg4: number,
|
||||
): unknown;
|
||||
|
||||
reportCountIndicators(
|
||||
reportCountIndicators (
|
||||
arg0: string,
|
||||
arg1: Map<unknown, unknown>,
|
||||
arg2: string,
|
||||
@@ -114,7 +115,7 @@ export interface NodeQQNTWrapperUtil {
|
||||
arg4: boolean,
|
||||
): unknown;
|
||||
|
||||
reportValueIndicators(
|
||||
reportValueIndicators (
|
||||
arg0: string,
|
||||
arg1: Map<unknown, unknown>,
|
||||
arg2: string,
|
||||
@@ -122,142 +123,154 @@ export interface NodeQQNTWrapperUtil {
|
||||
arg4: number,
|
||||
): 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 {
|
||||
create(): NodeIQQNTStartupSessionWrapper;
|
||||
stop(): void;
|
||||
start(): void;
|
||||
createWithModuleList(uk: unknown): unknown;
|
||||
getSessionIdList(): unknown;
|
||||
create (): NodeIQQNTStartupSessionWrapper;
|
||||
stop (): void;
|
||||
start (): void;
|
||||
createWithModuleList (uk: unknown): unknown;
|
||||
getSessionIdList (): unknown;
|
||||
}
|
||||
export interface NodeIQQNTWrapperSession {
|
||||
getNTWrapperSession(str: string): NodeIQQNTWrapperSession;
|
||||
getNTWrapperSession (str: string): NodeIQQNTWrapperSession;
|
||||
|
||||
get(): NodeIQQNTWrapperSession;
|
||||
get (): NodeIQQNTWrapperSession;
|
||||
|
||||
new(): NodeIQQNTWrapperSession;
|
||||
|
||||
create(): NodeIQQNTWrapperSession;
|
||||
create (): NodeIQQNTWrapperSession;
|
||||
|
||||
init(
|
||||
init (
|
||||
wrapperSessionInitConfig: WrapperSessionInitConfig,
|
||||
nodeIDependsAdapter: NodeIDependsAdapter,
|
||||
nodeIDispatcherAdapter: NodeIDispatcherAdapter,
|
||||
nodeIKernelSessionListener: NodeIKernelSessionListener,
|
||||
): 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 {
|
||||
@@ -271,20 +284,20 @@ export interface EnginInitDesktopConfig {
|
||||
global_path_config: {
|
||||
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 {
|
||||
get(): NodeIQQNTWrapperEngine;
|
||||
get (): NodeIQQNTWrapperEngine;
|
||||
|
||||
initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
|
||||
initWithDeskTopConfig (config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void;
|
||||
}
|
||||
|
||||
export interface WrapperNodeApi {
|
||||
NodeIO3MiscService: NodeIO3MiscService;
|
||||
NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;
|
||||
NodeIQQNTWrapperSession: NodeIQQNTWrapperSession;
|
||||
NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper
|
||||
NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapper;
|
||||
NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine;
|
||||
NodeIKernelLoginService: NodeIKernelLoginService;
|
||||
|
||||
|
||||
4
packages/napcat-develop/config/.env.example
Normal file
4
packages/napcat-develop/config/.env.example
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_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...');
|
||||
await import(pathToFileURL(NAPCAT_MJS_PATH).href);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
{
|
||||
"name": "napcat-develop",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "powershell ./nodeTest.ps1"
|
||||
"name": "napcat-develop",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "powershell ./nodeTest.ps1",
|
||||
"copy-env": "xcopy config ..\\napcat-shell\\dist\\config /E /I /Y"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./index.js"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./index.js"
|
||||
},
|
||||
"./*": {
|
||||
"require": "./*"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^11.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"./*": {
|
||||
"require": "./*"
|
||||
}
|
||||
},
|
||||
"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 { 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 { FFmpegService } from 'napcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { logSubscription, LogWrapper } from 'napcat-core/helper/log';
|
||||
@@ -79,11 +79,14 @@ export async function NCoreInitFramework (
|
||||
// 启动WebUi
|
||||
WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
|
||||
InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription).then().catch(e => logger.logError(e));
|
||||
// 初始化LLNC的Onebot实现
|
||||
const oneBotAdapter = new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper);
|
||||
// 注册到 WebUiDataRuntime,供调试功能使用
|
||||
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
||||
await oneBotAdapter.InitOneBot();
|
||||
// 使用 NapCatAdapterManager 统一管理协议适配器
|
||||
const adapterManager = new NapCatAdapterManager(loaderObject.core, loaderObject.context, pathWrapper);
|
||||
await adapterManager.initAdapters();
|
||||
// 注册 OneBot 适配器到 WebUiDataRuntime,供调试功能使用
|
||||
const oneBotAdapter = adapterManager.getOneBotAdapter();
|
||||
if (oneBotAdapter) {
|
||||
WebUiDataRuntime.setOneBotContext(oneBotAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
export class NapCatFramework {
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
{
|
||||
"name": "napcat-framework",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
"name": "napcat-framework",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.ts"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"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"
|
||||
"./*": {
|
||||
"import": "./*"
|
||||
}
|
||||
},
|
||||
"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-pty': resolve(__dirname, '../napcat-pty'),
|
||||
'@/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: {
|
||||
|
||||
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 { ReadStream } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export interface ImageSize {
|
||||
width: number;
|
||||
@@ -12,17 +19,18 @@ export enum ImageType {
|
||||
BMP = 'bmp',
|
||||
GIF = 'gif',
|
||||
WEBP = 'webp',
|
||||
TIFF = 'tiff',
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
interface ImageParser {
|
||||
export interface ImageParser {
|
||||
readonly type: ImageType;
|
||||
canParse(buffer: Buffer): boolean;
|
||||
parseSize(stream: ReadStream): Promise<ImageSize | undefined>;
|
||||
canParse (buffer: Buffer): boolean;
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@@ -35,316 +43,39 @@ function matchMagic (buffer: Buffer, magic: number[], offset = 0): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// PNG解析器
|
||||
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];
|
||||
// 所有解析器实例
|
||||
const parserInstances = {
|
||||
png: new PngParser(),
|
||||
jpeg: new JpegParser(),
|
||||
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) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
// 类型到解析器的映射
|
||||
const typeToParser = new Map<ImageType, ImageParser>([
|
||||
[ImageType.PNG, parserInstances.png],
|
||||
[ImageType.JPEG, parserInstances.jpeg],
|
||||
[ImageType.BMP, parserInstances.bmp],
|
||||
[ImageType.GIF, parserInstances.gif],
|
||||
[ImageType.WEBP, parserInstances.webp],
|
||||
[ImageType.TIFF, parserInstances.tiff],
|
||||
]);
|
||||
|
||||
// JPEG解析器
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 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(),
|
||||
];
|
||||
// 所有解析器列表(用于回退)
|
||||
const parsers: ReadonlyArray<ImageParser> = Object.values(parserInstances);
|
||||
|
||||
export async function detectImageType (filePath: string): Promise<ImageType> {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -354,18 +85,22 @@ export async function detectImageType (filePath: string): Promise<ImageType> {
|
||||
end: 63,
|
||||
});
|
||||
|
||||
let buffer: Buffer | null = null;
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
stream.once('error', (err) => {
|
||||
stream.on('error', (err) => {
|
||||
stream.destroy();
|
||||
reject(err);
|
||||
});
|
||||
|
||||
stream.once('readable', () => {
|
||||
buffer = stream.read(64) as Buffer;
|
||||
stream.destroy();
|
||||
stream.on('data', (chunk: Buffer | string) => {
|
||||
const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
chunks.push(chunkBuffer);
|
||||
});
|
||||
|
||||
if (!buffer) {
|
||||
stream.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
|
||||
if (buffer.length === 0) {
|
||||
return resolve(ImageType.UNKNOWN);
|
||||
}
|
||||
|
||||
@@ -377,12 +112,6 @@ export async function detectImageType (filePath: string): Promise<ImageType> {
|
||||
|
||||
resolve(ImageType.UNKNOWN);
|
||||
});
|
||||
|
||||
stream.once('end', () => {
|
||||
if (!buffer) {
|
||||
resolve(ImageType.UNKNOWN);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -390,7 +119,7 @@ export async function imageSizeFromFile (filePath: string): Promise<ImageSize |
|
||||
try {
|
||||
// 先检测类型
|
||||
const type = await detectImageType(filePath);
|
||||
const parser = parsers.find(p => p.type === type);
|
||||
const parser = typeToParser.get(type);
|
||||
if (!parser) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -422,3 +151,71 @@ export async function imageSizeFallBack (
|
||||
): Promise<ImageSize> {
|
||||
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 { TSchema } from '@sinclair/typebox';
|
||||
import { StreamPacket, StreamPacketBasic, StreamStatus } from './stream/StreamBasic';
|
||||
export const ActionExamples = {
|
||||
Common: {
|
||||
errors: [
|
||||
{ code: 1400, description: '请求参数错误或业务逻辑执行失败' },
|
||||
{ code: 1401, description: '权限不足' },
|
||||
{ code: 1404, description: '资源不存在' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
status,
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -32,15 +41,22 @@ export class OB11Response {
|
||||
}
|
||||
}
|
||||
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> {
|
||||
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
||||
core: NapCatCore;
|
||||
private validate?: ValidateFunction<unknown> = undefined;
|
||||
payloadSchema?: TSchema = undefined;
|
||||
returnSchema?: TSchema = undefined;
|
||||
payloadExample?: unknown = undefined;
|
||||
returnExample?: unknown = undefined;
|
||||
actionSummary: string = '';
|
||||
actionDescription: string = '';
|
||||
actionTags: string[] = [];
|
||||
obContext: NapCatOneBot11Adapter;
|
||||
useStream: boolean = false;
|
||||
errorExamples: Array<{ code: number, description: string; }> = ActionExamples.Common.errors;
|
||||
|
||||
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
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 { OneBotAction } from '../OneBotAction';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
|
||||
export class BotExit extends OneBotAction<void, void> {
|
||||
override actionName = ActionName.Exit;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = Type.Object({});
|
||||
override actionSummary = '退出登录';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = null;
|
||||
|
||||
async _handle () {
|
||||
process.exit(0);
|
||||
|
||||
@@ -2,21 +2,36 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
bot_appid: Type.String(),
|
||||
button_id: Type.String({ default: '' }),
|
||||
callback_data: Type.String({ default: '' }),
|
||||
msg_seq: Type.String({ default: '10086' }),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
bot_appid: Type.String({ description: '机器人AppID' }),
|
||||
button_id: Type.String({ default: '', description: '按钮ID' }),
|
||||
callback_data: Type.String({ default: '', description: '回调数据' }),
|
||||
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 payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
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({
|
||||
buttonId: payload.button_id,
|
||||
peerId: payload.group_id.toString(),
|
||||
|
||||
@@ -2,18 +2,33 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
rawData: Type.String(),
|
||||
brief: Type.String(),
|
||||
const PayloadSchema = Type.Object({
|
||||
rawData: Type.String({ description: '原始数据' }),
|
||||
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 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(
|
||||
this.core.selfInfo.uin,
|
||||
this.core.selfInfo.uid,
|
||||
|
||||
@@ -2,19 +2,34 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
album_id: Type.String(),
|
||||
lloc: Type.String(),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
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 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(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
|
||||
@@ -2,20 +2,32 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
album_id: Type.String(),
|
||||
lloc: Type.String(),
|
||||
content: Type.String(),
|
||||
export const DoGroupAlbumCommentPayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册 ID' }),
|
||||
lloc: Type.String({ description: '图片 ID' }),
|
||||
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 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(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
|
||||
@@ -2,18 +2,32 @@ import { Type, Static } from '@sinclair/typebox';
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 48 }),
|
||||
const PayloadSchema = Type.Object({
|
||||
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 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);
|
||||
return ret.emojiInfoList.map(e => e.url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,28 +2,68 @@ 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 { type NTQQMsgApi } from 'napcat-core/apis';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()]),
|
||||
emojiId: Type.Union([Type.Number(), Type.String()]),
|
||||
emojiType: Type.Union([Type.Number(), Type.String()]),
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 20 }),
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
emojiId: Type.Union([Type.Number(), Type.String()], { description: '表情ID' }),
|
||||
emojiType: Type.Union([Type.Number(), Type.String()], { description: '表情类型' }),
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 20, description: '获取数量' }),
|
||||
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 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);
|
||||
if (!msgIdPeer) throw new Error('消息不存在');
|
||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
||||
if (!msg) throw new Error('消息不存在');
|
||||
return await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||
msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), +payload.count
|
||||
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||
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 { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||
import { AIVoiceChatType } from 'napcat-core/packet/entities/aiChat';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
chat_type: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
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 {
|
||||
type: string;
|
||||
characters: {
|
||||
character_id: string;
|
||||
character_name: string;
|
||||
preview_url: string;
|
||||
}[];
|
||||
}
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
type: Type.String({ description: '角色类型' }),
|
||||
characters: Type.Array(
|
||||
Type.Object({
|
||||
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 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) {
|
||||
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +payload.chat_type as AIVoiceChatType);
|
||||
async _handle (payload: PayloadType) {
|
||||
const chatTypeNum = Number(payload.chat_type);
|
||||
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, chatTypeNum);
|
||||
return rawList?.map((item) => ({
|
||||
type: item.category,
|
||||
characters: item.voices.map((voice) => ({
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
interface GetClientkeyResponse {
|
||||
clientkey?: string;
|
||||
}
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
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 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 () {
|
||||
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 { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
category: Type.Union([Type.Number(), Type.String()]),
|
||||
count: Type.Union([Type.Union([Type.Number(), Type.String()])], { default: 1 }),
|
||||
const PayloadSchema = Type.Object({
|
||||
category: Type.String({ description: '分类ID' }),
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
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 payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取带分组的好友列表';
|
||||
override actionTags = ['用户扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
categoryId: 1,
|
||||
categoryName: '我的好友',
|
||||
categoryMbCount: 1,
|
||||
buddyList: []
|
||||
}
|
||||
];
|
||||
|
||||
async _handle () {
|
||||
return (await this.core.apis.FriendApi.getBuddyV2ExWithCate()).map(category => ({
|
||||
const categories = await this.core.apis.FriendApi.getBuddyV2ExWithCate();
|
||||
return categories.map(category => ({
|
||||
...category,
|
||||
buddyList: OB11Construct.friends(category.buddyList),
|
||||
}));
|
||||
|
||||
@@ -1,16 +1,51 @@
|
||||
import { GroupNotifyMsgStatus } from 'napcat-core';
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
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 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 NTQQGroupApi = this.core.apis.GroupApi;
|
||||
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
|
||||
const retData: Notify[] = [];
|
||||
const retData: ReturnType = [];
|
||||
|
||||
const notifyPromises = ignoredNotifies
|
||||
.filter(notify => notify.type === 7)
|
||||
|
||||
@@ -2,19 +2,35 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
album_id: Type.String(),
|
||||
attach_info: Type.String({ default: '' }),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
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 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(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
const PayloadSchema = Type.Object({
|
||||
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 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,79 @@
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||
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';
|
||||
|
||||
const SchemaData = Type.Union([
|
||||
const PayloadSchema = Type.Union([
|
||||
Type.Object({
|
||||
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')]),
|
||||
title: Type.String(),
|
||||
desc: Type.String(),
|
||||
picUrl: Type.String(),
|
||||
jumpUrl: Type.String(),
|
||||
webUrl: Type.Optional(Type.String()),
|
||||
rawArkData: Type.Optional(Type.Union([Type.String()])),
|
||||
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')], { description: '模板类型' }),
|
||||
title: Type.String({ description: '标题' }),
|
||||
desc: Type.String({ description: '描述' }),
|
||||
picUrl: Type.String({ description: '图片URL' }),
|
||||
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||
rawArkData: Type.Optional(Type.Union([Type.String()], { description: '是否返回原始Ark数据' })),
|
||||
}),
|
||||
Type.Object({
|
||||
title: Type.String(),
|
||||
desc: Type.String(),
|
||||
picUrl: Type.String(),
|
||||
jumpUrl: Type.String(),
|
||||
iconUrl: Type.String(),
|
||||
webUrl: Type.Optional(Type.String()),
|
||||
appId: Type.String(),
|
||||
scene: Type.Union([Type.Number(), Type.String()]),
|
||||
templateType: Type.Union([Type.Number(), Type.String()]),
|
||||
businessType: Type.Union([Type.Number(), Type.String()]),
|
||||
verType: Type.Union([Type.Number(), Type.String()]),
|
||||
shareType: Type.Union([Type.Number(), Type.String()]),
|
||||
versionId: Type.String(),
|
||||
sdkId: Type.String(),
|
||||
withShareTicket: Type.Union([Type.Number(), Type.String()]),
|
||||
rawArkData: Type.Optional(Type.Union([Type.String()])),
|
||||
title: Type.String({ description: '标题' }),
|
||||
desc: Type.String({ description: '描述' }),
|
||||
picUrl: Type.String({ description: '图片URL' }),
|
||||
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||
iconUrl: Type.String({ description: '图标URL' }),
|
||||
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||
appId: Type.String({ description: '小程序AppID' }),
|
||||
scene: Type.String({ description: '场景ID' }),
|
||||
templateType: Type.String({ description: '模板类型' }),
|
||||
businessType: Type.String({ description: '业务类型' }),
|
||||
verType: Type.String({ description: '版本类型' }),
|
||||
shareType: Type.String({ description: '分享类型' }),
|
||||
versionId: Type.String({ description: '版本ID' }),
|
||||
sdkId: Type.String({ description: 'SDK ID' }),
|
||||
withShareTicket: Type.String({ description: '是否携带分享票据' }),
|
||||
rawArkData: Type.Optional(Type.String({ description: '是否返回原始Ark数据' })),
|
||||
}),
|
||||
]);
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
], { description: '小程序Ark参数' });
|
||||
|
||||
export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
||||
data: MiniAppData | MiniAppRawData
|
||||
}> {
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
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 payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema; override actionSummary = '获取小程序 Ark';
|
||||
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;
|
||||
const customParams = {
|
||||
const customParams: MiniAppReqCustomParams = {
|
||||
title: payload.title,
|
||||
desc: payload.desc,
|
||||
picUrl: payload.picUrl,
|
||||
jumpUrl: payload.jumpUrl,
|
||||
webUrl: payload.webUrl,
|
||||
} as MiniAppReqCustomParams;
|
||||
webUrl: payload.webUrl ?? '',
|
||||
};
|
||||
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 {
|
||||
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
||||
reqParam = MiniAppInfoHelper.generateReq(
|
||||
|
||||
@@ -1,36 +1,65 @@
|
||||
import { NTVoteInfo } from 'napcat-core';
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
start: Type.Union([Type.Number(), Type.String()], { default: 0 }),
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 10 }),
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.Optional(Type.String({ description: 'QQ号' })),
|
||||
start: Type.Union([Type.Number(), Type.String()], { default: 0, description: '起始位置' }),
|
||||
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, {
|
||||
uid: string;
|
||||
time: string;
|
||||
favoriteInfo: {
|
||||
userInfos: Array<NTVoteInfo>;
|
||||
total_count: number;
|
||||
last_time: number;
|
||||
today_count: number;
|
||||
};
|
||||
voteInfo: {
|
||||
total_count: number;
|
||||
new_count: number;
|
||||
new_nearby_count: number;
|
||||
last_visit_time: number;
|
||||
userInfos: Array<NTVoteInfo>;
|
||||
};
|
||||
}> {
|
||||
const ReturnSchema = Type.Object({
|
||||
uid: Type.String({ description: '用户UID' }),
|
||||
time: Type.String({ description: '时间' }),
|
||||
favoriteInfo: Type.Object({
|
||||
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||
total_count: Type.Number({ description: '总点赞数' }),
|
||||
last_time: Type.Number({ description: '最后点赞时间' }),
|
||||
today_count: Type.Number({ description: '今日点赞数' }),
|
||||
}),
|
||||
voteInfo: Type.Object({
|
||||
total_count: Type.Number({ description: '总点赞数' }),
|
||||
new_count: Type.Number({ description: '新增点赞数' }),
|
||||
new_nearby_count: Type.Number({ description: '新增附近点赞数' }),
|
||||
last_visit_time: Type.Number({ description: '最后访问时间' }),
|
||||
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||
}),
|
||||
}, { description: '点赞详情' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetProfileLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetProfileLike;
|
||||
override payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload) {
|
||||
override payloadSchema = PayloadSchema;
|
||||
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 userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
const type = isSelf ? 2 : 1;
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
import { NTQQWebApi } from 'napcat-core/apis';
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
import { NTQQWebApi } from 'napcat-core/apis';
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
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 payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取 RKey';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
"key": "rkey_value",
|
||||
"expired": 1734567890
|
||||
}
|
||||
];
|
||||
|
||||
async _handle () {
|
||||
return await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
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 actionSummary = '获取机器人 UIN 范围';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{ minUin: '12345678', maxUin: '87654321' }
|
||||
];
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle () {
|
||||
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 { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
interface Friend {
|
||||
uin: number;
|
||||
uid: string;
|
||||
nick_name: string;
|
||||
age: number;
|
||||
source: string;
|
||||
}
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
uin: Type.Number({ description: 'QQ号' }),
|
||||
uid: Type.String({ description: '用户UID' }),
|
||||
nick_name: Type.String({ description: '昵称' }),
|
||||
age: Type.Number({ description: '年龄' }),
|
||||
source: Type.String({ description: '来源' }),
|
||||
}),
|
||||
{ description: '单向好友列表' }
|
||||
);
|
||||
|
||||
interface Block {
|
||||
str_uid: string;
|
||||
bytes_source: string;
|
||||
uint32_sex: number;
|
||||
uint32_age: number;
|
||||
bytes_nick: string;
|
||||
uint64_uin: number;
|
||||
}
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
||||
export class GetUnidirectionalFriendList extends OneBotAction<void, ReturnType> {
|
||||
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> {
|
||||
return ProtoBuf(class extends ProtoBufBase {
|
||||
@@ -30,7 +41,7 @@ export class GetUnidirectionalFriendList extends OneBotAction<void, Friend[]> {
|
||||
}).encode();
|
||||
}
|
||||
|
||||
async _handle (): Promise<Friend[]> {
|
||||
async _handle (): Promise<ReturnType> {
|
||||
const self_id = this.core.selfInfo.uin;
|
||||
const req_json = {
|
||||
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 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 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) => ({
|
||||
uin: block.uint64_uin,
|
||||
|
||||
@@ -2,17 +2,38 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/napcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
const PayloadSchema = Type.Object({
|
||||
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 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) {
|
||||
return await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id);
|
||||
async _handle (payload: PayloadType) {
|
||||
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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
file_id: Type.String(),
|
||||
current_parent_directory: Type.String(),
|
||||
target_parent_directory: Type.String(),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||
target_parent_directory: Type.String({ description: '目标父目录' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
interface MoveGroupFileResponse {
|
||||
ok: boolean;
|
||||
}
|
||||
const ReturnSchema = Type.Object({
|
||||
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 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);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
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 fs from 'fs';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GeneralCallResultStatus } from 'napcat-core';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
image: Type.String(),
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
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> {
|
||||
override payloadSchema = SchemaData;
|
||||
const ReturnSchema = Type.Any({ description: 'OCR结果' });
|
||||
|
||||
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);
|
||||
if (!success) {
|
||||
throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`);
|
||||
@@ -37,8 +48,10 @@ class OCRImageBase extends OneBotAction<Payload, GeneralCallResultStatus> {
|
||||
|
||||
export class OCRImage extends OCRImageBase {
|
||||
override actionName = ActionName.OCRImage;
|
||||
override actionSummary = '图片 OCR 识别';
|
||||
}
|
||||
|
||||
export class IOCRImage extends OCRImageBase {
|
||||
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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
file_id: Type.String(),
|
||||
current_parent_directory: Type.String(),
|
||||
new_name: Type.String(),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||
new_name: Type.String({ description: '新文件名' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
interface RenameGroupFileResponse {
|
||||
ok: boolean;
|
||||
}
|
||||
const ReturnSchema = Type.Object({
|
||||
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 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);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
cmd: Type.String(),
|
||||
data: Type.String(),
|
||||
rsp: Type.Union([Type.String(), Type.Boolean()], { default: true }),
|
||||
const PayloadSchema = Type.Object({
|
||||
cmd: Type.String({ description: '命令字' }),
|
||||
data: Type.String({ description: '十六进制数据' }),
|
||||
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> {
|
||||
override payloadSchema = SchemaData;
|
||||
const ReturnSchema = Type.Union([Type.String({ description: '响应十六进制数据' }), Type.Undefined()], { description: '发包结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SendPacket extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
face_id: Type.Union([Type.Number(), Type.String()]), // 参考 face_config.json 的 QSid
|
||||
face_type: Type.Union([Type.Number(), Type.String()], { default: '1' }),
|
||||
wording: Type.String({ default: ' ' }),
|
||||
const PayloadSchema = Type.Object({
|
||||
face_id: Type.Union([Type.Number(), Type.String()], { description: '图标ID' }), // 参考 face_config.json 的 QSid
|
||||
face_type: Type.Union([Type.Number(), Type.String()], { default: '1', description: '图标类型' }),
|
||||
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 payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema; override actionSummary = '设置自定义在线状态';
|
||||
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(
|
||||
payload.face_id.toString(),
|
||||
payload.wording,
|
||||
|
||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
add_type: Type.Number(),
|
||||
group_question: Type.Optional(Type.String()),
|
||||
group_answer: Type.Optional(Type.String()),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
add_type: Type.Number({ description: '加群方式' }),
|
||||
group_question: Type.Optional(Type.String({ description: '加群问题' })),
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload): Promise<null> {
|
||||
override actionSummary = '设置群加群选项';
|
||||
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, {
|
||||
addOption: payload.add_type,
|
||||
groupQuestion: payload.group_question,
|
||||
|
||||
@@ -2,21 +2,37 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
album_id: Type.String(),
|
||||
lloc: Type.String(),
|
||||
id: Type.String(), // 421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1
|
||||
set: Type.Boolean({ default: true }), // true=点赞 false=取消点赞 未实现
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
lloc: Type.String({ description: '媒体ID (lloc)' }),
|
||||
id: Type.String({ description: '点赞ID' }), // 421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|V5bCgAxMDEyOTU5MjU3.PyqaPndPxg!^||^421_1_0_1012959257|V61Yiali4PELg90bThrH4Bo2iI1M5Kab|17560363448^||^1
|
||||
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 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(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
|
||||
@@ -2,19 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
user_id: Type.Array(Type.String()),
|
||||
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()])),
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
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 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 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);
|
||||
|
||||
@@ -2,17 +2,31 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
remark: Type.String(),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload): Promise<null> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
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);
|
||||
if (ret.result !== 0) {
|
||||
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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
robot_member_switch: Type.Optional(Type.Number()),
|
||||
robot_member_examine: Type.Optional(Type.Number()),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
robot_member_switch: Type.Optional(Type.Number({ description: '机器人成员开关' })),
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload): Promise<null> {
|
||||
override actionSummary = '设置群机器人加群选项';
|
||||
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(
|
||||
payload.group_id,
|
||||
payload.robot_member_switch,
|
||||
|
||||
@@ -2,18 +2,29 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.String(),
|
||||
no_code_finger_open: Type.Optional(Type.Number()),
|
||||
no_finger_open: Type.Optional(Type.Number()),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
no_code_finger_open: Type.Optional(Type.Number({ description: '未知' })),
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload): Promise<null> {
|
||||
override actionSummary = '设置群搜索选项';
|
||||
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, {
|
||||
noCodeFingerOpenFlag: payload.no_code_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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()]),
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
class SetGroupSignBase extends GetPacketStatusDepends<Payload, void> {
|
||||
override payloadSchema = SchemaData;
|
||||
const ReturnSchema = Type.Void({ description: '打卡结果' });
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,30 @@ import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { ChatType } from 'napcat-core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
event_type: Type.Number(),
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: 'QQ号' }),
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload) {
|
||||
override payloadSchema = PayloadSchema;
|
||||
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());
|
||||
if (!uid) throw new Error('uid is empty');
|
||||
const peer = {
|
||||
|
||||
@@ -2,17 +2,29 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
longNick: Type.String(),
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,33 @@ import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
status: Type.Union([Type.Number(), Type.String()]),
|
||||
ext_status: Type.Union([Type.Number(), Type.String()]),
|
||||
battery_status: Type.Union([Type.Number(), Type.String()]),
|
||||
const PayloadSchema = Type.Object({
|
||||
status: Type.Union([Type.Number(), Type.String()], { description: '在线状态' }),
|
||||
ext_status: Type.Union([Type.Number(), Type.String()], { description: '扩展状态' }),
|
||||
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 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(
|
||||
+payload.status,
|
||||
+payload.ext_status,
|
||||
@@ -26,3 +40,216 @@ export class SetOnlineStatus extends OneBotAction<Payload, 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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
file: Type.String(),
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
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 payloadSchema = SchemaData;
|
||||
async _handle (payload: Payload): Promise<null> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
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));
|
||||
if (!success) {
|
||||
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
|
||||
@@ -26,7 +39,7 @@ export default class SetAvatar extends OneBotAction<Payload, null> {
|
||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||
}
|
||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||
if (ret.result as number === 1004022) {
|
||||
if (Number(ret.result) === 1004022) {
|
||||
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`);
|
||||
} else if (ret.result !== 0) {
|
||||
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user