Compare commits

...

7 Commits

Author SHA1 Message Date
手瓜一十雪
d59771ac2f fix: error 2024-08-12 17:14:44 +08:00
手瓜一十雪
fba2078fc0 chore: 过滤重复消息 2024-08-12 17:04:32 +08:00
手瓜一十雪
20a37fe2de Merge pull request #239 from clansty/main
fix: revert get_friends_with_category return type
2024-08-12 16:40:41 +08:00
Clansty
8f6d26b65c fix: revert get_friends_with_category return type 2024-08-12 16:35:08 +08:00
Wesley F. Young
b58a194c8a fix: constructor signature mismatch 2024-08-12 16:15:39 +08:00
手瓜一十雪
52f1b0a0ce chore: 鸣谢 2024-08-12 14:57:12 +08:00
手瓜一十雪
c2b8fb223b chore: 鸣谢 2024-08-12 14:56:49 +08:00
19 changed files with 75 additions and 50 deletions

View File

@@ -12,13 +12,6 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
- [x] **低占用**:无头模式占用资源极低,适合在服务器上运行
- [x] **WebUI**:自带 WebUI 支持,远程管理更加便捷
### V2 Roadmap
- **依赖注入**:彻底理清模块间依赖,明确加载顺序,根除滥用变量导出、`setTimeout` 初始化
- **ES 出包**:可读性更高,报错更可读
- **代码优化**:减少代码冗余,提高代码质量
- **输出标准化**:统一控制台输出格式
## 如何使用
可前往 [Release](https://github.com/NapNeko/NapCatQQ/releases/) 页面下载最新版本
@@ -29,13 +22,13 @@ NapCatQQ 是现代化的基于 NTQQ 的 Bot 协议端实现。
[Telegram Link](https://t.me/+nLZEnpne-pQ1OWFl)
## 鸣谢名单
## 附加协议
禁止未授权任何项目使用Core部分代码用于二次开发与分发
[Lagrange](https://github.com/LagrangeDev/Lagrange.Core)
## 鸣谢名单
感谢 [LLOneBot](https://github.com/LLOneBot/LLOneBot) 提供初始版本基础
感谢 [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持
---
**任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**
## 附加协议
禁止任何项目使用core部分代码用于二次开发与分发
**任何使用本仓库代码的地方,都应当严格遵守[本仓库开源许可](./LICENSE)。**

View File

@@ -4,7 +4,7 @@
"name": "NapCat",
"slug": "NapCat",
"description": "OneBot v11 protocol implementation with NapCat logic",
"version": "2.0.1",
"version": "2.0.2",
"icon": "./logo.png",
"authors": [
{

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "2.0.1",
"version": "2.0.2",
"scripts": {
"build:framework": "vite build --mode framework",
"build:shell": "vite build --mode shell",

View File

@@ -2,7 +2,7 @@ import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
export const napcat_version = '2.0.1';
export const napcat_version = '2.0.2';
export class NapCatPathWrapper {
binaryPath: string;

31
src/common/utils/LRU.ts Normal file
View File

@@ -0,0 +1,31 @@
export class LRUCache<K, V> {
private capacity: number;
private cache: Map<K, V>;
constructor(capacity: number) {
this.capacity = capacity;
this.cache = new Map<K, V>();
}
public get(key: K): V | undefined {
const value = this.cache.get(key);
if (value !== undefined) {
// Move the accessed key to the end to mark it as most recently used
this.cache.delete(key);
this.cache.set(key, value);
}
return value;
}
public put(key: K, value: V): void {
if (this.cache.has(key)) {
// If the key already exists, move it to the end to mark it as most recently used
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// If the cache is full, remove the least recently used key (the first one in the map)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}

View File

@@ -54,21 +54,21 @@ export class NTQQFriendApi {
uids.push(
...buddyListV2.flatMap(item => {
item.buddyUids.forEach(uid => {
categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName });
categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName });
});
return item.buddyUids;
}));
const data = await this.core.eventWrapper.callNoListenerEvent<NodeIKernelProfileService['getCoreAndBaseInfo']>(
'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids,
);
return Array.from(data).map(([key, value]) => {
const category = categoryMap.get(key);
return category ? {
...value,
categoryId: category.categoryId,
categroyName: category.categroyName,
} : value;
});
return buddyListV2.map(category => ({
categoryId: category.categoryId,
categorySortId: category.categorySortId,
categoryName: category.categroyName,
categoryMbCount: category.categroyMbCount,
onlineCount: category.onlineCount,
buddyList: category.buddyUids.map(uid => data.get(uid)!).filter(value => value),
}));
}
async isBuddy(uid: string) {

View File

@@ -231,7 +231,7 @@ export class NTQQUserApi {
//后期改成流水线处理
async getUidByUinV2(Uin: string) {
let uid = (await this.context.session.getProfileService().getUidByUinV2('FriendsServiceImpl', [Uin])).get(Uin);
let uid = (await this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [Uin])).get(Uin);
if (uid) return uid;
uid = (await this.context.session.getGroupService().getUidByUins([Uin])).uids.get(Uin);
if (uid) return uid;

View File

@@ -176,10 +176,7 @@ export interface SimpleInfo {
intimate: any | null;
}
export interface FriendV2 extends SimpleInfo {
categoryId?: number;
categroyName?: string;
}
export type FriendV2 = SimpleInfo;
export interface SelfStatusInfo {
uid: string;

View File

@@ -18,7 +18,7 @@ export enum ProfileBizType {
export interface NodeIKernelProfileService {
getUidByUinV2(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;//uin->uid
getUidByUin(callfrom: string, uin: Array<string>): Promise<Map<string, string>>;//uin->uid
getUinByUid(callfrom: string, uid: Array<string>): Promise<Map<string, string>>;

View File

@@ -8,7 +8,10 @@ export class GetFriendWithCategory extends BaseAction<void, any> {
async _handle(payload: void) {
if (this.CoreContext.context.basicInfoWrapper.requireMinNTQQBuild('26702')) {
//全新逻辑
return OB11Constructor.friendsV2(await this.CoreContext.apis.FriendApi.getBuddyV2ExWithCate(true));
return (await this.CoreContext.apis.FriendApi.getBuddyV2ExWithCate(true)).map(category => ({
...category,
buddyList: OB11Constructor.friendsV2(category.buddyList),
}));
} else {
throw new Error('this ntqq version not support, must be 26702 or later');
}

View File

@@ -69,7 +69,7 @@ class GetGroupMemberInfo extends BaseAction<Payload, OB11GroupMember> {
}
}
} else {
// Mlikiowa V2.0.1 Refactor Todo
// Mlikiowa V2.0.2 Refactor Todo
// retMember.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.lastSpeakTime || date.toString());
// retMember.join_time = parseInt((await getGroupMember(payload.group_id.toString(), retMember.user_id))?.joinTime || date.toString());
}

View File

@@ -83,7 +83,7 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
}
}
} else {
// Mlikiowa V2.0.1 Refactor Todo
// Mlikiowa V2.0.2 Refactor Todo
// _groupMembers.forEach(async item => {
// item.last_sent_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.lastSpeakTime || date.toString());
// item.join_time = parseInt((await getGroupMember(payload.group_id.toString(), item.user_id))?.joinTime || date.toString());

View File

@@ -44,7 +44,7 @@ class DeleteMsg extends BaseAction<Payload, void> {
await NTQQMsgApi.recallMsg(msg.Peer, [msg.MsgId]);
const data = await ret;
if (!data) {
throw new Error('Recall failed');
//throw new Error('Recall failed');
}
//await sleep(100);
//await NTQQMsgApi.getMsgsByMsgId(msg.Peer, [msg.MsgId]);

View File

@@ -56,7 +56,7 @@ const _handlers: {
if (atQQ === 'all') return SendMsgElementConstructor.at(coreContext, atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
// Mlikiowa V2.0.1 Refactor Todo
// Mlikiowa V2.0.2 Refactor Todo
const uid = await coreContext.apis.UserApi.getUidByUinV2(atQQ);
if (!uid) throw new Error('Get Uid Error');
return SendMsgElementConstructor.at(coreContext, atQQ, uid, AtType.atUser, '');
@@ -161,7 +161,7 @@ const _handlers: {
} else {
postData = data;
}
// Mlikiowa V2.0.1 Refactor Todo
// Mlikiowa V2.0.2 Refactor Todo
const signUrl = obContext.configLoader.configData.musicSignUrl;
if (!signUrl) {
if (data.type === 'qq') {

View File

@@ -408,7 +408,7 @@ export class OB11Constructor {
return;
}
//log("group msg", msg);
// Mlikiowa V2.0.1 Refactor Todo
// Mlikiowa V2.0.2 Refactor Todo
// if (msg.senderUin && msg.senderUin !== '0') {
// const member = await getGroupMember(msg.peerUid, msg.senderUin);
// if (member && member.cardName !== msg.sendMemberName) {
@@ -656,8 +656,6 @@ export class OB11Constructor {
remark: friend.coreInfo.nick,
sex: sexValue,
level: 0,
categroyName: friend.categroyName,
categoryId: friend.categoryId,
});
});
return data;

View File

@@ -33,6 +33,7 @@ import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '@/onebot/event/not
import { OB11GroupRequestEvent } from '@/onebot/event/request/OB11GroupRequest';
import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRecallNoticeEvent';
import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent';
import { LRUCache } from '@/common/utils/LRU';
//OneBot实现类
export class NapCatOneBot11Adapter {
@@ -87,7 +88,7 @@ export class NapCatOneBot11Adapter {
if (ob11Config.http.enablePost) {
ob11Config.http.postUrls.forEach(url => {
this.networkManager.registerAdapter(new OB11ActiveHttpAdapter(
url, ob11Config.token, this.core
url, ob11Config.token, this.core, this
));
});
}
@@ -116,7 +117,7 @@ export class NapCatOneBot11Adapter {
await WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig: OB11Config) => {
const prev = this.configLoader.configData;
this.configLoader.save(newConfig);
this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`)
this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
await this.reloadNetwork(prev, newConfig);
});
}
@@ -145,7 +146,7 @@ export class NapCatOneBot11Adapter {
if (now.http.enablePost) {
now.http.postUrls.forEach(url => {
this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
url, now.token, this.core
url, now.token, this.core, this
));
});
} else {
@@ -159,7 +160,7 @@ export class NapCatOneBot11Adapter {
);
for (const url of added) {
await this.networkManager.registerAdapterAndOpen(new OB11ActiveHttpAdapter(
url, now.token, this.core
url, now.token, this.core, this
));
}
}
@@ -215,7 +216,6 @@ export class NapCatOneBot11Adapter {
private initMsgListener() {
const msgListener = new MsgListener();
msgListener.onInputStatusPush = async data => {
const uin = await this.core.apis.UserApi.getUinByUidV2(data.fromUin);
this.context.logger.log(`[Notice] [输入状态] ${uin} ${data.statusText}`);
@@ -245,12 +245,15 @@ export class NapCatOneBot11Adapter {
.catch(e => this.context.logger.logError('处理消息失败', e));
}
};
const msgIdSend = new LRUCache<string, boolean>(100);
msgListener.onMsgInfoListUpdate = async msgList => {
this.emitRecallMsg(msgList)
.catch(e => this.context.logger.logError('处理消息失败', e));
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
// console.log(msg);
if (!!msgIdSend.get(msg.msgId)) continue;
msgIdSend.put(msg.msgId, true);
if (msg.sendStatus == 2) {
// 完成后再post
OB11Constructor.message(this.core, msg, this.configLoader.configData.messagePostFormat)

View File

@@ -7,7 +7,7 @@ export interface OB11User {
age?: number;
qid?: string;
login_days?: number;
categroyName?: string;
categoryName?: string;
categoryId?: number;
}

View File

@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
undefined,
SettingButton('V2.0.1', 'napcat-update-button', 'secondary'),
SettingButton('V2.0.2', 'napcat-update-button', 'secondary'),
),
]),
SettingList([

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V2.0.1", "napcat-update-button", "secondary")
SettingButton("V2.0.2", "napcat-update-button", "secondary")
)
]),
SettingList([