Compare commits

..

36 Commits

Author SHA1 Message Date
手瓜一十雪
99b504b5f6 fix: #880 2025-03-16 09:12:52 +08:00
Mlikiowa
1146454fec release: v4.6.9 2025-03-15 10:58:09 +00:00
手瓜一十雪
805e014a75 fix: #877 2025-03-15 18:54:51 +08:00
Mlikiowa
d3acd1efc1 release: v4.6.8 2025-03-14 10:13:29 +00:00
手瓜一十雪
9fcd218a5a fix: #873 2025-03-14 18:12:58 +08:00
手瓜一十雪
d6a0830cfe fix: #875 2025-03-14 18:07:03 +08:00
手瓜一十雪
40a63b9c66 fix: #870 2025-03-14 17:53:03 +08:00
手瓜一十雪
eeb19a04cc fix: packet异常 2025-03-14 17:39:37 +08:00
Mlikiowa
91e457eb03 release: v4.6.7 2025-03-09 08:31:07 +00:00
手瓜一十雪
78d1919d7f feat: 32896 2025-03-09 16:30:43 +08:00
手瓜一十雪
8393acf173 Merge pull request #856 from HDTianRu/main
feat: 额外返回原msgSeq条目
2025-03-09 10:09:41 +08:00
手瓜一十雪
bca152a047 feat: readme 翻新 2025-03-09 10:08:49 +08:00
HDTianRu
6a15908a93 feat: 额外返回原msgSeq条目 2025-03-08 16:36:17 +08:00
bietiaop
c626bbab74 fix: #854 2025-03-07 10:25:38 +08:00
Mlikiowa
c5c7dcc6f2 release: v4.6.6 2025-03-06 10:51:30 +00:00
手瓜一十雪
03dafe727e fix: win 2025-03-06 18:51:05 +08:00
Mlikiowa
744921c45e release: v4.6.5 2025-03-06 10:09:45 +00:00
手瓜一十雪
abc4a4dcba feat: 32793 2025-03-06 18:09:14 +08:00
Mlikiowa
7e0da2f929 release: v4.6.4 2025-03-05 13:15:11 +00:00
手瓜一十雪
a3b70d0f1f fix 2025-03-05 21:14:52 +08:00
Mlikiowa
d291724f06 release: v4.6.3 2025-03-03 09:17:03 +00:00
手瓜一十雪
122a9ca2cc feat: o3拦截 2025-03-03 17:16:36 +08:00
手瓜一十雪
48aaddd32b feat:rkey 2025-03-03 12:28:55 +08:00
手瓜一十雪
47401af856 feat: searchMsgWithKeywords 2025-03-02 16:07:27 +08:00
Mlikiowa
709adfd812 release: v4.6.2 2025-03-02 07:11:16 +00:00
手瓜一十雪
038d0c5412 fix: #785 2025-03-02 14:55:47 +08:00
手瓜一十雪
6bb4362ed4 feat: 32721 2025-03-02 14:36:11 +08:00
手瓜一十雪
e617f9452d fix: #841 2025-03-02 14:32:21 +08:00
手瓜一十雪
6d8bb49a37 fix: #837 2025-03-02 14:27:09 +08:00
手瓜一十雪
4f6073ee86 fix: 837 2025-03-02 14:26:28 +08:00
手瓜一十雪
2e7176304b fix: #843 2025-03-02 14:24:51 +08:00
Mlikiowa
e36cf11004 release: v4.6.1 2025-02-27 08:35:02 +00:00
手瓜一十雪
0e49e17f68 feat: 32690 2025-02-27 16:34:09 +08:00
手瓜一十雪
524de45f6b Merge pull request #827 from NapNeko/dependabot/npm_and_yarn/globals-16.0.0
chore(deps-dev): bump globals from 15.15.0 to 16.0.0
2025-02-27 16:18:17 +08:00
手瓜一十雪
85741a4b60 feat: 32690 2025-02-27 16:14:39 +08:00
dependabot[bot]
f9ccb8c978 chore(deps-dev): bump globals from 15.15.0 to 16.0.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.15.0 to 16.0.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.15.0...v16.0.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 09:06:38 +00:00
46 changed files with 255 additions and 456 deletions

View File

@@ -1,67 +1,62 @@
<div align="center">
# NapCat
![NapCatQQ](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Fnewlogo.png&name=1&owner=1&pattern=Diagonal+Stripes&stargazers=1&theme=Auto)
_Modern protocol-side framework implemented based on NTQQ._
> 云起兮风生,心向远方兮路未曾至.
</div>
---
## 欢迎回家
NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 特性介绍
- [x] **安装简单**:就算是笨蛋也能使用
- [x] **性能友好**:就算是低内存也能使用
- [x] **接口丰富**:就算是没有也能使用
- [x] **稳定好用**:就算是被捉也能使用
## Welcome
+ NapCatQQ is a modern implementation of the Bot protocol based on NTQQ.
- NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现
## 使用框架
## Feature
+ **Easy to Use**
- 作为初学者能够轻松使用.
+ **Quick and Efficient**
- 在低内存操作系统长时运行.
+ **Rich API Interface**
- 完整实现了大部分标准接口.
+ **Stable and Reliable**
- 持续稳定的开发与维护.
## Quick Start
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
**首次使用**请务必查看如下文档看使用教程
### 文档地址
## Link
[Cloudflare.Worker](https://doc.napneko.icu/)
| Docs | [![Github.IO](https://img.shields.io/badge/docs%20on-Github.IO-orange)](https://napneko.github.io/) | [![Cloudflare.Worker](https://img.shields.io/badge/docs%20on-Cloudflare.Worker-black)](https://doc.napneko.icu/) | [![Cloudflare.HKServer](https://img.shields.io/badge/docs%20on-Cloudflare.HKServer-informational)](https://napcat.napneko.icu/) |
|:-:|:-:|:-:|:-:|
[Cloudflare.HKServer](https://napcat.napneko.icu/)
| Docs | [![Cloudflare.Pages](https://img.shields.io/badge/docs%20on-Cloudflare.Pages-blue)](https://napneko.pages.dev/) | [![Server.Other](https://img.shields.io/badge/docs%20on-Server.Other-green)](https://docs.napcat.cyou/) | [![NapCat.Wiki](https://img.shields.io/badge/docs%20on-NapCat.Wiki-red)](https://www.napcat.wiki) |
|:-:|:-:|:-:|:-:|
[Github.IO](https://napneko.github.io/)
| Contact | [![QQ Group#1](https://img.shields.io/badge/QQ%20Group%231-Join-blue)](https://qm.qq.com/q/I6LU87a0Yq) | [![QQ Group#2](https://img.shields.io/badge/QQ%20Group%232-Join-blue)](https://qm.qq.com/q/HaRcfrHpUk) | [![Telegram](https://img.shields.io/badge/Telegram-MelodicMoonlight-blue)](https://t.me/MelodicMoonlight) |
|:-:|:-:|:-:|:-:|
[Cloudflare.Pages](https://napneko.pages.dev/)
## Thanks
[Server.Other](https://docs.napcat.cyou/)
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
[NapCat.Wiki](https://www.napcat.wiki)
+ [LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目部分开发
## 回家旅途
[QQ Group#1](https://qm.qq.com/q/I6LU87a0Yq)
[QQ Group#2](https://qm.qq.com/q/HaRcfrHpUk)
[Telegram](https://t.me/MelodicMoonlight)
> QQ Group#2 准许Bot / Telegram与QQ Group#2 为新建Group
## 性能设计/协议标准
NapCat 已实现90+的 OneBot / GoCQ 标准接口,并提供兼容性保留接口,其设计理念遵守 无数据库/异步优先/后台刷新 的性能思想。
由此设计带来一系列好处在开发中获取群员列表通常小于50Ms单条文本消息发送在320Ms以内在1k+的群聊流畅运行同时带来一些副作用消息Id无法持久无法上报撤回消息原始内容。
NapCat 在设计理念下遵守 OneBot 规范大多数要求并且积极改进,任何合理的标准化 Issue 与 Pr 将被接收。
## 感谢他们
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
感谢 React 强力驱动 NapCat.WebUi
不过最最重要的 还是需要感谢屏幕前的你哦~
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
---
## 特殊感谢
[LLOneBot](https://github.com/LLOneBot/LLOneBot) 相关的开发曾参与本项目
## License
本项目采用 混合协议 开源,因此使用本项目时,你需要注意以下几点:
1. 第三方库代码或修改部分遵循其原始开源许可.
2. 本项目获取部分项目授权而不受部分约束
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
## 开源附加
任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**

Binary file not shown.

View File

@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -19,7 +19,7 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
if not exist "%QQPath%" (
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -27,8 +27,8 @@ for %%a in ("%RetString%") do (
SET QQPath=%pathWithoutUninstall%QQ.exe
if not exist "%QQpath%" (
echo provided QQ path is invalid: %QQpath%
if not exist "%QQPath%" (
echo provided QQ path is invalid
pause
exit /b
)

View File

@@ -1,10 +1,9 @@
{
"name": "qq-chat",
"version": "9.9.17-30899",
"verHash": "ececf273",
"linuxVersion": "3.2.15-30899",
"linuxVerHash": "63c751e8",
"type": "module",
"version": "9.9.18-32793",
"verHash": "d43f097e",
"linuxVersion": "3.2.16-32793",
"linuxVerHash": "ee4bd910",
"private": true,
"description": "QQ",
"productName": "QQ",
@@ -17,10 +16,27 @@
"bin": {
"qd": "externals/devtools/cli/index.js"
},
"appid": {
"win32": "537258389",
"darwin": "537258412",
"linux": "537258424"
},
"main": "./loadNapCat.js",
"buildVersion": "30899",
"peerDependenciesMeta": {
"*": {
"optional": true
}
},
"pnpm": {
"patchedDependencies": {
"@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
"@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
"vuex@4.1.0": "patches/vuex@4.1.0.patch"
}
},
"buildVersion": "32793",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",
"eleArch": "x64"
}
}

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.6.0",
"version": "4.6.9",
"icon": "./logo.png",
"authors": [
{

View File

@@ -136,7 +136,7 @@ const OneBotApiDebug: React.FC<OneBotApiDebugProps> = (props) => {
</div>
<Card
shadow="sm"
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible z-20"
className="my-4 bg-opacity-50 backdrop-blur-md overflow-visible"
>
<CardHeader className="font-bold text-lg gap-1 pb-0">
<span className="mr-2"></span>

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.6.0",
"version": "4.6.9",
"scripts": {
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
@@ -42,6 +42,7 @@
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"commander": "^13.0.0",
"cors": "^2.8.5",
"esbuild": "0.25.0",
"eslint": "^9.14.0",
"eslint-import-resolver-typescript": "^3.6.1",
@@ -49,7 +50,7 @@
"express-rate-limit": "^7.5.0",
"fast-xml-parser": "^4.3.6",
"file-type": "^20.0.0",
"globals": "^15.12.0",
"globals": "^16.0.0",
"image-size": "^1.1.1",
"json5": "^2.2.3",
"multer": "^1.4.5-lts.1",
@@ -64,9 +65,7 @@
},
"dependencies": {
"@ffmpeg.wasm/core-mt": "^0.13.2",
"cors": "^2.8.5",
"express": "^5.0.0",
"openai": "^4.85.4",
"silk-wasm": "^3.6.1",
"ws": "^8.18.0"
}

View File

@@ -163,7 +163,7 @@ class Store {
const current = this.get<StoreValueType>(key);
if (current === null) {
this.set(key, 1);
this.set(key, 1, 60);
return 1;
}
@@ -180,7 +180,7 @@ class Store {
}
const newValue = numericValue + 1;
this.set(key, newValue);
this.set(key, newValue, 60);
return newValue;
}
}

View File

@@ -1 +1 @@
export const napCatVersion = '4.6.0';
export const napCatVersion = '4.6.9';

View File

@@ -42,7 +42,7 @@ export class NTQQFileApi {
this.core = core;
this.rkeyManager = new RkeyManager([
'https://ss.xingzhige.com/music_card/rkey', // 国内
'https://rkey.napneko.icu/rkeys' // Cloudflare
'https://secret-service.bietiaop.com/rkeys',//国内
],
this.context.logger
);

View File

@@ -27,6 +27,9 @@ export class NTQQGroupApi {
this.core = core;
}
async setGroupRemark(groupCode: string, remark: string) {
return this.context.session.getGroupService().modifyGroupRemark(groupCode, remark);
}
async fetchGroupDetail(groupCode: string) {
const [, detailInfo] = await this.core.eventWrapper.callNormalEventV2(
'NodeIKernelGroupService/getGroupDetailInfo',
@@ -345,9 +348,9 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().uploadGroupBulletinPic(groupCode, _Pskey, imageurl);
}
async handleGroupRequest(notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
async handleGroupRequest(doubt: boolean, notify: GroupNotify, operateType: NTGroupRequestOperateTypes, reason?: string) {
return this.context.session.getGroupService().operateSysNotify(
false,
doubt,
{
operateType: operateType,
targetMsg: {

View File

@@ -12,7 +12,7 @@ export class NTQQMsgApi {
this.context = context;
this.core = core;
}
async clickInlineKeyboardButton(...params: Parameters<NodeIKernelMsgService['clickInlineKeyboardButton']>) {
return this.context.session.getMsgService().clickInlineKeyboardButton(...params);
}

View File

@@ -186,5 +186,37 @@
"9.9.17-31363": {
"appid": 537266500,
"qua": "V1_WIN_NQ_9.9.17_31363_GW_B"
},
"3.2.16-32690": {
"appid": 537271229,
"qua": "V1_LNX_NQ_3.2.16_32690_GW_B"
},
"9.9.18-32690": {
"appid": 537271194,
"qua": "V1_WIN_NQ_9.9.18_32690_GW_B"
},
"6.9.66-32690": {
"appid": 537271218,
"qua": "V1_MAC_NQ_6.9.66_32690_GW_B"
},
"3.2.16-32721": {
"appid": 537271229,
"qua": "V1_LNX_NQ_3.2.16_32721_GW_B"
},
"9.9.18-32793": {
"appid": 537271244,
"qua": "V1_WIN_NQ_9.9.18_32793_GW_B"
},
"3.2.16-32793": {
"appid": 537271279,
"qua": "V1_LNX_NQ_3.2.16_32793_GW_B"
},
"3.2.16-32869": {
"appid": 537271329,
"qua": "V1_LNX_NQ_3.2.16_32869_GW_B"
},
"9.9.18-32869": {
"appid": 537271294,
"qua": "V1_WIN_NQ_9.9.18_32869_GW_B"
}
}
}

View File

@@ -4,5 +4,6 @@
"fileLogLevel": "debug",
"consoleLogLevel": "info",
"packetBackend": "auto",
"packetServer": ""
}
"packetServer": "",
"o3HookMode": 1
}

View File

@@ -246,5 +246,49 @@
"6.9.65-31363-arm64": {
"send": "422CEF8",
"recv": "422F710"
},
"9.9.18-32690-x64": {
"send": "39F9630",
"recv": "39FDE30"
},
"3.2.16-32690-x64": {
"send": "A5E24C0",
"recv": "A5E5EE0"
},
"3.2.16-32690-arm64": {
"send": "7226630",
"recv": "7229F60"
},
"3.2.16-32721-x64": {
"send": "A5E24C0",
"recv": "A5E5EE0"
},
"3.2.16-32721-arm64": {
"send": "7226630",
"recv": "7229F60"
},
"9.9.18-32793-x64": {
"send": "39F9A30",
"recv": "39FE230"
},
"3.2.16-32793-x64": {
"send": "A5E24C0",
"recv": "A5E5EE0"
},
"3.2.16-32793-arm64": {
"send": "7226630",
"recv": "7229F60"
},
"9.9.18-32869-x64": {
"send": "39F9A30",
"recv": "39FE230"
},
"3.2.16-32869-x64": {
"send": "A5E24C0",
"recv": "A5E5EE0"
},
"3.2.16-32869-arm64": {
"send": "7226630",
"recv": "7229F60"
}
}
}

View File

@@ -10,6 +10,7 @@ export const NapcatConfigSchema = Type.Object({
consoleLogLevel: Type.String({ default: 'info' }),
packetBackend: Type.String({ default: 'auto' }),
packetServer: Type.String({ default: '' }),
o3HookMode: Type.Number({ default: 0 }),
});
export type NapcatConfig = Static<typeof NapcatConfigSchema>;

View File

@@ -1,4 +1,4 @@
import { ChatType } from '@/core';
import { ChatType, RawMessage } from '@/core';
export interface SearchGroupInfo {
groupCode: string;
ownerUid: string;
@@ -56,7 +56,7 @@ export interface GroupSearchResult {
nextPos: number;
}
export interface NodeIKernelSearchListener {
onSearchGroupResult(params: GroupSearchResult): any;
onSearchFileKeywordsResult(params: {
@@ -94,4 +94,27 @@ export interface NodeIKernelSearchListener {
}[]
}[]
}): any;
onSearchMsgKeywordsResult(params: {
searchId: string,
hasMore: boolean,
resultItems: Array<{
msgId: string,
msgSeq: string,
msgTime: string,
senderUid: string,
senderUin: string,
senderNick: string,
senderNickHits: unknown[],
senderRemark: string,
senderRemarkHits: unknown[],
senderCard: string,
senderCardHits: unknown[],
fieldType: number,
fieldText: string,
msgRecord: RawMessage;
hitsInfo: Array<unknown>,
msgAbstract: unknown,
}>
}): void | Promise<void>;
}

View File

@@ -11,7 +11,7 @@ import { PacketLogger } from '@/core/packet/context/loggerContext';
// 0 send 1 recv
export interface NativePacketExportType {
InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
InitHook?: (send: string, recv: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void, o3_hook: boolean) => boolean;
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
}
@@ -42,6 +42,7 @@ export class NativePacketClient extends IPacketClient {
const platform = process.platform + '.' + process.arch;
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
if (type === 0 && this.cb.get(trace_id + 'recv')) {
@@ -55,7 +56,7 @@ export class NativePacketClient extends IPacketClient {
// console.log('callback:', callback, trace_id);
callback?.({ seq, cmd, hex_data });
}
});
}, this.napcore.config.o3HookMode == 1);
this.available = true;
}

View File

@@ -165,7 +165,7 @@ export interface NodeIKernelGroupService {
modifyGroupName(groupCode: string, groupName: string, isNormalMember: boolean): Promise<GeneralCallResult>;
modifyGroupRemark(groupCode: string, remark: string): void;
modifyGroupRemark(groupCode: string, remark: string): Promise<GeneralCallResult>;
modifyGroupDetailInfo(groupCode: string, arg: unknown): void;

View File

@@ -1,4 +1,4 @@
import { ChatType } from '@/core/types';
import { ChatType, Peer } from '@/core/types';
import { GeneralCallResult } from './common';
export interface NodeIKernelSearchService {
@@ -54,7 +54,7 @@ export interface NodeIKernelSearchService {
cancelSearchChatMsgs(...args: unknown[]): unknown;// needs 3 arguments
searchMsgWithKeywords(...args: unknown[]): unknown;// needs 2 arguments
searchMsgWithKeywords(keyWords: string[], param: Peer & { searchFields: number, pageLimit: number }): Promise<GeneralCallResult>;
searchMoreMsgWithKeywords(...args: unknown[]): unknown;// needs 1 arguments

View File

@@ -7,13 +7,13 @@ import { SelfInfo } from '@/core/types';
import { NodeIKernelLoginListener } from '@/core/listeners';
import { NodeIKernelLoginService } from '@/core/services';
import { NodeIQQNTWrapperSession, WrapperNodeApi } from '@/core/wrapper';
import { InitWebUi, WebUiConfig } from '@/webui';
import { InitWebUi, WebUiConfig, webUiRuntimePort } from '@/webui';
import { NapCatOneBot11Adapter } from '@/onebot';
//Framework ES入口文件
export async function getWebUiUrl() {
const WebUiConfigData = (await WebUiConfig.GetWebUIConfig());
return 'http://127.0.0.1:' + WebUiConfigData.port + '/webui/?token=' + WebUiConfigData.token;
return 'http://127.0.0.1:' + webUiRuntimePort + '/webui/?token=' + WebUiConfigData.token;
}
export async function NCoreInitFramework(

View File

@@ -0,0 +1,22 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.String(),
remark: Type.String(),
});
type Payload = Static<typeof SchemaData>;
export default class SetGroupRemark extends OneBotAction<Payload, null> {
override actionName = ActionName.SetGroupRemark;
override payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<null> {
let ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
if (ret.result != 0) {
throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
}
return null;
}
}

View File

@@ -5,7 +5,7 @@ import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
user_id: Type.Union([Type.Number(), Type.String()]),
special_title: Type.String(),
special_title: Type.String({ default: '' }),
});
type Payload = Static<typeof SchemaData>;
@@ -16,7 +16,7 @@ export class SetSpecialTittle extends GetPacketStatusDepends<Payload, void> {
async _handle(payload: Payload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if(!uid) throw new Error('User not found');
if (!uid) throw new Error('User not found');
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
}
}

View File

@@ -20,11 +20,12 @@ export default class SetGroupAddRequest extends OneBotAction<Payload, null> {
const approve = payload.approve?.toString() !== 'false';
const reason = payload.reason ?? ' ';
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(flag);
const notify = invite_notify ?? await this.findNotify(flag);
const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(flag);
if (!notify) {
throw new Error('No such request');
}
await this.core.apis.GroupApi.handleGroupRequest(
doubt,
notify,
approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
reason,
@@ -36,7 +37,8 @@ export default class SetGroupAddRequest extends OneBotAction<Payload, null> {
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
if (!notify) {
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
return { doubt: true, notify };
}
return notify;
return { doubt: false, notify };
}
}

View File

@@ -16,6 +16,8 @@ export default class SetGroupBan extends OneBotAction<Payload, null> {
async _handle(payload: Payload): Promise<null> {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('uid error');
let member_role = (await this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, true))?.role;
if (member_role === 4) throw new Error('cannot ban owner');
// 例如无管理员权限时 result为 120101005 errMsg为 'ERR_NOT_GROUP_ADMIN'
let ret = await this.core.apis.GroupApi.banMember(payload.group_id.toString(),
[{ uid: uid, timeStamp: +payload.duration }]);

View File

@@ -108,10 +108,12 @@ import { BotExit } from './extends/BotExit';
import { ClickInlineKeyboardButton } from './extends/ClickInlineKeyboardButton';
import { GetPrivateFileUrl } from './file/GetPrivateFileUrl';
import { GetUnidirectionalFriendList } from './extends/GetUnidirectionalFriendList';
import SetGroupRemark from './extends/SetGroupRemark';
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [
new SetGroupRemark(obContext, core),
new GetGroupInfoEx(obContext, core),
new FetchEmojiLike(obContext, core),
new GetFile(obContext, core),
@@ -227,8 +229,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetGroupSystemMsg(obContext, core),
new BotExit(obContext, core),
new ClickInlineKeyboardButton(obContext, core),
new GetPrivateFileUrl(obContext,core),
new GetUnidirectionalFriendList(obContext,core),
new GetPrivateFileUrl(obContext, core),
new GetUnidirectionalFriendList(obContext, core),
];
type HandlerUnion = typeof actionHandlers[number];

View File

@@ -10,6 +10,7 @@ export interface InvalidCheckResult {
}
export const ActionName = {
SetGroupRemark: 'set_group_remark',
NapCat_GetPrivateFileUrl: 'get_private_file_url',
ClickInlineKeyboardButton: 'click_inline_keyboard_button',
GetUnidirectionalFriendList: 'get_unidirectional_friend_list',

View File

@@ -355,6 +355,7 @@ export class OneBotMsgApi {
data: {
file: fileCode,
file_size: element.fileSize,
path: element.filePath,
},
};
},
@@ -808,6 +809,7 @@ export class OneBotMsgApi {
message_id: msg.id!,
message_seq: msg.id!,
real_id: msg.id!,
real_seq: msg.msgSeq,
message_type: msg.chatType == ChatType.KCHATTYPEGROUP ? 'group' : 'private',
sender: {
user_id: +(msg.senderUin ?? 0),

View File

@@ -84,17 +84,19 @@ export class OneBotQuickActionApi {
let notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(false, 100)).find(e => e.seq == flag);
if (!notify) {
notify = (await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100)).find(e => e.seq == flag);
return { doubt: true, notify };
}
return notify;
return { doubt: false, notify };
}
async handleGroupRequest(request: OB11GroupRequestEvent, quickAction: QuickActionGroupRequest) {
const invite_notify = this.obContext.apis.MsgApi.notifyGroupInvite.get(request.flag);
const notify = invite_notify ?? await this.findNotify(request.flag);
const { doubt, notify } = invite_notify ? { doubt: false, notify: invite_notify } : await this.findNotify(request.flag);
if (!isNull(quickAction.approve) && notify) {
this.core.apis.GroupApi.handleGroupRequest(
doubt,
notify,
quickAction.approve ? NTGroupRequestOperateTypes.KAGREE : NTGroupRequestOperateTypes.KREFUSE,
quickAction.reason,

View File

@@ -50,7 +50,6 @@ import {
import { OB11Message } from './types';
import { IOB11NetworkAdapter } from '@/onebot/network/adapter';
import { OB11HttpSSEServerAdapter } from './network/http-server-sse';
import { OB11PluginAdapter } from './network/plugin';
//OneBot实现类
export class NapCatOneBot11Adapter {
@@ -114,9 +113,9 @@ export class NapCatOneBot11Adapter {
//创建NetWork服务
// 注册Plugin 如果需要基于NapCat进行快速开发
this.networkManager.registerAdapter(
new OB11PluginAdapter('myPlugin', this.core, this,this.actions)
);
// this.networkManager.registerAdapter(
// new OB11PluginAdapter('myPlugin', this.core, this,this.actions)
// );
for (const key of ob11Config.network.httpServers) {
if (key.enable) {
this.networkManager.registerAdapter(

View File

@@ -1,5 +1,5 @@
import { OB11EmitEventContent, OB11NetworkReloadType } from './index';
import { NapCatOneBot11Adapter, OB11ArrayMessage } from '@/onebot';
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot';
import { NapCatCore } from '@/core';
import { PluginConfig } from '../config/config';
import { plugin_onmessage } from '@/plugin';
@@ -15,14 +15,14 @@ export class OB11PluginAdapter extends IOB11NetworkAdapter<PluginConfig> {
messagePostFormat: 'array',
reportSelfMessage: false,
enable: true,
debug: true,
debug: false,
};
super(name, config, core, obContext, actions);
}
onEvent<T extends OB11EmitEventContent>(event: T) {
if (event.post_type === 'message') {
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11ArrayMessage, this.actions, this).then().catch();
plugin_onmessage(this.config.name, this.core, this.obContext, event as OB11Message, this.actions, this).then().catch();
}
}

View File

@@ -10,6 +10,7 @@ export enum OB11MessageType {
// 消息接口定义
export interface OB11Message {
real_seq?: string;// 自行扩展
temp_source?: number;
message_sent_type?: string;
target_id?: number; // 自己发送消息/私聊消息
@@ -30,10 +31,6 @@ export interface OB11Message {
post_type?: EventType;
raw?: RawMessage;
}
export interface OB11ArrayMessage extends OB11Message {
message_format: 'array';
message: OB11MessageData[];
}
// 合并转发消息接口定义
export interface OB11ForwardMessage extends OB11Message {

View File

@@ -1,57 +0,0 @@
import { Mutex } from "async-mutex";
export class ChatHotManager {
// 存储群组的热度信息键为群组ID值为使用时间和使用计数
private chatHot: Map<string, { usetime: number, usecount: number }> = new Map();
// 互斥锁,确保热度信息的读写操作是安全的
private chatHotMutex = new Mutex();
/**
* 获取群组是否需要回复
* @param groupId 群组ID
* @returns 是否需要回复
*/
async getHot(groupId: string): Promise<boolean> {
return await this.chatHotMutex.runExclusive(async () => {
const chatHotData = this.chatHot.get(groupId);
const currentTime = Date.now();
if (chatHotData) {
console.log("原始热度", chatHotData?.usecount, currentTime - chatHotData.usetime > 30000);
if (currentTime - chatHotData.usetime > 30000) {
chatHotData.usetime = currentTime;
chatHotData.usecount = 0;
this.chatHot.set(groupId, chatHotData);
// 超出时间段重置计数
return false;
} else if (currentTime - chatHotData.usetime < 30000 && chatHotData.usecount > 0 && chatHotData.usecount < 2) {
// 在短时间内没请求,回复
return true;
}
// 在时间段内有请求,回复
return false;
}
// 初始化,不回复
this.chatHot.set(groupId, { usetime: currentTime, usecount: 0 });
return false;
});
}
/**
* 增加群组的热度计数
* @param groupId 群组ID
*/
async incrementHot(groupId: string) {
await this.chatHotMutex.runExclusive(() => {
const chatHotData = this.chatHot.get(groupId);
const currentTime = Date.now();
if (chatHotData) {
// 引用增加
chatHotData.usecount += 1;
this.chatHot.set(groupId, chatHotData);
} else {
// 初始化
this.chatHot.set(groupId, { usetime: currentTime, usecount: 1 });
}
});
}
}

View File

@@ -1,20 +0,0 @@
export const PROMPT_MEMROY = `
你是合并、更新和组织记忆的专家。当提供现有记忆和新信息时,你的任务是合并和更新记忆列表,以反映最准确和最新的信息。你还会得到每个现有记忆与新信息的匹配分数。确保利用这些信息做出明智的决定,决定哪些记忆需要更新或合并。
指南:
- 消除重复的记忆,合并相关记忆,以确保列表简洁和更新。
- 记忆根据人物区分,同时不必每次重复人物账号,只需在记忆中提及一次即可。
- 如果一个记忆直接与新信息矛盾,请批判性地评估两条信息:
- 如果新记忆提供了更近期或更准确的更新,用新记忆替换旧记忆。
- 如果新记忆看起来不准确或细节较少,保留旧记忆并丢弃新记忆。
- 注意区分对应人物的记忆和印象, 不要产生混淆人物的印象和记忆。
- 在所有记忆中保持一致且清晰的风格,确保每个条目简洁而信息丰富。
- 如果新记忆是现有记忆的变体或扩展,更新现有记忆以反映新信息。
`;
export const API_KEY = 'sk-xxxx';//需要配置
export const BASE_URL = 'https://vip.bili2233.work/v1';
export const MODEL = 'gemini-2.0-flash-thinking-exp';
export const BOT_NAME = '千千';
export const BOT_ADMIN = '1627126029';
export const PROMPT = `你的名字叫千千`;
export const CQCODE = `增加一下能力通过不同昵称和QQ进行区分哦,注意理清回复消息的人物, At人直接发送 [CQ:at,qq=1234] 这样可以直接at某个人喵这 回复消息需要发送[CQ:reply,id=xxx]这种格式叫CQ码,发送图片等操作你可以从聊天记录中学习哦, 如果聊天记录的image CQ码 maface类型你可以直接复制使用`;
export const MEMORY_FILE = 'F:/Qian/memory.json';

View File

@@ -1,9 +0,0 @@
import { ChatCompletionContentPart, ChatCompletionMessageParam } from "openai/resources";
export async function toSingleRole(msg: Array<any>) {
let ret = { role: 'user', content: new Array<ChatCompletionContentPart>() };
for (const m of msg) {
ret.content.push(...m.content as any)
}
return [ret] as Array<ChatCompletionMessageParam>;
}

View File

@@ -1,168 +1,11 @@
import { NapCatOneBot11Adapter, OB11ArrayMessage, OB11MessageData } from '@/onebot';
import { NapCatOneBot11Adapter, OB11Message } from '@/onebot';
import { NapCatCore } from '@/core';
import { ActionMap } from '@/onebot/action';
import { OB11PluginAdapter } from '@/onebot/network/plugin';
import { OpenAI } from 'openai';
import { ChatCompletionContentPart, ChatCompletionMessageParam } from 'openai/resources';
import { MemoryManager } from './memory';
import { ChatHotManager } from './chathot';
import { API_KEY, BASE_URL, BOT_ADMIN, BOT_NAME, CQCODE, MODEL, PROMPT, PROMPT_MEMROY } from './config';
import { toSingleRole } from './helper';
const client = new OpenAI({ apiKey: API_KEY, baseURL: BASE_URL });
const chatHotManager = new ChatHotManager();
const memoryManager = new MemoryManager(mergeAndUpdateMemory);
async function createChatCompletionWithRetry(params: any, retries: number = 5): Promise<any> {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await client.chat.completions.create(params);
} catch (error) {
console.error(`Ai会话 ${attempt + 1} failed:`, error);
if (attempt === retries - 1) throw error;
}
export const plugin_onmessage = async (adapter: string, _core: NapCatCore, _obCtx: NapCatOneBot11Adapter, message: OB11Message, action: ActionMap, instance: OB11PluginAdapter) => {
if (message.raw_message === 'ping') {
const ret = await action.get('send_group_msg')?.handle({ group_id: String(message.group_id), message: 'pong' }, adapter, instance.config);
console.log(ret);
}
}
async function messageToOpenAi(adapter: string, msg: OB11MessageData[], groupId: string, action: ActionMap, plugin: OB11PluginAdapter, message: OB11ArrayMessage) {
const msgArray: Array<ChatCompletionContentPart> = [];
let ret = '';
for (const m of msg) {
if (m.type === 'reply') {
ret += `[CQ:reply,id=${m.data.id}]`;
} else if (m.type === 'text') {
ret += m.data.text;
} else if (m.type === 'at') {
const memberInfo = await action.get('get_group_member_info')
?.handle({ group_id: groupId, user_id: m.data.qq }, adapter, plugin.config);
ret += `[CQ:at=${m.data.qq},name=${memberInfo?.data?.nickname}]`;
} else if (m.type === 'image') {
ret += `[CQ:image,file=${m.data.url}]`;
msgArray.push({ type: 'image_url', image_url: { url: m.data.url?.replace('https://', 'http://') || '' } });
} else if (m.type === 'face') {
ret += '[CQ:face,id=' + m.data.id + ']';
}
}
msgArray.push({ type: 'text', text: `${message.sender.nickname}(${message.sender.user_id})发送了消息(消息id:${message.message_id}) :` + ret });
return msgArray.reverse();
}
async function mergeAndUpdateMemory(existingMemories: Array<ChatCompletionContentPart>[], newMemory: Array<ChatCompletionContentPart>[]): Promise<string> {
const completion = await createChatCompletionWithRetry({
messages: await toSingleRole([
{ role: 'user', content: [{ type: 'text', text: PROMPT_MEMROY }] },
{ role: 'user', content: [{ type: 'text', text: '接下来是旧记忆' }] },
...(existingMemories.map(msg => ({ role: 'user', content: msg.filter(e => e.type === 'text') }))),
{ role: 'user', content: [{ type: 'text', text: '接下来是新记忆' }] },
...(newMemory.map(msg => ({ role: 'user', content: msg.filter(e => e.type === 'text') })))]),
model: MODEL
});
return completion.choices[0]?.message.content || '';
}
async function generateChatCompletion(contentData: Array<ChatCompletionMessageParam>): Promise<string> {
const chatCompletion = await createChatCompletionWithRetry({ messages: contentData, model: MODEL });
return chatCompletion.choices[0]?.message.content || '';
}
async function handleClearMemoryCommand(groupId: string, type: 'short' | 'long', action: ActionMap, adapter: string, instance: OB11PluginAdapter) {
await memoryManager.clearMemory(groupId, type);
const message = type === 'short' ? '短期上下文已清理' : '长期上下文已清理';
await sendGroupMessage(groupId, message, action, adapter, instance);
}
async function sendGroupMessage(groupId: string, text: string, action: ActionMap, adapter: string, instance: OB11PluginAdapter) {
return await action.get('send_group_msg')?.handle({ group_id: String(groupId), message: text }, adapter, instance.config);
}
async function prepareContentData(message: OB11ArrayMessage, msgArray: Array<ChatCompletionContentPart>, prompt: string, reply?: Array<ChatCompletionContentPart>) {
const group_id = message.group_id?.toString()!;
const longTermMemoryList = memoryManager.getLongTermMemory(group_id);
let shortTermMemoryList = memoryManager.getShortTermMemory(group_id);
let data = shortTermMemoryList.map(msg => ({ role: 'user' as const, content: msg.filter(e => e.type === 'text') }));
return await toSingleRole([
{ role: 'user', content: [{ type: 'text', text: prompt }] },
{ role: 'user', content: [{ type: 'text', text: '接下来是长时间记忆' }] },
{ role: 'user', content: [{ type: 'text', text: longTermMemoryList }] },
{ role: 'user', content: [{ type: 'text', text: '接下来是短时间记忆' }] },
...data,
{ role: 'user', content: [{ type: 'text' as const, text: '接下来是本次引用消息' }] },
...(reply ? [{ role: 'user' as const, content: reply }] : []),
{ role: 'user', content: [{ type: 'text' as const, text: '接下来是当前对话' }] },
{ role: 'user', content: msgArray }
]);
}
async function handleChatResponse(message: OB11ArrayMessage, msgArray: Array<ChatCompletionContentPart>, adapter: string, action: ActionMap, instance: OB11PluginAdapter, _core: NapCatCore, reply?: Array<ChatCompletionContentPart>) {
const prompt = `请根据下面聊天内容,继续与 ${message?.sender?.card || message?.sender?.nickname} 进行对话。${CQCODE},注意回复内容只用输出内容,不要提及此段话,注意一定不要使用markdown,请采用纯文本回复。你的人设:${PROMPT}`;
const contentData = await prepareContentData(message, msgArray, prompt, reply);
const msgRet = await generateChatCompletion(contentData);
const sentMsg = await sendGroupMessage(message.group_id?.toString()!, msgRet, action, adapter, instance);
return { id: sentMsg?.data?.message_id, text: msgRet };
}
async function shouldRespond(message: OB11ArrayMessage, core: NapCatCore, oriMsg: any, currentHot: boolean, msgArray: Array<ChatCompletionContentPart>, reply?: Array<ChatCompletionContentPart>): Promise<boolean> {
if (
!message.raw_message.startsWith(BOT_NAME) &&
!message.message.find(e => e.type == 'at' && e.data.qq == core.selfInfo.uin) &&
oriMsg?.sender.user_id.toString() !== core.selfInfo.uin
) {
console.log("聊天热度", currentHot ? '热度高' : '热度低');
if (currentHot && msgArray.length > 0) {
const prompt = `请根据在群内聊天与 ${message.sender.card || message.sender?.nickname} 发送的聊天消息推测本次消息是否应该回复。在上下文关系并非强相关的话题和图片不要随意回复,根据上下文非常明显需要时才进行回复,否则不回复,注意尤其减少对图片消息的回应可能性, 注意回复内容只用输出2 - 3个字, 一定注意不想回复请输出不回复三个字即可, 想回复输出回复即可,一定不要给出现任何多余的字, 你的人设:${PROMPT}`;
const contentData = await prepareContentData(message, msgArray, prompt, reply);
const msgRet = await generateChatCompletion(contentData);
console.log('Ai回应判断:' + msgRet)
if (msgRet.indexOf('不回复') !== -1) {
return false;
}
} else {
return false;
}
}
return true;
}
async function handleClearMemory(message: OB11ArrayMessage, action: ActionMap, adapter: string, instance: OB11PluginAdapter) {
if (message.raw_message === '/清除短期上下文' && message.sender.user_id.toString() === BOT_ADMIN) {
await handleClearMemoryCommand(message.group_id?.toString()!, 'short', action, adapter, instance);
return true;
}
if (message.raw_message === '/清除长期上下文' && message.sender.user_id.toString() === BOT_ADMIN) {
await handleClearMemoryCommand(message.group_id?.toString()!, 'long', action, adapter, instance);
return true;
}
return false;
}
export const plugin_onmessage = async (
adapter: string,
core: NapCatCore,
_obCtx: NapCatOneBot11Adapter,
message: OB11ArrayMessage,
action: ActionMap,
instance: OB11PluginAdapter
) => {
const currentHot = await chatHotManager.getHot(message.group_id?.toString()!);
const oriMsgId = message.message.find(e => e.type == 'reply')?.data.id;
const oriMsg = (oriMsgId ? await action.get('get_msg')?._handle({ message_id: oriMsgId }, adapter, instance.config) : undefined) as OB11ArrayMessage | undefined;
const msgArray = await messageToOpenAi(adapter, message.message, message.group_id?.toString()!, action, instance, message);
if (!msgArray) return;
await memoryManager.updateMemory(message.group_id?.toString()!, [msgArray], core.selfInfo.uin);
if (await handleClearMemory(message, action, adapter, instance)) return;
const oriMsgOpenai = oriMsg ? await messageToOpenAi(adapter, oriMsg.message, oriMsg.group_id?.toString()!, action, instance, oriMsg) : undefined;
if (await shouldRespond(message, core, oriMsg, currentHot, msgArray, oriMsgOpenai)) {
const sentMsg = await handleChatResponse(message, msgArray, adapter, action, instance, core, oriMsgOpenai);
await memoryManager.updateMemory(message.group_id?.toString()!, [[{
type: 'text',
text: `我(群昵称: 乔千)(${core.selfInfo.uin})发送了消息(消息id: ${sentMsg.id}) : ` + sentMsg.text
}]], core.selfInfo.uin);
await chatHotManager.incrementHot(message.group_id?.toString()!);
}
};
};

View File

@@ -1,102 +0,0 @@
import { Mutex } from "async-mutex";
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { ChatCompletionContentPart } from "openai/resources";
import { MEMORY_FILE } from "./config";
export class MemoryManager {
private longTermMemory: Map<string, string> = new Map();
private shortTermMemory: Map<string, Array<ChatCompletionContentPart>[]> = new Map();
private memoryCount: Map<string, number> = new Map();
private memMutex = new Mutex();
private SHORT_TERM_MEMORY_LIMIT = 100;
private mergeAndUpdateMemory: (currentMemory: Array<ChatCompletionContentPart>[], newMessages: Array<ChatCompletionContentPart>[]) => Promise<string>;
constructor(mergeAndUpdateMemory: (currentMemory: Array<ChatCompletionContentPart>[], newMessages: Array<ChatCompletionContentPart>[]) => Promise<string>) {
this.mergeAndUpdateMemory = mergeAndUpdateMemory;
this.loadFromJson(MEMORY_FILE);
setInterval(() => this.saveFromJson(MEMORY_FILE), 1000 * 60 * 5);
}
async updateMemory(
groupId: string,
newMessages: Array<ChatCompletionContentPart>[],
selfuin: string
) {
const currentMemory = this.shortTermMemory.get(groupId) || [];
const memCount = await this.incrementMemoryCount(groupId);
currentMemory.push(...newMessages);
if (memCount > this.SHORT_TERM_MEMORY_LIMIT) {
await this.handleMemoryOverflow(groupId, currentMemory, newMessages, selfuin);
}
this.shortTermMemory.set(groupId, currentMemory);
}
async incrementMemoryCount(groupId: string): Promise<number> {
return this.memMutex.runExclusive(() => {
const memCount = (this.memoryCount.get(groupId) || 0) + 1;
this.memoryCount.set(groupId, memCount);
return memCount;
});
}
async handleMemoryOverflow(
groupId: string,
currentMemory: Array<ChatCompletionContentPart>[],
newMessages: Array<ChatCompletionContentPart>[],
selfuin: string
) {
await this.memMutex.runExclusive(async () => {
const containsBotName = currentMemory.some(messages =>
messages.some(msg => msg.type === 'text' && msg.text.includes(selfuin))
);
if (containsBotName) {
const mergedMemory = await this.mergeAndUpdateMemory(currentMemory, newMessages);
this.longTermMemory.set(groupId, mergedMemory);
}
this.shortTermMemory.set(groupId, currentMemory.slice(-this.SHORT_TERM_MEMORY_LIMIT));
this.memoryCount.set(groupId, 0);
});
}
async clearMemory(groupId: string, type: 'short' | 'long') {
if (type === 'short') {
this.shortTermMemory.set(groupId, []);
} else {
this.longTermMemory.set(groupId, '');
}
}
getLongTermMemory(groupId: string): string {
return this.longTermMemory.get(groupId) || '';
}
getShortTermMemory(groupId: string): Array<ChatCompletionContentPart>[] {
return this.shortTermMemory.get(groupId) || [];
}
toJson() {
return {
longTermMemory: Array.from(this.longTermMemory.entries()),
shortTermMemory: Array.from(this.shortTermMemory.entries()),
memoryCount: Array.from(this.memoryCount.entries())
}
}
saveFromJson(file: string) {
let json = JSON.stringify(this.toJson(), null, 2);
writeFileSync(file, json);
}
loadFromJson(file: string) {
if (existsSync(file)) {
let json = readFileSync(file, { encoding: 'utf-8' });
let obj = JSON.parse(json);
this.longTermMemory = new Map(obj.longTermMemory);
this.shortTermMemory = new Map(obj.shortTermMemory);
this.memoryCount = new Map(obj.memoryCount);
}
}
}

View File

@@ -29,7 +29,7 @@ export let webUiPathWrapper: NapCatPathWrapper;
const MAX_PORT_TRY = 100;
import * as net from 'node:net';
import { WebUiDataRuntime } from './src/helper/Data';
export let webUiRuntimePort = 6099;
export async function InitPort(parsedConfig: WebUiConfigType): Promise<[string, number, string]> {
try {
await tryUseHost(parsedConfig.host);
@@ -45,6 +45,7 @@ export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapp
webUiPathWrapper = pathWrapper;
WebUiConfig = new WebUiConfigWrapper();
const [host, port, token] = await InitPort(await WebUiConfig.GetWebUIConfig());
webUiRuntimePort = port;
if (port == 0) {
logger.log('[NapCat] [WebUi] Current WebUi is not run.');
return;

View File

@@ -8,8 +8,7 @@ const external = [
'silk-wasm',
'ws',
'express',
'@ffmpeg.wasm/core-mt',
'openai'
'@ffmpeg.wasm/core-mt'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();